├── .babelrc
├── .editorconfig
├── .eslintrc
├── .gitignore
├── .idea
├── codeStyleSettings.xml
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── copyright
│ ├── adrien.xml
│ ├── profiles_settings.xml
│ └── rz.xml
├── encodings.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── misc.xml
├── modules.xml
├── pageblock.iml
├── vcs.xml
└── watcherTasks.xml
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── dist
├── main.amd.js
├── main.cjs.js
├── main.e6.js
├── main.esm.js
├── main.iife.js
└── main.umd.js
├── esdoc.json
├── examples
├── src
│ ├── css
│ │ └── demo.css
│ ├── img
│ │ ├── test1.jpg
│ │ ├── test2.jpg
│ │ ├── test2.webp
│ │ ├── test3.jpg
│ │ ├── test4.jpg
│ │ └── test5.jpg
│ ├── js
│ │ ├── ExampleNav.js
│ │ ├── api
│ │ │ └── Api.js
│ │ ├── app.js
│ │ ├── blocks
│ │ │ ├── DefaultBlock.js
│ │ │ ├── InViewBlock.js
│ │ │ └── UsersBlock.js
│ │ ├── config
│ │ │ └── config.example.js
│ │ ├── factories
│ │ │ └── TransitionFactory.js
│ │ ├── pages
│ │ │ ├── DefaultPage.js
│ │ │ └── HomePage.js
│ │ ├── services
│ │ │ ├── Splashscreen.js
│ │ │ └── WebpackAsyncBlockBuilder.js
│ │ ├── transitions
│ │ │ ├── BigTransition.js
│ │ │ ├── DefaultTransition.js
│ │ │ ├── FadeTransition.js
│ │ │ └── SlideTransition.js
│ │ └── utils
│ │ │ └── utils.js
│ └── views
│ │ ├── index.html
│ │ ├── page1.html
│ │ └── partial.html
└── webpack
│ ├── build
│ ├── base.js
│ ├── environments.js
│ └── index.js
│ ├── config
│ ├── base.js
│ ├── environments.js
│ └── index.js
│ └── modules
│ └── HtmlWebpackMultiBuildPlugin.js
├── package.json
├── rollup.config.js
├── src
├── StartingBlocks.js
├── abstracts
│ ├── AbstractBlock.js
│ ├── AbstractBlockBuilder.js
│ ├── AbstractBootableService.js
│ ├── AbstractInViewBlock.js
│ ├── AbstractPage.js
│ ├── AbstractService.js
│ ├── AbstractSplashscreen.js
│ ├── AbstractTransition.js
│ └── AbstractTransitionFactory.js
├── bundle.js
├── dispatcher
│ └── Dispatcher.js
├── errors
│ ├── DependencyNotFulfilledException.js
│ └── UnknownServiceException.js
├── pages
│ └── DefaultPage.js
├── services
│ ├── BlockBuilder.js
│ ├── CacheProvider.js
│ ├── Dom.js
│ ├── History.js
│ ├── PageBuilder.js
│ ├── Pjax.js
│ └── Prefetch.js
├── transitions
│ └── DefaultTransition.js
├── types
│ └── EventTypes.js
└── utils
│ ├── BootstrapMedia.js
│ ├── Logger.js
│ ├── Scroll.js
│ ├── Utils.js
│ ├── debounce.js
│ ├── gaTrackErrors.js
│ └── polyfills.js
└── webpack.config.babel.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/preset-env", {
4 | "useBuiltIns": "usage"
5 | }]
6 | ],
7 | "plugins": [
8 | "@babel/plugin-transform-runtime",
9 | "@babel/plugin-syntax-dynamic-import",
10 | "@babel/plugin-syntax-import-meta",
11 | "@babel/plugin-proposal-class-properties",
12 | "@babel/plugin-proposal-json-strings",
13 | [
14 | "@babel/plugin-proposal-decorators",
15 | {
16 | "legacy": true
17 | }
18 | ],
19 | "@babel/plugin-proposal-function-sent",
20 | "@babel/plugin-proposal-export-namespace-from",
21 | "@babel/plugin-proposal-numeric-separator",
22 | "@babel/plugin-proposal-throw-expressions",
23 | "@babel/plugin-proposal-export-default-from",
24 | "@babel/plugin-proposal-logical-assignment-operators",
25 | "@babel/plugin-proposal-optional-chaining",
26 | [
27 | "@babel/plugin-proposal-pipeline-operator",
28 | {
29 | "proposal": "minimal"
30 | }
31 | ],
32 | "@babel/plugin-proposal-nullish-coalescing-operator",
33 | "@babel/plugin-proposal-do-expressions",
34 | "@babel/plugin-proposal-function-bind"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Unix-style newlines with a newline ending every file
2 | [*]
3 | indent_style = space
4 | indent_size = 4
5 | end_of_line = lf
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
10 | [*.md]
11 | trim_trailing_whitespace = false
12 |
13 | [Makefile]
14 | indent_style = tab
15 |
16 | [package.json]
17 | indent_size = 4
18 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "babel-eslint",
4 | "parserOptions": {
5 | "sourceType": "module"
6 | },
7 | "env": {
8 | "browser": true
9 | },
10 | "extends": ["standard"],
11 | "plugins": [
12 | "html",
13 | "dependencies"
14 | ],
15 | "rules": {
16 | // enable additional rules
17 | "indent": [
18 | "warn",
19 | 4
20 | ],
21 | "dependencies/case-sensitive": 1,
22 | "dependencies/no-cycles": 1,
23 | "dependencies/require-json-ext": 1
24 | },
25 | "globals" : {
26 | "DEVELOPMENT": false,
27 | "PRODUCTION": false,
28 | "ENVIRONMENT": false
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /build/app*.js
2 | /doc
3 | /examples/.cache
4 | /examples/dist/js/app.js
5 | /main.js
6 |
7 | # Created by https://www.gitignore.io/api/npm,node
8 |
9 | #!! ERROR: npm is undefined. Use list command to see defined gitignore types !!#
10 |
11 | ### Node ###
12 | # Logs
13 | logs
14 | *.log
15 | npm-debug.log*
16 |
17 | # Runtime data
18 | pids
19 | *.pid
20 | *.seed
21 | *.pid.lock
22 |
23 | # Directory for instrumented libs generated by jscoverage/JSCover
24 | lib-cov
25 |
26 | # Coverage directory used by tools like istanbul
27 | coverage
28 |
29 | # nyc test coverage
30 | .nyc_output
31 |
32 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
33 | .grunt
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (http://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules
43 | jspm_packages
44 |
45 | # Optional npm cache directory
46 | .npm
47 |
48 | # Optional eslint cache
49 | .eslintcache
50 |
51 | # Optional REPL history
52 | .node_repl_history
53 |
54 | # Output of 'npm pack'
55 | *.tgz
56 |
57 | # Yarn Integrity file
58 | .yarn-integrity
59 | yarn.lock
60 |
61 | # End of https://www.gitignore.io/api/npm,node
62 |
63 |
64 | # Created by https://www.gitignore.io/api/phpstorm
65 |
66 | ### PhpStorm ###
67 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
68 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
69 |
70 | # User-specific stuff:
71 | .idea/**/workspace.xml
72 | .idea/**/tasks.xml
73 | .idea/dictionaries
74 |
75 | # Sensitive or high-churn files:
76 | .idea/**/dataSources/
77 | .idea/**/dataSources.ids
78 | .idea/**/dataSources.xml
79 | .idea/**/dataSources.local.xml
80 | .idea/**/sqlDataSources.xml
81 | .idea/**/dynamic.xml
82 | .idea/**/uiDesigner.xml
83 |
84 | # Gradle:
85 | .idea/**/gradle.xml
86 | .idea/**/libraries
87 |
88 | # CMake
89 | cmake-build-debug/
90 |
91 | # Mongo Explorer plugin:
92 | .idea/**/mongoSettings.xml
93 |
94 | ## File-based project format:
95 | *.iws
96 |
97 | ## Plugin-specific files:
98 |
99 | # IntelliJ
100 | /out/
101 |
102 | # mpeltonen/sbt-idea plugin
103 | .idea_modules/
104 |
105 | # JIRA plugin
106 | atlassian-ide-plugin.xml
107 |
108 | # Cursive Clojure plugin
109 | .idea/replstate.xml
110 |
111 | # Crashlytics plugin (for Android Studio and IntelliJ)
112 | com_crashlytics_export_strings.xml
113 | crashlytics.properties
114 | crashlytics-build.properties
115 | fabric.properties
116 |
117 | ### PhpStorm Patch ###
118 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
119 |
120 | # *.iml
121 | # modules.xml
122 | # .idea/misc.xml
123 | # *.ipr
124 |
125 | # Sonarlint plugin
126 | .idea/sonarlint
127 |
128 | # End of https://www.gitignore.io/api/phpstorm
129 |
130 | # Config & Examples dist
131 | /examples/src/js/config/config.js
132 | /examples/dist/
133 |
--------------------------------------------------------------------------------
/.idea/codeStyleSettings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/copyright/adrien.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/copyright/rz.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/pageblock.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/watcherTasks.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - node
4 | - lts/*
5 | - '10'
6 | - '8'
7 | script:
8 | - cp examples/src/js/config/config.example.js examples/src/js/config/config.js
9 | - echo "Generating bundle.js…"
10 | - npm run build
11 | - npm run demo
12 | skip_cleanup: true
13 | jobs:
14 | include:
15 | - stage: npm release
16 | node_js: "lts/*"
17 | script:
18 | # Copy config for demo
19 | - cp examples/src/js/config/config.example.js examples/src/js/config/config.js
20 | - echo "Generating bundle.js…"
21 | # Only generate bundle, not demo
22 | - npm run build
23 | # Only generate demo
24 | - npm run demo
25 | - echo "Deploying to npm…"
26 | deploy:
27 | on:
28 | tags: true
29 | # Do not reset files to send bundle.js
30 | # to NPMJS
31 | skip_cleanup: true
32 | email: ambroise@rezo-zero.com
33 | provider: npm
34 | api_key:
35 | secure: ghCDQ6wIjCRhYb3pfCMydSETFyJFkZ6B74NnyRFMP/7RtWCFv7K+g9xbvzta4ru/Y1lnM+GVYIpd9DkiwkrOi10foFGqSOnYfqDzwXN/DgdM1sgM2hMwWnh3cXljmJzU3MHJuu/QzxJDeadt3hMBo6GrYh5HXwAwAZ5egrnFcJC2x0szuORtGCFHJxV9dxQGyIw6LTwi9tU7sUY+0BNy/IjiMUDlOvSLIguL7VdZnq2/3o9YU+7EWgpCMSm9nKqdA4ChQ0lCKyom5JBTqZjE8O2krvVMbkEcIi9XBYLy8dbtZXZ1DXoA7HAuUgReVf3b6qra8rMCwbrsO7pzwseLvXcAFq4ZM0ZA1Ty04kZlyOmVJMhxDHjHX1mWuPrMCLau18M/E8qpyeUWArSTb2/hW51OKwqTZ2iSaiZVUmkyYpK8o5gI6OSWzMi/9EPAvZe4hl2e3fnYr7Hn4FCYB58lXGfh299KCZSSNuinAmGcRl5euJk/ZLKmpbGaLFJMulhNQcNnxMFsK/2q5m9Uqgv6UM+vF3IuKrWKyanR0PnPedHMhqo/6jWqIAcZ+kwXvC965y0xHFQpElZ45u36NSoqmQTMTr0wM+R6RTgT5808LviegHzSbMgXdDj9rweQphkWK2XnNTsLlFooQSdiXwBmXWgM+VRhPmVLD7eCS9vnI6w=
36 |
37 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## version 5.0.0 2018-03-06
4 |
5 | - Drop jquery and waitForImages
6 | - Remove `onLoad` method in `AbstractPage` and `AbstractBlock`
7 |
8 | ## Version 4.2.0: 2018-03-06
9 |
10 | - Change `classFactory.getBlockInstance()` method parameters order (`page, $cont, nodeType`). Make sure to update your `classFactory` file
11 | - You can now switch `classFactory.getBlockInstance()` to `async` and dynamically load your modules with `import('yourModule').then()`, see example for more details.
12 | - Optional web worker feature to load pages content. Add `workerEnabled` router option to true to enable
13 |
14 | ## Version 4.1.0: 2018-02-20
15 |
16 | - Change `classFactory.getPageInstance()` method parameters order and removed `isHome` parameter
17 | - Added new `data` object parameter in history `add` method if you want to manually add specific data
18 |
19 | ## Version 4.0.0: 2018-02-08
20 |
21 | - Big refactoring of all classes
22 | - No more need to bind manually all **links** in Pages, Blocks or Nav, everything is handled by *Pjax* class
23 | - Removed all Router constructor arguments except for `options` array
24 | - `AbstractNav` has been removed, do not extend it anymore.
25 | - Starting-blocks wrapper default ID is now `sb-wrapper`, you can change it in your `Router` options (i.e. `ajaxWrapperId: 'main-container'`)
26 | - *jquery*, *loglevel* and *jquery.waitforimages* are declared external. You should provide them manually as file or as CDN in your project.
27 | - *Pjax* is now using native `window.fetch` methods to perform XHR requests. Make sure to use necessary polyfills. **If window.fetch is not supported, ajax will be disabled.**
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Rezo Zero
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | #
2 | # Make commands for page block
3 | #
4 |
5 | # Ignore existing folders for these commands
6 | .PHONY: build doc update clean push-doc uninstall
7 |
8 | #
9 | # Default task
10 | #
11 | all: install build
12 |
13 | install : node_modules
14 |
15 | update :
16 | npm update;
17 |
18 | uninstall : clean
19 | rm -rf node_modules;
20 |
21 | watch :
22 | npm run dev;
23 |
24 | build :
25 | NODE_ENV=production TARGET=all npm run build;
26 | #
27 | # Clean generated files
28 | #
29 | clean :
30 | rm -rf doc;
31 | #
32 | # Generate documentation
33 | #
34 | doc :
35 | npm run doc;
36 | #
37 | # Push generated Documentation on Rezo Zero host.
38 | # Credentials required, of course
39 | push-doc : doc
40 | rsync -avcz -e "ssh -p 39001" doc/ core@startingblocks.rezo-zero.com:~/http/;
41 |
42 | node_modules:
43 | npm install;
44 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Starting Blocks
2 |
3 | *Starting Blocks* is a framework for ajax page, transitions and blocks written in vanilla JS,
4 | made by [Rezo Zero](https://www.rezo-zero.com/).
5 |
6 | [](https://www.npmjs.com/package/starting-blocks)
7 | [](https://www.npmjs.com/package/starting-blocks)
8 | [](https://travis-ci.org/rezozero/starting-blocks)
9 |
10 | * [Features](#features)
11 | * [Install with Yarn or NPM](#install-with-yarn-or-npm)
12 | * [Usage](#usage)
13 | * [Services](#services)
14 | - [Pjax](#pjax)
15 | - [Splashscreen](#splashscreen)
16 | - [TransitionFactory](#transitionfactory)
17 | * [DOM structure](#dom-structure)
18 | * [Page](#page)
19 | - [Page overridable methods](#page-overridable-methods)
20 | * [Block](#block)
21 | - [Common block overridable methods](#common-block-overridable-methods)
22 | - [In view block overrivable methods](#in-view-block-overrivable-methods)
23 | * [Options](#options)
24 | * [Events](#events)
25 | * [Docs](#docs)
26 | * [Naming conventions](#naming-conventions)
27 | * [Improving Starting Blocks](#improving-starting-blocks)
28 | * [Compatibility](#compatibility)
29 | * [Demo](#demo)
30 | * [Go further with Starting Blocks](#go-further-with-starting-blocks)
31 | * [Contributors](#contributors)
32 |
33 | ## Features
34 |
35 | - *Pjax* : fetch pages and handling `History` state
36 | - *Transition manager* : enhance your website with custom page transitions
37 | - *Page/Blocks* pattern : build dynamic and interactive sections (map, slideshow, Ajax forms, canvas…)
38 |
39 | And more...
40 |
41 | - In view detection
42 | - Splashscreen
43 | - Prefetch
44 | - Cache
45 |
46 | ## Install with Yarn or NPM
47 |
48 | ```shell
49 | yarn add starting-blocks
50 | ```
51 |
52 | ```shell
53 | npm install starting-blocks --save
54 | ```
55 |
56 | ## Usage
57 |
58 | *Starting Blocks* is bundled with *NPM* in order to use native *ES6* `import` syntax.
59 | We highly recommend to use a module bundler and to write your code in *ES6* syntax. If you use a bundler like
60 | ***Webpack***, it will be able to remove *dead code* (i.e. when a *Starting Blocks* service is not used) if you use *curly brace* syntax.
61 |
62 | Minimal configuration :
63 |
64 | ```js
65 | import StartingBlocks from 'starting-blocks'
66 | import HomePage from './pages/HomePage'
67 | import UsersBlock from './blocks/UsersBlock'
68 |
69 | // Create a new Starting Blocks instance
70 | const startingBlocks = new StartingBlocks({
71 | // ...options
72 | })
73 |
74 | // For each page or block sections: you must map the service name
75 | // with the data-node-type attribute (see DOM Structure section).
76 | // «c» parameter stands for Starting-Blocks service container
77 |
78 | // Register page services factory
79 | startingBlocks.instanceFactory('HomePage', c => {
80 | return new HomePage(c)
81 | })
82 |
83 | // Register block services factory
84 | startingBlocks.instanceFactory('UsersBlock', c => {
85 | return new UsersBlock(c)
86 | })
87 |
88 | // 🚀 Boot the whole thing
89 | startingBlocks.boot()
90 | ```
91 |
92 | ## Services
93 |
94 | *Starting Blocks* use a dependency injection container to delegate object creation and handle dependencies.
95 | You can use or provide these standard services to enhance your website :
96 |
97 | | Service name | Init type | Ready to use | Dependencies | Description
98 | | --- | --- | --- | --- | --- |
99 | | `Pjax` | `bootableProvider` | true | `History` | Enable Ajax navigation on all your website internal links
100 | | `History` | `provider` | true | | | Helps to keep track of the navigation state
101 | | `Prefetch` | `bootableProvider` | true | `Pjax` | Prefetch links on mouse enter (useful with `Pjax`)
102 | | `CacheProvider` | `provider` | true | | Cache ajax requests (useful with `Pjax`)
103 | | `Splashscreen` | `bootableProvider` | false | | Add a splash screen for the first init
104 | | `TransitionFactory` | `bootableProvider` | false | | Instantiate page transitions objects according to your *Pjax* context
105 |
106 | #### Pjax
107 |
108 | ```js
109 | import StartingBlocks, { History, Pjax } from 'starting-blocks'
110 |
111 | // ...Instantiate starting blocks
112 |
113 | // Add service
114 | startingBlocks.provider('History', History)
115 | startingBlocks.bootableProvider('Pjax', Pjax)
116 |
117 | // ...Boot
118 | ```
119 |
120 | ⚠️ Don't forget to prepare your DOM adding specific *data attributes* and required *classes*, see [DOM structure section](#dom-structure)
121 |
122 | #### Splashscreen
123 |
124 | We implemented a *Splashscreen* service that is triggered at the first website launch.
125 | Create your own class that extends our `AbstractSplashscreen`:
126 |
127 | ```js
128 | import { AbstractSplashscreen, Dispatcher, EventTypes } from 'starting-blocks'
129 |
130 | export default class Splashscreen extends AbstractSplashscreen {
131 | constructor (container) {
132 | super(container, 'Splashscreen')
133 | //... custom values
134 | }
135 |
136 | // You need to override this method
137 | hide () {
138 | return new Promise(resolve => {
139 | // custom logic, animations...
140 | resolve()
141 | })
142 | }
143 | }
144 | ```
145 |
146 | ```js
147 | import Splashscreen from './Splashscreen'
148 |
149 | // ...Instantiate starting blocks
150 |
151 | // Add service
152 | startingBlocks.bootableProvider('Splashscreen', Splashscreen)
153 |
154 | // ...Boot
155 | ```
156 |
157 | #### TransitionFactory
158 |
159 | When AJAX navigation is enabled, transitions are triggered to manage animations between pages.
160 | To choose a custom transition, you can set `data-transition` attribute with a transition name on each link:
161 | ```html
162 | Contact
163 | ```
164 |
165 | Then, create a `TransitionFactory` class and register it into **Starting Blocks** as a service.
166 | In this class, you must implement `getTransition (previousState, state)` method.
167 | This method will be called on each transition and will give you access to `history` state informations:
168 |
169 | - `previousState` and `state`
170 | - **transitionName** : `data-transition` attributes of the clicked link
171 | - **context** : equal to `"history"`, `"link"`
172 |
173 | Example:
174 |
175 | ```javascript
176 | // src/factories/TransitionFactory.js
177 | import DefaultTransition from './transitions/DefaultTransition';
178 | import FadeTransition from './transitions/FadeTransition';
179 |
180 | export default class TransitionFactory {
181 | getTransition (previousState, state) {
182 | switch (state.transitionName) {
183 | case 'fade':
184 | return new FadeTransition()
185 | default:
186 | return new DefaultTransition()
187 | }
188 | }
189 | }
190 | ```
191 |
192 | How to register your Transition factory service?
193 |
194 | ```js
195 | import TransitionFactory from '../factories/TransitionFactory'
196 |
197 | // ...Instantiate starting blocks
198 |
199 | // Add service
200 | startingBlocks.provider('TransitionFactory', TransitionFactory)
201 |
202 | // ...Boot
203 | ```
204 |
205 | To create a new transition you need to write a new class extending our `AbstractTransition` boilerplate. Implement `start()` method and use `Promises` to manage your animation timeline.
206 | ⚠️ Be careful, you have to wait for `this.newPageLoading` Promise resolution to make sure the new page DOM is ready.
207 | Then, you have access to old and new `Page` instances.
208 |
209 | Example with *fade* animation (we use *TweenLite* from [gsap](https://github.com/greensock/GreenSock-JS) for this example):
210 |
211 | ```javascript
212 | // src/transitions/FadeTransition.js
213 | import AbstractTransition from '../AbstractTransition'
214 | import { TweenLite } from 'gsap'
215 |
216 | /**
217 | * Fade Transition example. Fade Out / Fade In between old and new pages.
218 | */
219 | export default class FadeTransition extends AbstractTransition {
220 | /**
221 | * Entry point of the animation
222 | * Automatically called on init()
223 | */
224 | start () {
225 | // Wait new content and the end of fadeOut animation
226 | // this.newPageLoading is a Promise which is resolved when the new content is loaded
227 | Promise.all([this.newPageLoading, this.fadeOut()])
228 | // then fadeIn the new content
229 | .then(this.fadeIn.bind(this))
230 | }
231 |
232 | /**
233 | * Fade out the old content.
234 | * @returns {Promise}
235 | */
236 | fadeOut () {
237 | return new Promise(resolve => {
238 | TweenLite.to(this.oldPage.rootElement, 0.4, {
239 | alpha: 0,
240 | onComplete: resolve
241 | })
242 | })
243 | }
244 |
245 | /**
246 | * Fade in the new content
247 | */
248 | fadeIn () {
249 | // Add display: none on the old container
250 | this.oldPage.rootElement.style.display = 'none'
251 |
252 | // Prepare new content css properties for the fade animation
253 | this.newPage.rootElement.style.visibility = 'visible'
254 | this.newPage.rootElement.style.opacity = '0'
255 |
256 | // Scroll to the top
257 | document.documentElement.scrollTop = 0
258 | document.body.scrollTop = 0
259 |
260 | // fadeIn the new content container
261 | TweenLite.to(this.newPage.rootElement, 0.4, {
262 | autoAlpha: 1,
263 | onComplete: () => {
264 | // IMPORTANT: Call this method at the end
265 | this.done()
266 | }
267 | })
268 | }
269 | }
270 | ```
271 |
272 | ## DOM structure
273 |
274 | This ES6 javascript framework has been designed to handle either complete HTML responses or
275 | *partial* HTML responses to lighten backend process and bandwidth.
276 | One of the most useful Page and Block property is `rootElement` that will always refer to the current page/block main-section.
277 |
278 | **In a page context,** `rootElement` is the DOM element which is extracted at the end of each completed AJAX requests.
279 | When it detects that HTTP response is *partial*, it initializes `rootElement` using the whole response content. Every new DOM content will be appended to the `#sb-wrapper` HTML section in order to enable cross-transitions between old and new content.
280 |
281 | **In a block context,** `rootElement` will store the DOM element with `[data-node-type]` attribute.
282 |
283 | When `Pjax` service is used and `window.fetch` supported, **all links inside document body** are listened to fetch pages asynchronously and make transitions.
284 |
285 | To declare a partial DOM section as the `rootElement` you must add some classes and
286 | data to your HTML tags.
287 |
288 | ```html
289 |
290 |
296 |
297 |
298 |
301 |
302 |
303 |
304 | ```
305 |
306 | - `id` *attribute* is obviously mandatory as it will be used to update your navigation and some other parts of your website. **Make sure that your ID is not the same as to your `data-node-name`.**
307 | - `page-content` *class* is essential in order to extract your DOM element during AJAX request. You can customize this class name in [`StartingBlock` options](#options) (`pageClass: "page-content"`).
308 | - `data-node-type` *attribute* will be used to map your element to the matching JS class (in this example: `HomePage.js`). **Every class must extend the `AbstractPage` or `AbstractBlock` class**. Then you have to declare your pages and blocks services in your *Starting Blocks* instance via `instanceFactory` method.
309 | - `data-node-name` is used to name your page object **and to rename body class and ID after it.**
310 | - `data-is-home`: 0 or 1
311 | - `data-meta-title` *attribute* will be used to change your new page *title* (`document.title`), it can be used in other cases such as some social network modules which require a clean page-title.
312 |
313 | You’ll find `index.html` and `page1.html` examples files. You can even test them
314 | by spawning a simple server with `npm run serve` command.
315 | Then go to your favorite browser and type `http://localhost:8080`.
316 |
317 | ## Page
318 |
319 | Each custom page needs to extend our `AbstractPage` class to be registered as a service in your *Starting Blocks* instance.
320 | Be careful that `data-node-type` attribute matches with your service declaration.
321 | By default *Starting Blocks* will instantiate the `DefaultPage` class if no `data-node-type` attribute is found.
322 |
323 | **Best practice** : Create your own `DefaultPage` with your common features then override the default
324 | service to use it as a common base for your custom pages :
325 |
326 | ```js
327 | import { AbstractPage } from 'starting-blocks'
328 |
329 | export default class CustomDefaultPage extends AbstractPage {
330 | //... common methods
331 | }
332 |
333 | // Custom page example
334 | export default class HomePage extends CustomDefaultPage {
335 | //... home page custom methods
336 | }
337 | ```
338 |
339 | ```js
340 | import CustomDefaultPage from './pages/CustomDefaultPage'
341 |
342 | // ...Instantiate starting blocks
343 |
344 | // Override the DefaultPage service with your own
345 | startingBlocks.instanceFactory('DefaultPage', c => {
346 | return new CustomDefaultPage(c)
347 | })
348 | // ...Boot
349 | ```
350 |
351 | #### Page overridable methods
352 |
353 | | Method | Description |
354 | | --- | --- |
355 | | `onResize` | On viewport resize, this method is debounced. |
356 |
357 | ## Block
358 |
359 | A block is a section of your page. It can be a `Slideshow`, an ajax form, a map...
360 | *Starting Blocks* automatically maps those DOM elements with a custom ES6 class in the same way the future [`CustomElementRegistry`](https://developer.mozilla.org/fr/docs/Web/API/CustomElementRegistry) will perform.
361 | Create your own class extending our `AbstractBlock` or `AbstractInViewBlock` then register them as a service.
362 | `data-node-type` attribute content and your service name must match.
363 |
364 | #### Common block overridable methods in AbstractBlock
365 |
366 | | Method | Description |
367 | | --- | --- |
368 | | `onResize` | On viewport resize, this method is *debounced* of 50ms. |
369 | | `onPageReady` | Triggered once all page blocks have been created. |
370 |
371 | #### In-view block overridable methods in AbstractInViewBlock
372 |
373 | | Method | Description |
374 | | --- | --- |
375 | | `onIntersectionCallback` | Triggered when in view block state changed (in or out). |
376 | | `onScreen` | Called when block is **in** the viewport. |
377 | | `offScreen` | Called when block is **out** of the viewport. |
378 |
379 | `AbstractInViewBlock` extends `AbstractBlock` and thus implements each of its methods too.
380 |
381 | ## Options
382 |
383 | You can pass some options when instantiating `StartingBlocks` object:
384 |
385 | | Parameter | Type | Default | Description |
386 | | --- | --- | --- | --- |
387 | | wrapperId | string | 'sb-wrapper' |
388 | | pageBlockClass | string | 'page-block' |
389 | | pageClass | string | 'page-content' |
390 | | objectTypeAttr | string | 'data-node-type' |
391 | | noAjaxLinkClass | string | 'no-ajax-link' |
392 | | noPrefetchClass | string | 'no-prefetch' |
393 | | manualDomAppend | boolean | false | To manually manage page build directly in transition instances
394 |
395 | ## Events
396 |
397 | | Const name | Event name | Description |
398 | | --- | --- | --- |
399 | | `BEFORE_PAGE_LOAD` | `SB_BEFORE_PAGE_LOAD` | Before Router initialize XHR request to load new page. |
400 | | `AFTER_PAGE_LOAD` | `SB_AFTER_PAGE_LOAD` | After `window.fetch` XHR request succeeded. |
401 | | `AFTER_DOM_APPENDED` | `SB_AFTER_DOM_APPENDED` | After Router appended new page DOM to `wrapperId`. |
402 | | `AFTER_PAGE_BOOT` | `SB_AFTER_PAGE_BOOT` | After Router create new page instance. |
403 | | `CONTAINER_READY` | `SB_CONTAINER_READY` | |
404 | | `TRANSITION_START` | `SB_TRANSITION_START` | |
405 | | `TRANSITION_COMPLETE` | `SB_TRANSITION_COMPLETE` | |
406 | | `BEFORE_SPLASHSCREEN_HIDE` | `SB_BEFORE_SPLASHSCREEN_HIDE` | |
407 | | `AFTER_SPLASHSCREEN_HIDE` | `SB_AFTER_SPLASHSCREEN_HIDE` | |
408 |
409 | ## Docs
410 |
411 | To generate documentation, you’ll at least NodeJS v4.4 and to install ESDoc.
412 |
413 | ```bash
414 | npm run doc;
415 | ```
416 |
417 | Documentation will be available in `doc/` folder.
418 |
419 | ## Naming conventions
420 |
421 | We dropped *jQuery* in Starting-blocks v5 and we changed several variables names.
422 | We suffixed every `DOMElement` variable with `Element`, `Container`, `Elements` or `Containers`.
423 |
424 | Examples:
425 |
426 | ```js
427 | let mainContainer = document.getElementById('main-container')
428 | let imageContainer = document.getElementById('image-container')
429 | let imageElements = imageContainer.querySelectorAll('.image')
430 | ```
431 |
432 | ## Improving Starting Blocks
433 |
434 | To work locally on *Starting Blocks*, you’ll find some HTML files in `examples/` folder.
435 |
436 | - Install dependencies: `yarn`.
437 | - Type `npm run dev` to improve *Starting Blocks* locally.
438 | - Type `npm run build` to optimize project in one file as: `main.js`.
439 | - Type `npm run demo` to build demo project in `examples/` folder.
440 |
441 | ## Compatibility
442 |
443 | *Starting Blocks* use native `Promise`, `fetch`, `IntersectionObserver` and `MutationObserver` browser features.
444 | **Don't forget to use some polyfills for old browsers.**
445 |
446 | ## Demo
447 |
448 | To launch the example you need to change the `examples/srv/js/config/config.example.js` file with your own informations.
449 |
450 | ## Go further with Starting Blocks
451 |
452 | If you use *Webpack* you will be able to dynamically lazy-load your blocks with a custom `BlockBuilder`.
453 | Create a custom *BlockBuilder* and override the default one:
454 |
455 | ```js
456 | import { AbstractBlockBuilder } from 'starting-blocks'
457 |
458 | export default class WebpackAsyncBlockBuilder extends AbstractBlockBuilder {
459 | // Dynamic import
460 | async getBlockInstance (nodeTypeName) {
461 | try {
462 | const Block = await this.getModule(nodeTypeName)
463 |
464 | if (!this.hasService(nodeTypeName)) {
465 | this.container.$register({
466 | $name: nodeTypeName,
467 | $type: 'instanceFactory',
468 | $value: c => {
469 | return new Block(c)
470 | }
471 | })
472 | }
473 |
474 | return this.getService(nodeTypeName).instance()
475 | } catch (e) {
476 | console.error(e.message)
477 | return null
478 | }
479 | }
480 |
481 | async getModule (nodeTypeName) {
482 | return import(`../blocks/${nodeTypeName}` /* webpackChunkName: "block-" */)
483 | .then(block => {
484 | return block.default
485 | })
486 | }
487 | }
488 | ```
489 |
490 | Then override the default `PageBuilder` service:
491 |
492 | ```js
493 | // Custom block builder (dynamic import)
494 | startingBlocks.provider('BlockBuilder', WebpackAsyncBlockBuilder)
495 | ```
496 |
497 | ## Contributors
498 |
499 | - [Adrien Scholaert](https://github.com/Gouterman)
500 | - [Ambroise Maupate](https://github.com/ambroisemaupate)
501 | - [Quentin Neyraud](https://github.com/quentinneyraud)
502 | - [Maxime Bérard](https://github.com/maximeberard)
503 |
--------------------------------------------------------------------------------
/esdoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "source": "./src",
3 | "excludes": ["bundle.js"],
4 | "destination": "./doc",
5 | "plugins": [
6 | {"name": "esdoc-exclude-source-plugin"},
7 | {"name": "esdoc-standard-plugin"},
8 | {
9 | "name": "esdoc-ecmascript-proposal-plugin",
10 | "option": {
11 | "classProperties": true,
12 | "objectRestSpread": true,
13 | "doExpressions": true,
14 | "functionBind": true,
15 | "functionSent": true,
16 | "asyncGenerators": true,
17 | "decorators": true,
18 | "exportExtensions": true,
19 | "dynamicImport": true
20 | }
21 | },
22 | {
23 | "name": "esdoc-brand-plugin",
24 | "option": {
25 | "title": "Starting Blocks JS framework",
26 | "description": "A page transition and blocks ES6 framework by REZO ZERO",
27 | "repository": "https://github.com/rezozero/starting-blocks",
28 | "site": "https://startingblocks.rezo-zero.com/",
29 | "author": "https://www.rezo-zero.com/"
30 | }
31 | }
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/examples/src/css/demo.css:
--------------------------------------------------------------------------------
1 | html {
2 | position: relative;
3 | min-height: 100%;
4 | }
5 |
6 | html,
7 | body {
8 | overflow-x: hidden; /* Prevent scroll on narrow devices */
9 | }
10 |
11 | body {
12 | padding-top: 75px;
13 | margin-bottom: 60px;
14 | }
15 |
16 | .box-shadow {
17 | box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, .05);
18 | }
19 |
20 | .text-white-50 {
21 | color: rgba(255, 255, 255, .5);
22 | }
23 |
24 | /** Users Blocks **/
25 | .usersblock .avatar-cont {
26 | margin-right: 10px;
27 | }
28 |
29 | .usersblock .avatar-cont,
30 | .usersblock .avatar-cont img {
31 | width: 48px;
32 | height: 48px;
33 | }
34 |
35 | .usersblock__intro {
36 | color: #000;
37 | }
38 |
39 | #splashscreen {
40 | position: fixed;
41 | z-index: 1040;
42 | top: 0;
43 | right: 0;
44 | bottom: 0;
45 | left: 0;
46 | display: flex;
47 | align-content: center;
48 | align-items: center;
49 | justify-content: center;
50 | }
51 |
52 | .splashscreen-inner {
53 | position: relative;
54 | z-index: 11;
55 | display: block;
56 | }
57 |
58 | .splashscreen-bg {
59 | position: absolute;
60 | z-index: 10;
61 | top: 0;
62 | right: 0;
63 | bottom: 0;
64 | left: 0;
65 | background-color: #eeeeee;
66 | }
67 |
68 | .splashscreen-text {
69 | position: relative;
70 | z-index: 11;
71 | font-size: 16px;
72 | margin-top: 150px;
73 | letter-spacing: 5px;
74 | }
75 |
76 | .spinner {
77 | position: absolute;
78 | z-index: 12;
79 | top: 50%;
80 | left: 50%;
81 | width: 66px;
82 | height: 66px;
83 | margin-top: -33px;
84 | margin-left: -33px;
85 | -webkit-animation: contanim 2s linear infinite;
86 | animation: contanim 2s linear infinite;
87 | }
88 |
89 | .spinner svg {
90 | display: block;
91 | margin: 0; padding: 0;
92 | width: 100%;
93 | height: 100%;
94 | left: 0;
95 | top: 0;
96 | position: absolute;
97 | -webkit-transform: rotate(-90deg);
98 | transform: rotate(-90deg);
99 | }
100 |
101 | .spinner svg:nth-child(1) circle {
102 | stroke: #84EBBD;
103 | stroke-dasharray: 1, 300;
104 | stroke-dashoffset: 0;
105 | -webkit-animation: strokeanim 3s calc(.2s * (1)) ease infinite;
106 | animation: strokeanim 3s calc(.2s * (1)) ease infinite;
107 | -webkit-transform-origin: center center;
108 | transform-origin: center center;
109 | }
110 |
111 | .spinner svg:nth-child(2) circle {
112 | stroke: #4977EC;
113 | stroke-dasharray: 1, 300;
114 | stroke-dashoffset: 0;
115 | -webkit-animation: strokeanim 3s calc(.2s * (2)) ease infinite;
116 | animation: strokeanim 3s calc(.2s * (2)) ease infinite;
117 | -webkit-transform-origin: center center;
118 | transform-origin: center center;
119 | }
120 |
121 | .spinner svg:nth-child(3) circle {
122 | stroke: #F6BB67;
123 | stroke-dasharray: 1, 300;
124 | stroke-dashoffset: 0;
125 | -webkit-animation: strokeanim 3s calc(.2s * (3)) ease infinite;
126 | animation: strokeanim 3s calc(.2s * (3)) ease infinite;
127 | -webkit-transform-origin: center center;
128 | transform-origin: center center;
129 | }
130 |
131 | .spinner svg:nth-child(4) circle {
132 | stroke: #333841;
133 | stroke-dasharray: 1, 300;
134 | stroke-dashoffset: 0;
135 | -webkit-animation: strokeanim 3s calc(.2s * (4)) ease infinite;
136 | animation: strokeanim 3s calc(.2s * (4)) ease infinite;
137 | -webkit-transform-origin: center center;
138 | transform-origin: center center;
139 | }
140 |
141 | @-webkit-keyframes strokeanim {
142 | 0% {
143 | stroke-dasharray: 1, 300;
144 | stroke-dashoffset: 0;
145 | }
146 | 50% {
147 | stroke-dasharray: 120, 300;
148 | stroke-dashoffset: -58.548324585;
149 | }
150 | 100% {
151 | stroke-dasharray: 120, 300;
152 | stroke-dashoffset: -175.6449737549;
153 | }
154 | }
155 |
156 | @keyframes strokeanim {
157 | 0% {
158 | stroke-dasharray: 1, 300;
159 | stroke-dashoffset: 0;
160 | }
161 | 50% {
162 | stroke-dasharray: 120, 300;
163 | stroke-dashoffset: -58.548324585;
164 | }
165 | 100% {
166 | stroke-dasharray: 120, 300;
167 | stroke-dashoffset: -175.6449737549;
168 | }
169 | }
170 |
171 | @-webkit-keyframes contanim {
172 | 100% {
173 | -webkit-transform: rotate(360deg);
174 | transform: rotate(360deg);
175 | }
176 | }
177 |
178 | @keyframes contanim {
179 | 100% {
180 | -webkit-transform: rotate(360deg);
181 | transform: rotate(360deg);
182 | }
183 | }
184 |
185 | /** Big transition **/
186 | #big-transition {
187 | position: fixed;
188 | z-index: 2000;
189 | top: 0;
190 | left: 0;
191 | right: 0;
192 | bottom: 0;
193 | display: flex;
194 | opacity: 0;
195 | visibility: hidden;
196 | }
197 |
198 | #big-transition #big-transition-vertical,
199 | #big-transition #big-transition-horizontal {
200 | position: absolute;
201 | top: 0;
202 | left: 0;
203 | right: 0;
204 | bottom: 0;
205 | display: flex;
206 | }
207 |
208 | #big-transition #big-transition-vertical > div {
209 | height: 100%;
210 | flex: 1;
211 | background: #eee;
212 | transform-origin: 0 0;
213 | transform: scaleY(0);
214 | }
215 |
216 | #big-transition #big-transition-horizontal {
217 | flex-flow: column;
218 | }
219 |
220 | #big-transition #big-transition-horizontal > div {
221 | width: 100%;
222 | flex: 1 1;
223 | background: #8f8f8f;
224 | transform-origin: 0 0;
225 | transform: scaleX(0);
226 | }
227 |
228 | /** Footer **/
229 | .footer {
230 | position: absolute;
231 | bottom: 0;
232 | width: 100%;
233 | height: 60px;
234 | line-height: 60px;
235 | background-color: #f5f5f5;
236 | }
237 |
--------------------------------------------------------------------------------
/examples/src/img/test1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rezozero/starting-blocks/9ebd53c8527e7ee6bade1b7beef48044d430c24a/examples/src/img/test1.jpg
--------------------------------------------------------------------------------
/examples/src/img/test2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rezozero/starting-blocks/9ebd53c8527e7ee6bade1b7beef48044d430c24a/examples/src/img/test2.jpg
--------------------------------------------------------------------------------
/examples/src/img/test2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rezozero/starting-blocks/9ebd53c8527e7ee6bade1b7beef48044d430c24a/examples/src/img/test2.webp
--------------------------------------------------------------------------------
/examples/src/img/test3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rezozero/starting-blocks/9ebd53c8527e7ee6bade1b7beef48044d430c24a/examples/src/img/test3.jpg
--------------------------------------------------------------------------------
/examples/src/img/test4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rezozero/starting-blocks/9ebd53c8527e7ee6bade1b7beef48044d430c24a/examples/src/img/test4.jpg
--------------------------------------------------------------------------------
/examples/src/img/test5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rezozero/starting-blocks/9ebd53c8527e7ee6bade1b7beef48044d430c24a/examples/src/img/test5.jpg
--------------------------------------------------------------------------------
/examples/src/js/ExampleNav.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2018.
3 | * @file ExampleNav.js
4 | * @author Adrien Scholaert
5 | */
6 |
7 | import {
8 | EventTypes
9 | } from 'starting-blocks'
10 |
11 | /**
12 | * An example nav which binds links for AJAX use.
13 | */
14 | export default class ExampleNav {
15 | constructor () {
16 | /**
17 | * @type {HTMLElement | null}
18 | */
19 | this.container = document.getElementById('main-nav')
20 |
21 | /**
22 | * @type {Array}
23 | */
24 | this.linkElements = [...this.container.querySelectorAll('a')]
25 |
26 | // Binded methods
27 | this.onAfterPageBoot = this.onAfterPageBoot.bind(this)
28 | }
29 |
30 | init () {
31 | this.initEvents()
32 | }
33 |
34 | initEvents () {
35 | window.addEventListener(EventTypes.AFTER_PAGE_BOOT, this.onAfterPageBoot)
36 | }
37 |
38 | onAfterPageBoot () {
39 | // Remove all active class
40 | for (const linkElement of this.linkElements) {
41 | linkElement.classList.remove('active')
42 |
43 | if (linkElement.href === window.location.href) {
44 | linkElement.classList.add('active')
45 | }
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/examples/src/js/api/Api.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Ambroise Maupate and Julien Blanchet
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is furnished
9 | * to do so, subject to the following conditions:
10 | * The above copyright notice and this permission notice shall be included in all
11 | * copies or substantial portions of the Software.
12 | *
13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19 | * IN THE SOFTWARE.
20 | *
21 | * Except as contained in this notice, the name of the ROADIZ shall not
22 | * be used in advertising or otherwise to promote the sale, use or other dealings
23 | * in this Software without prior written authorization from Ambroise Maupate and Julien Blanchet.
24 | *
25 | * @file Api.js
26 | * @author Adrien Scholaert
27 | */
28 |
29 | import config from '../config/config'
30 |
31 | export async function getData (sourceUrl, params = {}) {
32 | const url = new URL(sourceUrl)
33 | const mergedParams = { ...params, access_token: config.token }
34 |
35 | Object.keys(mergedParams).forEach(key => url.searchParams.append(key, mergedParams[key]))
36 |
37 | const result = await window.fetch(url.toString())
38 | const json = await result.json()
39 |
40 | return json
41 | }
42 |
--------------------------------------------------------------------------------
/examples/src/js/app.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017, Rezo Zero
3 | *
4 | * @file app.js
5 | * @author Adrien Scholaert
6 | * @author Ambroise Maupate
7 | */
8 |
9 | import StartingBlocks, {
10 | Pjax,
11 | History,
12 | Prefetch,
13 | CacheProvider,
14 | polyfills
15 | } from 'starting-blocks'
16 | import WebpackAsyncBlockBuilder from './services/WebpackAsyncBlockBuilder'
17 | import Splashscreen from './services/Splashscreen'
18 | import TransitionFactory from './factories/TransitionFactory'
19 | import HomePage from './pages/HomePage'
20 | import ExampleNav from './ExampleNav'
21 | import 'gsap/CSSPlugin'
22 | import 'lazysizes'
23 |
24 | (() => {
25 | // BEING IMPORTANT (Bug Safari 10.1)
26 | // DO NOT REMOVE
27 | if (window.MAIN_EXECUTED) {
28 | throw new Error('Safari 10')
29 | }
30 |
31 | window.MAIN_EXECUTED = true
32 | // END IMPORTANT
33 |
34 | /**
35 | * Declare polyfills
36 | */
37 | polyfills()
38 |
39 | /**
40 | * Build nav
41 | * @type {ExampleNav}
42 | */
43 | const nav = new ExampleNav()
44 |
45 | // console.log(StartingBlocks)
46 |
47 | /**
48 | * Build a new starting blocks
49 | */
50 | const startingBlocks = new StartingBlocks({
51 | manualDomAppend: true,
52 | debug: 1
53 | })
54 |
55 | // Add services
56 | startingBlocks.provider('TransitionFactory', TransitionFactory)
57 | startingBlocks.provider('History', History)
58 | startingBlocks.provider('CacheProvider', CacheProvider)
59 |
60 | // Custom block builder (dynamic import)
61 | startingBlocks.provider('BlockBuilder', WebpackAsyncBlockBuilder)
62 |
63 | // Add bootable services
64 | startingBlocks.bootableProvider('Prefetch', Prefetch)
65 | startingBlocks.bootableProvider('Pjax', Pjax)
66 | startingBlocks.bootableProvider('Splashscreen', Splashscreen)
67 |
68 | // Register pages
69 | startingBlocks.instanceFactory('HomePage', c => {
70 | return new HomePage(c)
71 | })
72 |
73 | // If you want to use standard block import
74 | // startingBlocks.instanceFactory('UsersBlock', c => {
75 | // return new UsersBlock(c)
76 | // })
77 |
78 | nav.init()
79 | startingBlocks.boot()
80 | })()
81 |
--------------------------------------------------------------------------------
/examples/src/js/blocks/DefaultBlock.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Ambroise Maupate and Julien Blanchet
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is furnished
9 | * to do so, subject to the following conditions:
10 | * The above copyright notice and this permission notice shall be included in all
11 | * copies or substantial portions of the Software.
12 | *
13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19 | * IN THE SOFTWARE.
20 | *
21 | * Except as contained in this notice, the name of the ROADIZ shall not
22 | * be used in advertising or otherwise to promote the sale, use or other dealings
23 | * in this Software without prior written authorization from Ambroise Maupate and Julien Blanchet.
24 | *
25 | * @file DefaultBlock.js
26 | * @author Adrien Scholaert
27 | */
28 |
29 | import { AbstractBlock } from 'starting-blocks'
30 |
31 | export default class DefaultBlock extends AbstractBlock {
32 | init () {
33 | return super.init()
34 | }
35 |
36 | initEvents () {
37 | return super.initEvents()
38 | }
39 |
40 | destroy () {
41 | return super.destroy()
42 | }
43 |
44 | destroyEvents () {
45 | return super.destroyEvents()
46 | }
47 |
48 | onResize () {
49 | return super.onResize()
50 | }
51 |
52 | onPageReady () {
53 | return super.onPageReady()
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/examples/src/js/blocks/InViewBlock.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017, Rezo Zero
3 | *
4 | * @file InViewBlock.js
5 | * @author Adrien Scholaert
6 | */
7 |
8 | import { AbstractInViewBlock } from 'starting-blocks'
9 | import { TweenMax, Power4 } from 'gsap'
10 |
11 | export default class InViewBlock extends AbstractInViewBlock {
12 | constructor (container) {
13 | super(container, 'InViewBlock')
14 |
15 | // Prepare values
16 | this.imgs = []
17 | this.direction = 'right'
18 | this.xPercent = 30
19 |
20 | this.observerOptions = {
21 | ...this.observerOptions,
22 | threshold: 0.1
23 | }
24 | }
25 |
26 | init () {
27 | super.init()
28 |
29 | if (this.rootElement.hasAttribute('data-direction')) {
30 | this.direction = this.rootElement.getAttribute('data-direction')
31 | }
32 |
33 | if (this.direction === 'left') {
34 | this.xPercent = -this.xPercent
35 | }
36 |
37 | // Find elements
38 | this.imgs = [...this.rootElement.querySelectorAll('img')]
39 | }
40 |
41 | initEvents () {
42 | super.initEvents()
43 |
44 | for (const img of this.imgs) {
45 | img.addEventListener('load', () => {
46 | console.log('img loaded')
47 | })
48 | }
49 | }
50 |
51 | onScreen (entry) {
52 | TweenMax.to(this.rootElement, 1.5, {
53 | xPercent: 0,
54 | alpha: 1,
55 | delay: 0.15,
56 | ease: Power4.easeOut
57 | })
58 | }
59 |
60 | offScreen () {
61 | TweenMax.set(this.rootElement, {
62 | xPercent: this.xPercent,
63 | alpha: 0
64 | })
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/examples/src/js/blocks/UsersBlock.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Ambroise Maupate and Julien Blanchet
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is furnished
9 | * to do so, subject to the following conditions:
10 | * The above copyright notice and this permission notice shall be included in all
11 | * copies or substantial portions of the Software.
12 | *
13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19 | * IN THE SOFTWARE.
20 | *
21 | * Except as contained in this notice, the name of the ROADIZ shall not
22 | * be used in advertising or otherwise to promote the sale, use or other dealings
23 | * in this Software without prior written authorization from Ambroise Maupate and Julien Blanchet.
24 | *
25 | * @file UsersBlock.js
26 | * @author Adrien Scholaert
27 | */
28 |
29 | import { AbstractBlock } from 'starting-blocks'
30 | import * as Api from '../api/Api'
31 | import * as Utils from '../utils/utils'
32 |
33 | export default class UsersBlock extends AbstractBlock {
34 | constructor (container) {
35 | super(container, 'UsersBlock')
36 |
37 | // Elements
38 | this.avatarContainer = null
39 | this.contributorsListingContainer = null
40 |
41 | // Values
42 | this.data = null
43 | this.owner = null
44 | this.contributors = []
45 | this.initialUrl = 'https://api.github.com/repos/rezozero/starting-blocks'
46 | }
47 |
48 | async init () {
49 | super.init()
50 |
51 | // Elements
52 | this.avatarContainer = this.page.rootElement.querySelectorAll('.avatar-cont')[0]
53 | this.contributorsListingContainer = this.page.rootElement.querySelectorAll('.usersblock__contributors-list')[0]
54 |
55 | // Init request
56 | this.data = await Api.getData(this.initialUrl)
57 |
58 | this.fillData(this.data)
59 | }
60 |
61 | initEvents () {
62 | return super.initEvents()
63 | }
64 |
65 | destroy () {
66 | return super.destroy()
67 | }
68 |
69 | destroyEvents () {
70 | return super.destroyEvents()
71 | }
72 |
73 | async fillData () {
74 | if (!this.data) return
75 |
76 | this.contributors = await Api.getData(this.data.contributors_url)
77 | this.owner = this.data.owner
78 |
79 | this.setAvatar()
80 | this.setContributors()
81 | }
82 |
83 | setContributors () {
84 | for (let contributor of this.contributors) {
85 | const tpl = `
86 |
87 |
88 |
92 |
93 |
@${contributor.login}
94 |
95 | Contributions: ${contributor.contributions}
96 | See more
97 |
98 |
99 |
100 |
`
101 |
102 | this.contributorsListingContainer.insertAdjacentHTML('afterbegin', tpl)
103 | }
104 | }
105 |
106 | async setAvatar () {
107 | if (!this.owner.avatar_url) return
108 | const img = await Utils.loadImage(this.owner.avatar_url)
109 | img.classList.add('img-thumbnail')
110 | this.avatarContainer.appendChild(img)
111 | }
112 |
113 | onResize () {
114 | return super.onResize()
115 | }
116 |
117 | onPageReady () {
118 | return super.onPageReady()
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/examples/src/js/config/config.example.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Ambroise Maupate and Julien Blanchet
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is furnished
9 | * to do so, subject to the following conditions:
10 | * The above copyright notice and this permission notice shall be included in all
11 | * copies or substantial portions of the Software.
12 | *
13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19 | * IN THE SOFTWARE.
20 | *
21 | * Except as contained in this notice, the name of the ROADIZ shall not
22 | * be used in advertising or otherwise to promote the sale, use or other dealings
23 | * in this Software without prior written authorization from Ambroise Maupate and Julien Blanchet.
24 | *
25 | * @file config.example.js
26 | * @author Adrien Scholaert
27 | */
28 |
29 | export default {
30 | token: 'your_token_to_me'
31 | }
32 |
--------------------------------------------------------------------------------
/examples/src/js/factories/TransitionFactory.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2016, Ambroise Maupate
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is furnished
9 | * to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 | * IN THE SOFTWARE.
21 | *
22 | * @file TransitionFactory.js
23 | * @author Quentin Neyraud
24 | * @author Adrien Scholaert
25 | */
26 |
27 | import { AbstractTransitionFactory } from 'starting-blocks'
28 | import FadeTransition from '../transitions/FadeTransition'
29 | import SlideTransition from '../transitions/SlideTransition'
30 | import BigTransition from '../transitions/BigTransition'
31 |
32 | /**
33 | * Transition mapper class.
34 | *
35 | * This class maps your `data-transition` with your *ES6* classes.
36 | *
37 | * **You must define your own ClassFactory for each of your projects.**.
38 | */
39 | export default class TransitionFactory extends AbstractTransitionFactory {
40 | /**
41 | * Get Transition
42 | *
43 | * @param {Object} previousState
44 | * @param {Object} state
45 | * @returns {AbstractTransition}
46 | */
47 | getTransition (previousState, state) {
48 | /**
49 | * You can customise transition logic with the previousState and the new state
50 | *
51 | * Ex: when back or prev button its pressed we use FadeTransition
52 | */
53 |
54 | let transition
55 |
56 | if (state && state.context === 'history') {
57 | transition = new FadeTransition()
58 | transition.serviceName = 'FadeTransition'
59 | } else {
60 | switch (state.transitionName) {
61 | case 'slide':
62 | transition = new SlideTransition()
63 | transition.serviceName = 'SlideTransition'
64 | break
65 | case 'fade':
66 | transition = new FadeTransition()
67 | transition.serviceName = 'FadeTransition'
68 | break
69 | default:
70 | transition = new BigTransition()
71 | transition.serviceName = 'BigTransition'
72 |
73 | break
74 | }
75 | }
76 |
77 | transition.container = this.container
78 |
79 | return transition
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/examples/src/js/pages/DefaultPage.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2016, Ambroise Maupate
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is furnished
9 | * to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 | * IN THE SOFTWARE.
21 | *
22 | * @file Page.js
23 | * @author Ambroise Maupate
24 | */
25 |
26 | import { AbstractPage } from 'starting-blocks'
27 |
28 | /**
29 | * Some example "page".
30 | *
31 | * @extends {AbstractPage}
32 | * @class
33 | */
34 | export default class DefaultPage extends AbstractPage {
35 | constructor (container) {
36 | super(container, 'DefaultPage')
37 |
38 | // Values
39 |
40 | /**
41 | * @type {HTMLElement}
42 | */
43 | this.duplicateButtonElement = null
44 |
45 | // Bind methods
46 | this.onButtonClick = this.onButtonClick.bind(this)
47 | }
48 |
49 | async init () {
50 | await super.init()
51 |
52 | this.duplicateButtonElement = this.rootElement.querySelectorAll('a.duplicate-last')[0]
53 | }
54 |
55 | initEvents () {
56 | super.initEvents()
57 |
58 | if (this.duplicateButtonElement) {
59 | this.duplicateButtonElement.addEventListener('click', this.onButtonClick)
60 | }
61 | }
62 |
63 | destroyEvents () {
64 | super.destroyEvents()
65 |
66 | if (this.duplicateButtonElement) {
67 | this.duplicateButtonElement.removeEventListener('click', this.onButtonClick)
68 | }
69 | }
70 |
71 | onButtonClick (e) {
72 | e.preventDefault()
73 | let newBlockElement = this.blockElements[this.blockElements.length - 1].cloneNode(true)
74 | newBlockElement.setAttribute('id', `block-${this.blockElements.length + 1}`)
75 | this.rootElement.appendChild(newBlockElement)
76 | return false
77 | }
78 |
79 | onLazyImageProcessed (index) {
80 | super.onLazyImageProcessed(index)
81 | }
82 |
83 | onResize () {
84 | super.onResize()
85 | }
86 |
87 | onLazyImageSet (element) {
88 | super.onLazyImageSet(element)
89 | }
90 |
91 | onLazyImageLoad (element) {
92 | super.onLazyImageLoad(element)
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/examples/src/js/pages/HomePage.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2016, Ambroise Maupate
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is furnished
9 | * to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 | * IN THE SOFTWARE.
21 | *
22 | * @file Home.js
23 | * @author Ambroise Maupate
24 | */
25 |
26 | import { AbstractPage, EventTypes } from 'starting-blocks'
27 | import { TweenMax, Power3 } from 'gsap'
28 |
29 | /**
30 | * Some example "home" page.
31 | *
32 | * @extends {AbstractPage}
33 | * @class
34 | */
35 | export default class HomePage extends AbstractPage {
36 | constructor (container) {
37 | super(container, 'HomePage')
38 |
39 | // Elements
40 | this.elements = []
41 |
42 | // Bind methods
43 | this.prepareShow = this.prepareShow.bind(this)
44 | this.show = this.show.bind(this)
45 | }
46 |
47 | async init () {
48 | await super.init()
49 |
50 | this.elements = [...this.rootElement.querySelectorAll('.bg-white')]
51 | }
52 |
53 | initEvents () {
54 | super.initEvents()
55 |
56 | window.addEventListener(EventTypes.BEFORE_SPLASHSCREEN_HIDE, this.prepareShow)
57 | window.addEventListener(EventTypes.START_SPLASHSCREEN_HIDE, this.show)
58 | }
59 |
60 | destroyEvents () {
61 | super.destroyEvents()
62 |
63 | window.removeEventListener(EventTypes.BEFORE_SPLASHSCREEN_HIDE, this.prepareShow)
64 | window.removeEventListener(EventTypes.START_SPLASHSCREEN_HIDE, this.show)
65 | }
66 |
67 | prepareShow () {
68 | TweenMax.set(this.elements, {
69 | alpha: 0,
70 | y: 200
71 | })
72 | }
73 |
74 | show () {
75 | TweenMax.staggerTo(this.elements, 1.2, {
76 | alpha: 1,
77 | delay: 0.2,
78 | ease: Power3.easeOut,
79 | y: 0
80 | }, 0.2)
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/examples/src/js/services/Splashscreen.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017, Rezo Zero
3 | *
4 | * @file Splashscreen.js
5 | * @author Adrien Scholaert
6 | */
7 |
8 | import { AbstractSplashscreen, Dispatcher, EventTypes } from 'starting-blocks'
9 | import { TweenMax, Power3 } from 'gsap'
10 |
11 | export default class Splashscreen extends AbstractSplashscreen {
12 | constructor (container) {
13 | super(container, 'Splashscreen')
14 |
15 | // Elements
16 | this.rootElement = document.getElementById('splashscreen')
17 | this.innerEl = this.rootElement.querySelector('.splashscreen-inner')
18 | this.bgEl = this.rootElement.querySelector('.splashscreen-bg')
19 |
20 | // Values
21 | this.minimalDuration = 2000 // ms
22 | this.minimalDurationPromise = new Promise(resolve => {
23 | window.setTimeout(() => {
24 | resolve()
25 | }, this.minimalDuration)
26 | })
27 | }
28 |
29 | hide () {
30 | return Promise
31 | .all([this.minimalDurationPromise])
32 | .then(() => this.launchHideAnimation())
33 | }
34 |
35 | launchHideAnimation () {
36 | return new Promise(resolve => {
37 | Dispatcher.commit(EventTypes.START_SPLASHSCREEN_HIDE)
38 |
39 | TweenMax.to(this.innerEl, 0.5, {
40 | alpha: 0
41 | })
42 |
43 | TweenMax.to(this.rootElement, 1.2, {
44 | yPercent: 100,
45 | ease: Power3.easeInOut,
46 | onComplete: () => {
47 | TweenMax.set(this.rootElement, {
48 | display: 'none'
49 | })
50 |
51 | resolve()
52 | }
53 | })
54 | })
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/examples/src/js/services/WebpackAsyncBlockBuilder.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017, Rezo Zero
3 | *
4 | * @file WebpackAsyncBlockBuilder.js
5 | * @author Adrien Scholaert
6 | */
7 |
8 | import { AbstractBlockBuilder } from 'starting-blocks'
9 |
10 | export default class WebpackAsyncBlockBuilder extends AbstractBlockBuilder {
11 | // Dynamic import
12 | async getBlockInstance (nodeTypeName) {
13 | try {
14 | const Block = await this.getModule(nodeTypeName)
15 |
16 | if (!this.hasService(nodeTypeName)) {
17 | this.container.$register({
18 | $name: nodeTypeName,
19 | $type: 'instanceFactory',
20 | $value: c => {
21 | return new Block(c)
22 | }
23 | })
24 | }
25 |
26 | return this.getService(nodeTypeName).instance()
27 | } catch (e) {
28 | console.error(e.message)
29 | return null
30 | }
31 | }
32 |
33 | async getModule (nodeTypeName) {
34 | return import(`../blocks/${nodeTypeName}` /* webpackChunkName: "block-" */)
35 | .then(block => {
36 | return block.default
37 | })
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/examples/src/js/transitions/BigTransition.js:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | * Copyright (c) 2017. Ambroise Maupate and Julien Blanchet
4 | *
5 | * Permission is hereby granted, free of charge, to any person obtaining a copy
6 | * of this software and associated documentation files (the "Software"), to deal
7 | * in the Software without restriction, including without limitation the rights
8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | * copies of the Software, and to permit persons to whom the Software is furnished
10 | * to do so, subject to the following conditions:
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 | * IN THE SOFTWARE.
21 | *
22 | * Except as contained in this notice, the name of the ROADIZ shall not
23 | * be used in advertising or otherwise to promote the sale, use or other dealings
24 | * in this Software without prior written authorization from Ambroise Maupate and Julien Blanchet.
25 | *
26 | * @file BigTransition.js
27 | * @author Adrien Scholaert
28 | */
29 |
30 | import { AbstractTransition } from 'starting-blocks'
31 | import TweenMax from 'gsap/TweenMax'
32 |
33 | /**
34 | * Fade Transition class example. Fade Out / Fade In content.
35 | *
36 | * @extends {AbstractTransition}
37 | */
38 | export default class FadeTransition extends AbstractTransition {
39 | constructor () {
40 | super()
41 | this.mainElement = document.getElementById('big-transition')
42 | this.verticalElements = [...document.getElementById('big-transition-vertical').querySelectorAll('div')]
43 | this.horizontalElements = [...document.getElementById('big-transition-horizontal').querySelectorAll('div')]
44 | }
45 |
46 | /**
47 | * Entry point of the animation
48 | * Automatically called on init()
49 | */
50 | start () {
51 | // Wait new content and the end of fadeOut animation
52 | // this.newPageLoading is a Promise which is resolved when the new content is loaded
53 | Promise.all([this.newPageLoading, this.startAnim()])
54 | // then fadeIn the new content
55 | .then(this.endAnim.bind(this))
56 | }
57 |
58 | /**
59 | * Fade out the old content.
60 | * @returns {Promise}
61 | */
62 | startAnim () {
63 | return new Promise((resolve) => {
64 | TweenMax.to(this.oldPage.rootElement, 0.75, {
65 | alpha: 0
66 | })
67 |
68 | TweenMax.set(this.mainElement, {
69 | autoAlpha: 1,
70 | onComplete: () => {
71 | TweenMax.staggerTo(this.horizontalElements, 0.5, {
72 | scaleX: 1
73 | }, 0.1)
74 |
75 | TweenMax.staggerTo(this.verticalElements.reverse(), 0.5, {
76 | scaleY: 1
77 | }, 0.1, resolve)
78 | }
79 | })
80 | })
81 | }
82 |
83 | /**
84 | * Fade in the new content
85 | */
86 | async endAnim () {
87 | // Destroy old page
88 | this.destroyOldPage()
89 |
90 | // Manually append and build the new page
91 | await this.buildNewPage()
92 |
93 | // Scroll top
94 | this.scrollTop()
95 |
96 | // Fade animation
97 | TweenMax.fromTo(this.newPage.rootElement, 0.75, {
98 | autoAlpha: 0
99 | }, {
100 | autoAlpha: 1
101 | })
102 |
103 | TweenMax.staggerTo(this.horizontalElements, 0.5, {
104 | scaleX: 0
105 | }, 0.1)
106 |
107 | TweenMax.staggerTo(this.verticalElements, 0.5, {
108 | scaleY: 0
109 | }, 0.1, () => {
110 | TweenMax.set(this.mainElement, {
111 | autoAlpha: 0
112 | })
113 |
114 | // IMPORTANT: Call this method at the end
115 | this.done()
116 | })
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/examples/src/js/transitions/DefaultTransition.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Ambroise Maupate and Julien Blanchet
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is furnished
9 | * to do so, subject to the following conditions:
10 | * The above copyright notice and this permission notice shall be included in all
11 | * copies or substantial portions of the Software.
12 | *
13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19 | * IN THE SOFTWARE.
20 | *
21 | * Except as contained in this notice, the name of the ROADIZ shall not
22 | * be used in advertising or otherwise to promote the sale, use or other dealings
23 | * in this Software without prior written authorization from Ambroise Maupate and Julien Blanchet.
24 | *
25 | * @file DefaultTransition.js
26 | * @author Adrien Scholaert
27 | */
28 | import { AbstractTransition } from 'starting-blocks'
29 |
30 | /**
31 | * Default Transition. Show / Hide content.
32 | *
33 | * @extends {AbstractTransition}
34 | */
35 | export default class DefaultTransition extends AbstractTransition {
36 | start () {
37 | Promise.all([this.newPageLoading])
38 | .then(this.finish.bind(this))
39 | }
40 |
41 | async finish () {
42 | this.destroyOldPage()
43 | this.scrollTop()
44 | await this.buildNewPage()
45 | this.done()
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/examples/src/js/transitions/FadeTransition.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Ambroise Maupate and Julien Blanchet
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is furnished
9 | * to do so, subject to the following conditions:
10 | * The above copyright notice and this permission notice shall be included in all
11 | * copies or substantial portions of the Software.
12 | *
13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19 | * IN THE SOFTWARE.
20 | *
21 | * Except as contained in this notice, the name of the ROADIZ shall not
22 | * be used in advertising or otherwise to promote the sale, use or other dealings
23 | * in this Software without prior written authorization from Ambroise Maupate and Julien Blanchet.
24 | *
25 | * @file FadeTransition.js
26 | * @author Adrien Scholaert
27 | */
28 |
29 | import TweenLite from 'gsap/TweenLite'
30 | import { AbstractTransition } from 'starting-blocks'
31 |
32 | /**
33 | * Fade Transition class example. Fade Out / Fade In content.
34 | *
35 | * @extends {AbstractTransition}
36 | */
37 | export default class FadeTransition extends AbstractTransition {
38 | /**
39 | * Entry point of the animation
40 | * Automatically called on init()
41 | */
42 | start () {
43 | // Wait new content and the end of fadeOut animation
44 | // this.newPageLoading is a Promise which is resolved when the new content is loaded
45 | Promise.all([this.newPageLoading, this.fadeOut()])
46 | // then fadeIn the new content
47 | .then(this.fadeIn.bind(this))
48 | }
49 |
50 | /**
51 | * Fade out the old content.
52 | * @returns {Promise}
53 | */
54 | fadeOut () {
55 | return new Promise((resolve) => {
56 | TweenLite.to(this.oldPage.rootElement, 0.4, {
57 | alpha: 0,
58 | onComplete: resolve
59 | })
60 | })
61 | }
62 |
63 | /**
64 | * Fade in the new content
65 | */
66 | async fadeIn () {
67 | // Destroy old page
68 | this.destroyOldPage()
69 |
70 | // Manually append and build the new page
71 | await this.buildNewPage()
72 |
73 | // Scroll top
74 | this.scrollTop()
75 |
76 | // fadeIn the new content container
77 | TweenLite.to(this.newPage.rootElement, 0.4, {
78 | autoAlpha: 1,
79 | onComplete: () => {
80 | // IMPORTANT: Call this method at the end
81 | this.done()
82 | }
83 | })
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/examples/src/js/transitions/SlideTransition.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Ambroise Maupate and Julien Blanchet
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is furnished
9 | * to do so, subject to the following conditions:
10 | * The above copyright notice and this permission notice shall be included in all
11 | * copies or substantial portions of the Software.
12 | *
13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19 | * IN THE SOFTWARE.
20 | *
21 | * Except as contained in this notice, the name of the ROADIZ shall not
22 | * be used in advertising or otherwise to promote the sale, use or other dealings
23 | * in this Software without prior written authorization from Ambroise Maupate and Julien Blanchet.
24 | *
25 | * @file SlideTransition.js
26 | * @author Adrien Scholaert
27 | */
28 |
29 | import { AbstractTransition } from 'starting-blocks'
30 | import TweenLite from 'gsap/TweenLite'
31 | import { Power4 } from 'gsap/EasePack'
32 | import 'gsap/ScrollToPlugin'
33 |
34 | /**
35 | * Slide Transition class example.
36 | *
37 | * @extends {AbstractTransition}
38 | */
39 | export default class SlideTransition extends AbstractTransition {
40 | /**
41 | * Entry point of the animation
42 | * Automatically called on init()
43 | */
44 | start () {
45 | Promise.all([this.newPageLoading, this.slideOut()])
46 | .then(this.slideIn.bind(this))
47 | }
48 |
49 | /**
50 | * Slide out the old content.
51 | * @returns {Promise}
52 | */
53 | slideOut () {
54 | return new Promise((resolve) => {
55 | TweenLite.to(this.oldPage.rootElement, 0.5, {
56 | xPercent: 25,
57 | alpha: 0,
58 | ease: Power4.easeIn,
59 | onComplete: resolve
60 | })
61 | })
62 | }
63 |
64 | /**
65 | * Slide in the new content
66 | */
67 | async slideIn () {
68 | // Destroy old page
69 | this.destroyOldPage()
70 |
71 | // Manually append and build the new page
72 | await this.buildNewPage()
73 |
74 | // Scroll top
75 | this.scrollTop()
76 |
77 | // Animate the new page
78 | TweenLite.fromTo(this.newPage.rootElement, 0.75, {
79 | visibility: 'visible',
80 | alpha: 0,
81 | xPercent: -25
82 | }, {
83 | xPercent: 0,
84 | alpha: 1,
85 | ease: Power4.easeOut,
86 | onComplete: () => {
87 | // IMPORTANT: Call this method at the end
88 | this.done()
89 | }
90 | })
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/examples/src/js/utils/utils.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Ambroise Maupate and Julien Blanchet
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is furnished
9 | * to do so, subject to the following conditions:
10 | * The above copyright notice and this permission notice shall be included in all
11 | * copies or substantial portions of the Software.
12 | *
13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19 | * IN THE SOFTWARE.
20 | *
21 | * Except as contained in this notice, the name of the ROADIZ shall not
22 | * be used in advertising or otherwise to promote the sale, use or other dealings
23 | * in this Software without prior written authorization from Ambroise Maupate and Julien Blanchet.
24 | *
25 | * @file utils.js
26 | * @author Adrien Scholaert
27 | */
28 |
29 | export function loadImage (url) {
30 | return new Promise(resolve => {
31 | const imageElement = new window.Image()
32 | imageElement.addEventListener('load', () => {
33 | resolve(imageElement)
34 | })
35 | imageElement.src = url
36 | })
37 | }
38 |
--------------------------------------------------------------------------------
/examples/src/views/partial.html:
--------------------------------------------------------------------------------
1 |
28 |
29 |
34 |
35 |
36 |
Page partial
37 |
This page only contains a div element with .page-content class.
38 |
39 |
40 | Link to page 1
41 | Link to home page
42 |
43 |
44 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus ex est, maximus aliquam sagittis vel, tempus vel odio. Praesent felis elit, blandit et laoreet nec, hendrerit et arcu. Aenean ligula sapien, sodales vel semper in, pulvinar id nulla. Donec pellentesque non tellus vel imperdiet. Pellentesque posuere, nisl sed rutrum tincidunt, augue augue vestibulum ex, vel ornare justo arcu eu magna. Praesent vel nisl enim. Aenean a odio porttitor, fermentum est et, mattis sem. Suspendisse aliquam, risus a aliquam vestibulum, eros ipsum pulvinar sem, quis posuere tellus ex id velit. Quisque eu pellentesque nisl, sed volutpat purus. Sed auctor convallis sapien sed hendrerit. Vivamus et neque facilisis, ultrices ex non, faucibus velit. Aliquam laoreet sed turpis a blandit. Nulla fringilla massa non fermentum rutrum.
45 |
46 |
47 |
Vivamus dolor massa, aliquet ut leo vel, sodales mattis tortor. Aenean at mauris et ex mollis fringilla. Sed volutpat viverra vehicula. Nullam maximus ligula ac nisl aliquam commodo. Donec sed vestibulum orci. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin ut porttitor lorem, sit amet aliquet dui. Nunc dictum, lorem vitae elementum pharetra, sapien orci dignissim ligula, aliquam interdum erat urna a nibh. Morbi quis tellus maximus, pellentesque erat ac, porttitor nulla. Curabitur rhoncus urna non magna mattis aliquet. Vestibulum non lobortis turpis. Nunc aliquet velit a magna dictum pulvinar vitae laoreet tortor. Duis auctor iaculis arcu ac rutrum. Suspendisse vel odio risus.
48 |
49 |
50 |
Vivamus dolor massa, aliquet ut leo vel, sodales mattis tortor. Aenean at mauris et ex mollis fringilla. Sed volutpat viverra vehicula. Nullam maximus ligula ac nisl aliquam commodo. Donec sed vestibulum orci. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin ut porttitor lorem, sit amet aliquet dui. Nunc dictum, lorem vitae elementum pharetra, sapien orci dignissim ligula, aliquam interdum erat urna a nibh. Morbi quis tellus maximus, pellentesque erat ac, porttitor nulla. Curabitur rhoncus urna non magna mattis aliquet. Vestibulum non lobortis turpis. Nunc aliquet velit a magna dictum pulvinar vitae laoreet tortor. Duis auctor iaculis arcu ac rutrum. Suspendisse vel odio risus.
51 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/examples/webpack/build/base.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack'
2 | import debug from 'debug'
3 | import WebpackNotifierPlugin from 'webpack-notifier'
4 | import CleanTerminalPlugin from 'clean-terminal-webpack-plugin'
5 | import CopyWebpackPlugin from 'copy-webpack-plugin'
6 | import HtmlWebpackPlugin from 'html-webpack-plugin'
7 | import HtmlWebpackMultiBuildPlugin from '../modules/HtmlWebpackMultiBuildPlugin'
8 |
9 | const dbg = debug('StartingBlocks:webpack-config:base ')
10 | dbg.color = debug.colors[3]
11 |
12 | const getWebpackConfigBase = (config) => {
13 | dbg('⚙ Exporting default webpack configuration.')
14 |
15 | const paths = config.utils_paths
16 |
17 | let webpackConfig = {
18 | cache: true,
19 | stats: config.stats,
20 | devtool: config.devtool,
21 | target: 'web',
22 | resolve: config.resolve,
23 | module: {
24 | rules: [{
25 | test: /\.js$/,
26 | enforce: 'pre',
27 | loader: 'eslint-loader',
28 | exclude: [/node_modules/, /starting-blocks/]
29 | }, {
30 | test: /\.js?$/,
31 | exclude: /(node_modules)/,
32 | loader: 'babel-loader',
33 | query: {
34 | cacheDirectory: true
35 | }
36 | }]
37 | },
38 | plugins: [
39 | new CleanTerminalPlugin(),
40 | new webpack.DefinePlugin(config.globals),
41 | new webpack.NoEmitOnErrorsPlugin(),
42 | new WebpackNotifierPlugin({ alwaysNotify: true }),
43 | new CopyWebpackPlugin([{
44 | from: paths.clientDemo('img'),
45 | to: paths.distDemo('img')
46 | }, {
47 | from: paths.clientDemo('views'),
48 | to: paths.distDemo('')
49 | }, {
50 | from: paths.clientDemo('css'),
51 | to: paths.distDemo('css')
52 | }]),
53 | new HtmlWebpackPlugin({
54 | filename: paths.distDemo('index.html'),
55 | template: paths.clientDemo('views/index.html'),
56 | cache: false,
57 | inject: false,
58 | refreshOnChange: config.refreshOnChange
59 | }),
60 | new HtmlWebpackMultiBuildPlugin()
61 | ],
62 | externals: config.externals
63 | }
64 |
65 | if (config.bundleAnalyzerReportDemo) {
66 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
67 | webpackConfig.plugins.push(new BundleAnalyzerPlugin())
68 | }
69 |
70 | if (config.refreshOnChange) {
71 | webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin())
72 | }
73 |
74 | return webpackConfig
75 | }
76 |
77 | export default getWebpackConfigBase
78 |
--------------------------------------------------------------------------------
/examples/webpack/build/environments.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack'
2 | import debug from 'debug'
3 |
4 | const dbg = debug('StartingBlocks:webpack-config:environments ')
5 | dbg.color = debug.colors[5]
6 |
7 | const optimization = {
8 | splitChunks: {
9 | chunks: 'all',
10 | cacheGroups: {
11 | commons: {
12 | name: 'commons',
13 | chunks: 'all',
14 | minChunks: 2,
15 | minSize: 0,
16 | enforce: true
17 | }
18 | }
19 | }
20 | }
21 |
22 | export default {
23 | modern: (base, config) => {
24 | const paths = config.utils_paths
25 |
26 | return {
27 | entry: {
28 | app: paths.clientDemo('js/app.js')
29 | },
30 | output: {
31 | path: paths.distDemo(),
32 | filename: 'js/modern.[name].js'
33 | },
34 | module: {
35 | rules: [{
36 | test: /\.js$/,
37 | enforce: 'pre',
38 | loader: 'eslint-loader',
39 | exclude: [/node_modules/, /starting-blocks/]
40 | }, {
41 | test: /\.js?$/,
42 | exclude: /(node_modules)/,
43 | loader: 'babel-loader',
44 | query: {
45 | cacheDirectory: true,
46 | presets: [
47 | [
48 | '@babel/preset-env', {
49 | targets: {
50 | esmodules: true
51 | }
52 | }
53 | ]
54 | ]
55 | }
56 | }]
57 | }
58 | }
59 | },
60 |
61 | legacy: (base, config) => {
62 | const paths = config.utils_paths
63 |
64 | return {
65 | entry: {
66 | app: [
67 | 'whatwg-fetch',
68 | 'es6-promise',
69 | 'intersection-observer',
70 | 'url-polyfill',
71 | paths.clientDemo('js/app.js')
72 | ]
73 | },
74 | output: {
75 | path: paths.distDemo(),
76 | filename: 'js/legacy.[name].js'
77 | }
78 | }
79 | },
80 |
81 | development: (base, config) => {
82 | return {
83 | mode: 'development',
84 | watch: true,
85 | optimization: {
86 | ...optimization
87 | }
88 | }
89 | },
90 |
91 | production: (base, config) => {
92 | dbg('🗑 Cleaning assets folder')
93 | dbg('👽 Using UglifyJs')
94 | dbg('🎨 Using PostCss')
95 |
96 | return {
97 | mode: 'production',
98 | plugins: [
99 | new webpack.DefinePlugin({
100 | 'process.env': {
101 | NODE_ENV: '"production"'
102 | }
103 | })
104 | ],
105 | optimization: {
106 | ...optimization
107 | }
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/examples/webpack/build/index.js:
--------------------------------------------------------------------------------
1 | import debug from 'debug'
2 | import getWebpackConfigBase from './base'
3 | import webpackConfigOverrides from './environments'
4 | import WebpackMerge from 'webpack-merge'
5 |
6 | const dbg = debug('StartingBlocks:webpack-config ')
7 | dbg.color = debug.colors[4]
8 |
9 | const getWebpackConfig = (config) => {
10 | dbg('👷♂️ Creating webpack configuration')
11 |
12 | const base = getWebpackConfigBase(config)
13 | const baseModern = WebpackMerge.smart(base, webpackConfigOverrides['modern'](base, config))
14 | const baseLegacy = WebpackMerge.smart(base, webpackConfigOverrides['legacy'](base, config))
15 |
16 | dbg(`🕵️♂️ Looking for environment overrides for NODE_ENV "${config.env}".`)
17 |
18 | const overrides = webpackConfigOverrides[config.env]
19 |
20 | if (webpackConfigOverrides[config.env]) {
21 | dbg('🙋♂️ Found overrides, applying to default configuration.')
22 |
23 | return [
24 | WebpackMerge.smart(baseModern, overrides(baseModern, config)),
25 | WebpackMerge.smart(baseLegacy, overrides(baseLegacy, config))
26 | ]
27 | } else {
28 | dbg('🤷♂️ No environment overrides found.')
29 | }
30 | }
31 |
32 | export default getWebpackConfig
33 |
--------------------------------------------------------------------------------
/examples/webpack/config/base.js:
--------------------------------------------------------------------------------
1 | import debug from 'debug'
2 | import path from 'path'
3 |
4 | const dbg = debug('StartingBlocks:config:base ')
5 | dbg.color = debug.colors[2]
6 |
7 | const getConfig = () => {
8 | let config = {
9 | env: process.env.NODE_ENV || 'development'
10 | }
11 |
12 | config = {
13 | ...config,
14 | devtool: false,
15 |
16 | // ----------------------------------
17 | // Project Structure
18 | // ----------------------------------
19 | path_base: path.resolve(__dirname, '..'),
20 | dir_entry_demo: '../src',
21 | dir_dist_demo: '../dist',
22 |
23 | bundleAnalyzerReportDemo: false,
24 |
25 | // ----------------------------------
26 | // Stats
27 | // ----------------------------------
28 | // stats: {
29 | // chunks: false,
30 | // chunkModules: false,
31 | // colors: true,
32 | // children: false,
33 | // version: false,
34 | // reasons: false
35 | // },
36 | stats: 'minimal',
37 |
38 | // ----------------------------------
39 | // Inputs
40 | // ----------------------------------
41 | js_vendors: [],
42 |
43 | // ----------------------------------
44 | // Externals
45 | // ----------------------------------
46 | externals: {
47 |
48 | },
49 |
50 | resolve: {
51 | alias: {
52 | 'starting-blocks': path.resolve(__dirname, '../../../')
53 | },
54 | extensions: ['.js']
55 | },
56 |
57 | // ----------------------------------
58 | // Globals
59 | // ----------------------------------
60 | // ⚠️ : You have to add all these constants to .eslintrc file
61 | globals: {
62 | 'DEVELOPMENT': JSON.stringify(config.env === 'development'),
63 | 'PRODUCTION': JSON.stringify(config.env === 'production'),
64 | 'ENVIRONMENT': JSON.stringify(config.env)
65 | }
66 | }
67 |
68 | config.public_path = ''
69 |
70 | dbg('⚙ Exporting default configuration.')
71 | return config
72 | }
73 |
74 | export default getConfig
75 |
--------------------------------------------------------------------------------
/examples/webpack/config/environments.js:
--------------------------------------------------------------------------------
1 | export default {
2 | development: () => {
3 | return {}
4 | },
5 |
6 | production: () => ({
7 | devtool: false
8 | })
9 | }
10 |
--------------------------------------------------------------------------------
/examples/webpack/config/index.js:
--------------------------------------------------------------------------------
1 | import debug from 'debug'
2 | import path from 'path'
3 | import getConfigBase from './base'
4 | import configOverrides from './environments'
5 |
6 | const dbg = debug('StartingBlocks:config ')
7 | dbg.color = debug.colors[1]
8 |
9 | const getConfig = () => {
10 | dbg('👷♂️ Creating configuration.')
11 |
12 | let configBase = getConfigBase()
13 |
14 | dbg(`🕵️♂️ Looking for environment overrides for NODE_ENV "${configBase.env}".`)
15 |
16 | const overrides = configOverrides[configBase.env]
17 |
18 | if (configOverrides[configBase.env]) {
19 | dbg('🙋♂️ Found overrides, applying to default configuration.')
20 | Object.assign(configBase, overrides(configBase))
21 | } else {
22 | dbg('🤷♂️ No environment overrides found.')
23 | }
24 |
25 | // ------------------------------------
26 | // Utilities
27 | // ------------------------------------
28 | const resolve = path.resolve
29 | const base = (...args) =>
30 | Reflect.apply(resolve, null, [configBase.path_base, ...args])
31 |
32 | configBase.utils_paths = {
33 | base: base,
34 | clientDemo: base.bind(null, configBase.dir_entry_demo),
35 | distDemo: base.bind(null, configBase.dir_dist_demo)
36 | }
37 |
38 | return configBase
39 | }
40 |
41 | export default getConfig
42 |
--------------------------------------------------------------------------------
/examples/webpack/modules/HtmlWebpackMultiBuildPlugin.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017, Rezo Zero
3 | *
4 | * @file HtmlWebpackMultiBuildPlugin.js
5 | * @author Adrien Scholaert
6 | */
7 |
8 | 'use strict';
9 |
10 | const HtmlWebpackPlugin = require('html-webpack-plugin');
11 |
12 | function HtmlWebpackMultiBuildPlugin(options) {
13 | this.options = options;
14 | this.js = [];
15 | }
16 |
17 | HtmlWebpackMultiBuildPlugin.prototype = {
18 | apply: function(compiler) {
19 | if (compiler.hooks) {
20 | // webpack 4 support
21 | compiler.hooks.compilation.tap('HtmlWebpackMultiBuildPlugin', compilation => {
22 | HtmlWebpackPlugin.getHooks(compilation).beforeAssetTagGeneration.tapAsync(
23 | 'HtmlWebpackMultiBuildPlugin',
24 | this.beforeHtmlGeneration.bind(this),
25 | );
26 | });
27 | } else {
28 | compiler.plugin('compilation', compilation => {
29 | compilation.plugin('html-webpack-plugin-before-html-generation', this.beforeHtmlGeneration.bind(this));
30 | });
31 | }
32 | },
33 |
34 | beforeHtmlGeneration: function(data, cb) {
35 | for (var jsName of data.assets.js) {
36 | if (!this.js.includes(jsName)) {
37 | this.js.push(jsName)
38 | }
39 | }
40 |
41 | data.plugin.options.modernScripts = this.js.filter((value) => value.indexOf('legacy') === -1);
42 | data.plugin.options.legacyScripts = this.js.filter((value) => value.indexOf('legacy') > 0);
43 |
44 | cb(null, data);
45 | }
46 | };
47 |
48 | module.exports = HtmlWebpackMultiBuildPlugin;
49 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "starting-blocks",
3 | "version": "5.0.7",
4 | "description": "A JS page and blocks framework written in ES6 to go along Roadiz CMS or any other great CMS.",
5 | "author": "Rezo Zero",
6 | "license": "MIT",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/rezozero/starting-blocks.git"
10 | },
11 | "bugs": {
12 | "url": "https://github.com/rezozero/starting-blocks/issues"
13 | },
14 | "homepage": "https://startingblocks.rezo-zero.com",
15 | "scripts": {
16 | "start": "concurrently \"npm run dev\" \"npm run serve\"",
17 | "test": "echo \"No test specified\"",
18 | "dev": "NODE_ENV=development rollup --config -w",
19 | "serve": "http-server ./examples/dist -o",
20 | "build": "NODE_ENV=production rollup --config",
21 | "demo": "NODE_ENV=production TARGET=demo webpack --progress",
22 | "dev-demo": "NODE_ENV=development TARGET=demo webpack --progress",
23 | "doc": "esdoc -c esdoc.json"
24 | },
25 | "engines": {
26 | "node": ">=8.0.0",
27 | "npm": ">=5.0.0"
28 | },
29 | "main": "dist/main.umd.js",
30 | "module": "dist/main.esm.js",
31 | "devDependencies": {
32 | "@babel/cli": "7.1.2",
33 | "@babel/core": "7.1.2",
34 | "@babel/plugin-proposal-class-properties": "7.1.0",
35 | "@babel/plugin-proposal-decorators": "7.1.2",
36 | "@babel/plugin-proposal-do-expressions": "7.0.0",
37 | "@babel/plugin-proposal-export-default-from": "7.0.0",
38 | "@babel/plugin-proposal-export-namespace-from": "7.0.0",
39 | "@babel/plugin-proposal-function-bind": "7.0.0",
40 | "@babel/plugin-proposal-function-sent": "7.1.0",
41 | "@babel/plugin-proposal-logical-assignment-operators": "7.0.0",
42 | "@babel/plugin-proposal-nullish-coalescing-operator": "7.0.0",
43 | "@babel/plugin-proposal-numeric-separator": "7.0.0",
44 | "@babel/plugin-proposal-optional-chaining": "7.0.0",
45 | "@babel/plugin-proposal-pipeline-operator": "7.0.0",
46 | "@babel/plugin-proposal-throw-expressions": "7.0.0",
47 | "@babel/plugin-syntax-dynamic-import": "7.0.0",
48 | "@babel/plugin-syntax-import-meta": "7.0.0",
49 | "@babel/plugin-transform-runtime": "7.1.0",
50 | "@babel/polyfill": "7.0.0",
51 | "@babel/preset-env": "7.1.0",
52 | "@babel/register": "7.0.0",
53 | "@babel/runtime": "7.1.2",
54 | "babel-eslint": "9.0.0",
55 | "babel-loader": "8.0.4",
56 | "bottlejs": "1.7.1",
57 | "clean-terminal-webpack-plugin": "1.1.0",
58 | "concurrently": "4.0.1",
59 | "copy-webpack-plugin": "4.5.4",
60 | "es6-promise": "4.2.5",
61 | "esdoc": "1.1.0",
62 | "esdoc-brand-plugin": "1.0.1",
63 | "esdoc-ecmascript-proposal-plugin": "1.0.0",
64 | "esdoc-exclude-source-plugin": "1.0.0",
65 | "esdoc-standard-plugin": "1.0.0",
66 | "eslint": "5.7.0",
67 | "eslint-config-standard": "12.0.0",
68 | "eslint-loader": "2.1.1",
69 | "eslint-plugin-dependencies": "2.4.0",
70 | "eslint-plugin-html": "4.0.6",
71 | "eslint-plugin-import": "2.14.0",
72 | "eslint-plugin-node": "7.0.1",
73 | "eslint-plugin-promise": "4.0.1",
74 | "eslint-plugin-standard": "4.0.0",
75 | "event-hooks-webpack-plugin": "2.1.0",
76 | "extract-text-webpack-plugin": "4.0.0-beta.0",
77 | "gsap": "2.0.2",
78 | "html-webpack-include-assets-plugin": "1.0.6",
79 | "html-webpack-multi-build-plugin": "1.0.0",
80 | "html-webpack-plugin": "4.0.0-beta.2",
81 | "http-server": "0.11.1",
82 | "imports-loader": "0.8.0",
83 | "intersection-observer": "0.5.1",
84 | "lazysizes": "4.1.4",
85 | "post-compile-webpack-plugin": "0.1.2",
86 | "rollup": "0.66.6",
87 | "rollup-plugin-alias": "1.4.0",
88 | "rollup-plugin-babel": "4.0.3",
89 | "rollup-plugin-commonjs": "9.2.0",
90 | "rollup-plugin-node-resolve": "3.4.0",
91 | "rollup-plugin-terser": "3.0.0",
92 | "rollup-plugin-uglify": "6.0.0",
93 | "script-ext-html-webpack-plugin": "2.0.1",
94 | "terser-webpack-plugin": "1.1.0",
95 | "uglifyjs-webpack-plugin": "2.0.1",
96 | "url-polyfill": "1.1.0",
97 | "webpack": "4.22.0",
98 | "webpack-bundle-analyzer": "2.13.1",
99 | "webpack-cli": "3.1.2",
100 | "webpack-merge": "4.1.4",
101 | "webpack-notifier": "1.7.0",
102 | "whatwg-fetch": "3.0.0",
103 | "worker-loader": "1.1.1"
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017, Rezo Zero
3 | *
4 | * @file rollup.config.js
5 | * @author Adrien Scholaert
6 | */
7 |
8 | import commonjs from 'rollup-plugin-commonjs'
9 | import resolve from 'rollup-plugin-node-resolve'
10 | import babel from 'rollup-plugin-babel'
11 |
12 | const normalBundles = {
13 | input: 'src/bundle.js',
14 | output: [{
15 | file: 'dist/main.esm.js',
16 | format: 'esm'
17 | }, {
18 | file: 'dist/main.cjs.js',
19 | format: 'cjs'
20 | }, {
21 | file: 'dist/main.amd.js',
22 | format: 'amd'
23 | }, {
24 | file: 'dist/main.umd.js',
25 | format: 'umd',
26 | name: 'starting-blocks'
27 | }],
28 | plugins: [
29 | commonjs(), // prise en charge de require
30 | resolve(), // prise en charge des modules depuis node_modules
31 | babel({ // transpilation
32 | runtimeHelpers: true,
33 | exclude: 'node_modules/**'
34 | })
35 | ]
36 | }
37 |
38 | const es6Bundle = {
39 | input: 'src/bundle.js',
40 | output: [{
41 | file: 'dist/main.e6.js',
42 | format: 'esm'
43 | }],
44 | plugins: [
45 | commonjs(), // prise en charge de require
46 | resolve(), // prise en charge des modules depuis node_modules
47 | babel({
48 | runtimeHelpers: true,
49 | exclude: 'node_modules/**',
50 | presets: [
51 | ['@babel/preset-env', {
52 | targets: {
53 | esmodules: true
54 | }
55 | }]
56 | ]
57 | })
58 | ]
59 | }
60 |
61 | export default [
62 | normalBundles,
63 | es6Bundle
64 | ]
65 |
--------------------------------------------------------------------------------
/src/StartingBlocks.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017, Rezo Zero
3 | *
4 | * @file StartingBlocks.js
5 | * @author Adrien Scholaert
6 | */
7 | import Bottle from 'bottlejs'
8 | import PageBuilder from './services/PageBuilder'
9 | import BlockBuilder from './services/BlockBuilder'
10 | import Dom from './services/Dom'
11 | import DefaultPage from './pages/DefaultPage'
12 |
13 | /**
14 | * @namespace
15 | * @type {Object} defaults - Default config
16 | * @property {String} defaults.wrapperId - Id of the main wrapper
17 | * @property {String} defaults.pageBlockClass
18 | * @property {String} defaults.pageClass - Class name used to identify the containers
19 | * @property {String} defaults.objectTypeAttr - The data attribute name to find the node type
20 | * @property {String} defaults.noAjaxLinkClass
21 | * @property {String} defaults.noPrefetchClass - Class name used to ignore prefetch on links.
22 | * @property {boolean} defaults.manualDomAppend
23 | * @const
24 | * @default
25 | */
26 | const CONFIG = {
27 | defaults: {
28 | wrapperId: 'sb-wrapper',
29 | pageBlockClass: 'page-block',
30 | pageClass: 'page-content',
31 | objectTypeAttr: 'data-node-type',
32 | noAjaxLinkClass: 'no-ajax-link',
33 | noPrefetchClass: 'no-prefetch',
34 | manualDomAppend: false,
35 | debug: 0
36 | }
37 | }
38 |
39 | export default class StartingBlocks {
40 | constructor (config = {}) {
41 | this.bottle = new Bottle()
42 | this.bootables = []
43 |
44 | this.bottle.value('Config', {
45 | ...CONFIG.defaults,
46 | ...config
47 | })
48 |
49 | window.startingBlocksDebugLevel = this.bottle.container.Config.debug
50 |
51 | this.provider('Dom', Dom)
52 | this.provider('BlockBuilder', BlockBuilder)
53 | this.instanceFactory('DefaultPage', c => {
54 | return new DefaultPage(c)
55 | })
56 | }
57 |
58 | provider (id, ClassName, ...args) {
59 | if (!id || !ClassName) {
60 | throw new Error('A parameter is missing')
61 | }
62 |
63 | this.bottle.provider(id, function () {
64 | this.$get = container => {
65 | return new ClassName(container, ...args)
66 | }
67 | })
68 | }
69 |
70 | factory (id, f) {
71 | this.bottle.factory(id, f)
72 | }
73 |
74 | instanceFactory (id, f) {
75 | this.bottle.instanceFactory(id, f)
76 | }
77 |
78 | bootableProvider (id, ClassName, ...args) {
79 | this.provider(id, ClassName, ...args)
80 | this.bootables.push(id)
81 | }
82 |
83 | boot () {
84 | this.bootableProvider('PageBuilder', PageBuilder)
85 |
86 | for (const serviceName of this.bootables) {
87 | if (this.bottle.container.hasOwnProperty(serviceName)) {
88 | this.bottle.container[serviceName].boot()
89 | }
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/abstracts/AbstractBlock.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2016, Ambroise Maupate
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is furnished
9 | * to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 | * IN THE SOFTWARE.
21 | *
22 | * @file AbstractBlock.js
23 | * @author Ambroise Maupate
24 | * @author Adrien Scholaert
25 | */
26 |
27 | import AbstractService from './AbstractService'
28 | import { debug } from '../utils/Logger'
29 |
30 | /**
31 | * Base class for creating block implementations.
32 | *
33 | * **Do not instanciate this class directly, create a sub-class**.
34 | *
35 | * @abstract
36 | */
37 | export default class AbstractBlock extends AbstractService {
38 | /**
39 | * Abstract block constructor.
40 | *
41 | * It‘s better to extend this class by using `init` method instead
42 | * of extending `constructor`.
43 | *
44 | * @param {Object} container
45 | * @param {String} blockName
46 | * @constructor
47 | */
48 | constructor (container, blockName = 'AbstractBlock') {
49 | super(container, blockName)
50 |
51 | /**
52 | * Node Type block name type
53 | *
54 | * @type {String|null}
55 | */
56 | this.type = null
57 |
58 | /**
59 | * Current page instance
60 | *
61 | * @type {AbstractPage|null}
62 | */
63 | this.page = null
64 |
65 | /**
66 | * Container
67 | * Root container HTMLElement for current block.
68 | *
69 | * @type {HTMLElement|null}
70 | */
71 | this.rootElement = null
72 |
73 | /**
74 | * Block id
75 | *
76 | * @type {String|null}
77 | */
78 | this.id = null
79 |
80 | /**
81 | * Node name
82 | *
83 | * @type {String}
84 | */
85 | this.name = null
86 | }
87 |
88 | /**
89 | * Basic members initialization for children classes.
90 | * Do not search for page blocks here, use `onPageReady` method instead
91 | *
92 | * @abstract
93 | */
94 | init () {
95 | debug('\t✳️ #' + this.id + ' %c[' + this.type + ']', 'color:grey')
96 | }
97 |
98 | /**
99 | * Bind load and resize events for this specific block.
100 | *
101 | * Do not forget to call `super.initEvents();` while extending this method.
102 | *
103 | * @abstract
104 | */
105 | initEvents () {}
106 |
107 | /**
108 | * Destroy current block.
109 | *
110 | * Do not forget to call `super.destroy();` while extending this method.
111 | */
112 | destroy () {
113 | debug('\t🗑️ #' + this.id + ' %c[' + this.type + ']', 'color:grey')
114 | this.destroyEvents()
115 | }
116 |
117 | /**
118 | * Unbind event block events.
119 | *
120 | * Make sure you’ve used binded methods to be able to
121 | * `off` them correctly.
122 | *
123 | * Do not forget to call `super.destroyEvents();` while extending this method.
124 | *
125 | * @abstract
126 | */
127 | destroyEvents () {}
128 |
129 | /**
130 | * Called on window resize
131 | *
132 | * @abstract
133 | */
134 | onResize () {}
135 |
136 | /**
137 | * Called once all page blocks have been created.
138 | *
139 | * @abstract
140 | */
141 | onPageReady () {}
142 | }
143 |
--------------------------------------------------------------------------------
/src/abstracts/AbstractBlockBuilder.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017, Rezo Zero
3 | *
4 | * @file AbstractBlockBuilder.js
5 | * @author Adrien Scholaert
6 | */
7 |
8 | import AbstractService from './AbstractService'
9 |
10 | export default class AbstractBlockBuilder extends AbstractService {
11 | /**
12 | * Returns an `AbstractBlock` child class instance
13 | * according to the nodeTypeName or an AbstractBlock as default.
14 | *
15 | * Comment out the default case if you don’t want a default block to be instantiated
16 | * for each block.
17 | *
18 | * @param {String} blockType
19 | * @return {AbstractBlock|null}
20 | */
21 | async getBlockInstance (blockType) { return null }
22 | }
23 |
--------------------------------------------------------------------------------
/src/abstracts/AbstractBootableService.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017, Rezo Zero
3 | *
4 | * @file AbstractBootableService.js
5 | * @author Adrien Scholaert
6 | */
7 |
8 | import AbstractService from './AbstractService'
9 |
10 | export default class AbstractBootableService extends AbstractService {
11 | boot () {}
12 | }
13 |
--------------------------------------------------------------------------------
/src/abstracts/AbstractInViewBlock.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017, Rezo Zero
3 | *
4 | * @file AbstractInViewBlock.js
5 | * @author Adrien Scholaert
6 | */
7 |
8 | import AbstractBlock from './AbstractBlock'
9 |
10 | export default class AbstractInViewBlock extends AbstractBlock {
11 | constructor (container, blockName = 'AbstractInViewBlock') {
12 | super(container, blockName)
13 |
14 | // Values
15 | this.observer = null
16 | this.observerOptions = {
17 | root: null,
18 | rootMargin: '0px',
19 | threshold: 0
20 | }
21 |
22 | // Bind method
23 | this.onIntersectionCallback = this.onIntersectionCallback.bind(this)
24 | }
25 |
26 | init () {
27 | super.init()
28 |
29 | // Create an observer
30 | this.observer = new window.IntersectionObserver(this.onIntersectionCallback, this.observerOptions)
31 |
32 | // Add block rootElement in the observer
33 | this.observer.observe(this.rootElement)
34 | }
35 |
36 | destroyEvents () {
37 | super.destroyEvents()
38 | this.unobserve()
39 | }
40 |
41 | onIntersectionCallback (entries) {
42 | for (const entry of entries) {
43 | if (entry.intersectionRatio > 0) {
44 | this.onScreen(entry)
45 | } else {
46 | this.offScreen(entry)
47 | }
48 | }
49 | }
50 |
51 | onScreen (entry) {}
52 |
53 | offScreen (entry) {}
54 |
55 | unobserve () {
56 | this.observer.unobserve(this.rootElement)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/abstracts/AbstractPage.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2016, Ambroise Maupate
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is furnished
9 | * to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 | * IN THE SOFTWARE.
21 | *
22 | * @file AbstractPage.js
23 | * @author Ambroise Maupate
24 | * @author Adrien Scholaert
25 | */
26 |
27 | import debounce from '../utils/debounce'
28 | import AbstractService from './AbstractService'
29 | import { debug, warn } from '../utils/Logger'
30 |
31 | /**
32 | * Base class for creating page implementations.
33 | *
34 | * **Do not instanciate this class directly, create a sub-class**.
35 | *
36 | * @abstract
37 | */
38 | export default class AbstractPage extends AbstractService {
39 | /**
40 | * Base constructor for Pages.
41 | * @constructor
42 | */
43 | constructor (container) {
44 | super(container, 'AbstractPage')
45 |
46 | /**
47 | * Container element
48 | *
49 | * @type {HTMLElement}
50 | */
51 | this.rootElement = null
52 |
53 | /**
54 | * Page id
55 | *
56 | * @type {String|null}
57 | */
58 | this.id = null
59 |
60 | /**
61 | * Page context (static or ajax)
62 | *
63 | * @type {String|null}
64 | */
65 | this.context = null
66 |
67 | /**
68 | * Page type
69 | *
70 | * @type {String|null}
71 | */
72 | this.type = null
73 |
74 | /**
75 | * Is home ?
76 | *
77 | * @type {boolean}
78 | */
79 | this.isHome = null
80 |
81 | /**
82 | * AbstractBlock collection.
83 | *
84 | * @type {Array}
85 | */
86 | this.blocks = []
87 |
88 | /**
89 | * Node name
90 | *
91 | * @type {String|null}
92 | */
93 | this.name = null
94 |
95 | /**
96 | * Meta title
97 | * @type {String|null}
98 | */
99 | this.metaTitle = null
100 |
101 | // Bind methods
102 | this.onResize = this.onResize.bind(this)
103 | this.onResizeDebounce = debounce(this.onResize, 50, false)
104 | this.bindedUpdateBlocks = debounce(this.updateBlocks.bind(this), 50, false)
105 | }
106 |
107 | /**
108 | * Initialize page.
109 | *
110 | * You should always extends this method in your child implementations instead
111 | * of extending page constructor.
112 | */
113 | async init () {
114 | // Debug
115 | debug('✳️ #' + this.id + ' %c[' + this.type + '] [' + this.context + ']', 'color:grey')
116 |
117 | /**
118 | * HTMLElement blocks collection.
119 | *
120 | * @type {Array}
121 | */
122 | this.blockElements = [...this.rootElement.querySelectorAll(`.${this.getService('Config').pageBlockClass}`)]
123 |
124 | /**
125 | * @type {Number}
126 | */
127 | this.blockLength = this.blockElements.length
128 |
129 | if (this.blockLength) {
130 | await this.initBlocks()
131 | }
132 |
133 | this.initEvents()
134 | }
135 |
136 | /**
137 | * Destroy current page and all its blocks.
138 | */
139 | destroy () {
140 | debug('🗑️ #' + this.id + ' %c[' + this.type + ']', 'color:grey')
141 | this.rootElement.parentNode.removeChild(this.rootElement)
142 | this.destroyEvents()
143 |
144 | // Do not remove name class on body if destroyed page is the same as current one.
145 | if (this.getService('PageBuilder').page !== null && this.getService('PageBuilder').page.name !== this.name) {
146 | document.body.classList.remove(this.name)
147 | }
148 |
149 | // Do not remove type class on body if destroyed page is the same as current one.
150 | if (this.getService('PageBuilder').page !== null && this.getService('PageBuilder').page.type !== this.type) {
151 | document.body.classList.remove(this.type)
152 | }
153 |
154 | // Blocks
155 | if (this.blocks !== null) {
156 | for (let blockIndex in this.blocks) {
157 | if (this.blocks.hasOwnProperty(blockIndex)) {
158 | this.blocks[blockIndex].destroy()
159 | }
160 | }
161 | }
162 | }
163 |
164 | /**
165 | * Initialize basic events.
166 | */
167 | initEvents () {
168 | window.addEventListener('resize', this.onResizeDebounce)
169 |
170 | this.domObserver = new window.MutationObserver(this.bindedUpdateBlocks)
171 | this.domObserver.observe(this.rootElement, {
172 | childList: true,
173 | attributes: false,
174 | characterData: false,
175 | subtree: true
176 | })
177 | }
178 |
179 | /**
180 | * Destroy events
181 | */
182 | destroyEvents () {
183 | window.removeEventListener('resize', this.onResizeDebounce)
184 | this.domObserver.disconnect()
185 | }
186 |
187 | /**
188 | * Initialize page blocks on page.
189 | */
190 | async initBlocks () {
191 | for (let blockIndex = 0; blockIndex < this.blockLength; blockIndex++) {
192 | /**
193 | * New Block.
194 | *
195 | * @type {AbstractBlock}
196 | */
197 | let block = await this.initSingleBlock(this.blockElements[blockIndex])
198 |
199 | // Prevent undefined blocks to be appended to block collection.
200 | if (block) {
201 | this.blocks.push(block)
202 | }
203 | }
204 |
205 | // Notify all blocks that page init is over.
206 | for (let i = this.blocks.length - 1; i >= 0; i--) {
207 | if (typeof this.blocks[i].onPageReady === 'function') this.blocks[i].onPageReady()
208 | }
209 | }
210 |
211 | /**
212 | * Append new blocks which were not present at init.
213 | */
214 | async updateBlocks () {
215 | debug('\t📯 Page DOM changed…')
216 |
217 | // Create new blocks
218 | this.blockElements = this.rootElement.querySelectorAll(`.${this.getService('Config').pageBlockClass}`)
219 | this.blockLength = this.blockElements.length
220 |
221 | for (let blockIndex = 0; blockIndex < this.blockLength; blockIndex++) {
222 | let blockElement = this.blockElements[blockIndex]
223 | const existingBlock = this.getBlockById(blockElement.id)
224 |
225 | if (!blockElement.id) break
226 |
227 | if (existingBlock === null) {
228 | try {
229 | let block = await this.initSingleBlock(this.blockElements[blockIndex])
230 |
231 | if (block) {
232 | this.blocks.push(block)
233 | block.onPageReady()
234 | }
235 | } catch (e) {
236 | warn(e.message)
237 | }
238 | }
239 | }
240 | }
241 |
242 | /**
243 | * @param {HTMLElement} blockElement
244 | * @return {AbstractBlock}
245 | */
246 | async initSingleBlock (blockElement) {
247 | if (!blockElement.id) return null
248 |
249 | let blockType = blockElement.getAttribute(this.getService('Config').objectTypeAttr)
250 | let blockInstance = await this.getService('BlockBuilder').getBlockInstance(blockType)
251 |
252 | if (!blockInstance) {
253 | return null
254 | }
255 |
256 | // Set values
257 | blockInstance.type = blockType
258 | blockInstance.page = this
259 | blockInstance.rootElement = blockElement
260 | blockInstance.id = blockElement.id
261 | blockInstance.name = blockElement.hasAttribute('data-node-name') ? blockElement.getAttribute('data-node-name') : ''
262 |
263 | // Init everything
264 | blockInstance.init()
265 | blockInstance.initEvents()
266 |
267 | return blockInstance
268 | }
269 |
270 | /**
271 | * Get a page block instance from its `id`.
272 | *
273 | * @param {String} id
274 | * @return {AbstractBlock|null}
275 | */
276 | getBlockById (id) {
277 | for (const block of this.blocks) {
278 | if (block.id && block.id === id) {
279 | return block
280 | }
281 | }
282 |
283 | return null
284 | }
285 |
286 | /**
287 | * Get a page block index from its `id`.
288 | *
289 | * @param {String} id
290 | * @return {*|null}
291 | */
292 | getBlockIndexById (id) {
293 | const l = this.blocks.length
294 |
295 | for (let i = 0; i < l; i++) {
296 | if (this.blocks[i].id && this.blocks[i].id === id) {
297 | return i
298 | }
299 | }
300 |
301 | return null
302 | }
303 |
304 | /**
305 | * Get the first page block instance from its `type`.
306 | *
307 | * @param {String} type
308 | * @return {AbstractBlock|null}
309 | */
310 | getFirstBlockByType (type) {
311 | const index = this.getFirstBlockIndexByType(type)
312 | if (this.blocks[index]) {
313 | return this.blocks[index]
314 | }
315 |
316 | return null
317 | }
318 |
319 | /**
320 | * Get the first page block index from its `type`.
321 | *
322 | * @param {String} type
323 | * @return {*|null}
324 | */
325 | getFirstBlockIndexByType (type) {
326 | const l = this.blocks.length
327 |
328 | for (let i = 0; i < l; i++) {
329 | if (this.blocks[i].type && this.blocks[i].type === type) {
330 | return i
331 | }
332 | }
333 |
334 | return null
335 | }
336 |
337 | /**
338 | * @abstract
339 | */
340 | onResize () {
341 | for (const block of this.blocks) {
342 | block.onResize()
343 | }
344 | }
345 | }
346 |
--------------------------------------------------------------------------------
/src/abstracts/AbstractService.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017, Rezo Zero
3 | *
4 | * @file AbstractService.js
5 | * @author Adrien Scholaert
6 | */
7 |
8 | import UnknownServiceException from '../errors/UnknownServiceException'
9 | import DependencyNotFulfilledException from '../errors/DependencyNotFulfilledException'
10 |
11 | export default class AbstractService {
12 | constructor (container = {}, serviceName = 'AbstractService', dependencies = ['Config']) {
13 | this._container = container
14 | this._serviceName = serviceName
15 |
16 | this.checkDependencies(dependencies)
17 | }
18 |
19 | init () {}
20 |
21 | hasService (serviceName) {
22 | return this._container.hasOwnProperty(serviceName)
23 | }
24 |
25 | checkDependencies (dependencies = []) {
26 | for (const serviceName of dependencies) {
27 | if (!this.hasService(serviceName)) {
28 | throw new DependencyNotFulfilledException(this._serviceName, serviceName)
29 | }
30 | }
31 | }
32 |
33 | getService (serviceName) {
34 | if (!this.hasService(serviceName)) {
35 | throw new UnknownServiceException(serviceName)
36 | }
37 |
38 | return this._container[serviceName]
39 | }
40 |
41 | get serviceName () {
42 | return this._serviceName
43 | }
44 |
45 | set serviceName (value) {
46 | this._serviceName = value
47 | }
48 |
49 | get container () {
50 | return this._container
51 | }
52 |
53 | set container (value) {
54 | this._container = value
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/abstracts/AbstractSplashscreen.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017, Rezo Zero
3 | *
4 | * @file AbstractSplashscreen.js
5 | * @author Adrien Scholaert
6 | */
7 |
8 | import AbstractBootableService from './AbstractBootableService'
9 |
10 | export default class AbstractSplashscreen extends AbstractBootableService {
11 | constructor (container, serviceName = 'AbstractSplashscreen') {
12 | super(container, serviceName)
13 |
14 | this._splashscreenHidden = false
15 | }
16 |
17 | set splashscreenHidden (value) {
18 | this._splashscreenHidden = value
19 | }
20 |
21 | get splashscreenHidden () {
22 | return this._splashscreenHidden
23 | }
24 |
25 | hide () {
26 | return Promise.resolve()
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/abstracts/AbstractTransition.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2016, Ambroise Maupate
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is furnished
9 | * to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 | * IN THE SOFTWARE.
21 | *
22 | * @file AbstractTransition.js
23 | * @author Quentin Neyraud
24 | * @author Adrien Scholaert
25 | */
26 | import Utils from '../utils/Utils'
27 | import AbstractService from './AbstractService'
28 |
29 | /**
30 | * Base class for creating transition.
31 | *
32 | * @abstract
33 | */
34 | export default class AbstractTransition extends AbstractService {
35 | /**
36 | * Constructor.
37 | * Do not override this method.
38 | *
39 | * @constructor
40 | */
41 | constructor (container, serviceName = 'Transition', dependencies = []) {
42 | super(container, serviceName, dependencies)
43 |
44 | /**
45 | * @type {AbstractPage|null} old Page instance
46 | */
47 | this.oldPage = null
48 |
49 | /**
50 | * @type {AbstractPage|null}
51 | */
52 | this.newPage = null
53 |
54 | /**
55 | * @type {Promise|null}
56 | */
57 | this.newPageLoading = null
58 |
59 | /**
60 | * @type {(HTMLElement|null)}
61 | */
62 | this.originElement = null
63 |
64 | /**
65 | * @type {(Object|null)}
66 | */
67 | this.cursorPosition = null
68 | }
69 |
70 | /**
71 | * Initialize transition.
72 | * Do not override this method.
73 | *
74 | * @param {AbstractPage} oldPage
75 | * @param {Promise} newPagePromise
76 | * @param {(HTMLElement|null)} el The html element where the transition has been launched
77 | * @param {Object} cursorPosition The cursor position when the transition has been launched
78 | * @returns {Promise}
79 | */
80 | init (oldPage, newPagePromise, el, cursorPosition) {
81 | this.oldPage = oldPage
82 | this._newPagePromise = newPagePromise
83 | this.originElement = el
84 | this.cursorPosition = cursorPosition
85 | this.deferred = Utils.deferred()
86 | this.newPageReady = Utils.deferred()
87 | this.newPageLoading = this.newPageReady.promise
88 |
89 | this.start()
90 |
91 | this._newPagePromise.then(newPage => {
92 | this.newPage = newPage
93 | this.newPageReady.resolve()
94 | })
95 |
96 | return this.deferred.promise
97 | }
98 |
99 | /**
100 | * Call this function when the Transition is finished.
101 | */
102 | done () {
103 | this.destroyOldPage()
104 |
105 | const visibility = this.newPage.rootElement.style.visibility
106 | if (visibility !== 'inherit' || visibility !== 'hidden') {
107 | this.newPage.rootElement.style.visibility = 'visible'
108 | }
109 |
110 | this.deferred.resolve()
111 | }
112 |
113 | scrollTop () {
114 | if (document.scrollingElement) {
115 | document.scrollingElement.scrollTop = 0
116 | document.scrollingElement.scrollTo(0, 0)
117 | } else {
118 | document.body.scrollTop = 0
119 | document.documentElement.scrollTop = 0
120 | window.scrollTo(0, 0)
121 | }
122 | }
123 |
124 | destroyOldPage () {
125 | if (this.oldPage) {
126 | this.oldPage.destroy()
127 | this.oldPage = null
128 | }
129 | }
130 |
131 | async buildNewPage () {
132 | if (this.container) {
133 | const pjaxService = this.getService('Pjax')
134 | const domService = this.getService('Dom')
135 | const pageBuilderService = this.getService('PageBuilder')
136 |
137 | // Add the new dom
138 | domService.putContainer(pjaxService.containerElement)
139 | // Build the new page
140 | this.newPage = await pageBuilderService.buildPage(pjaxService.containerElement)
141 | // Then notify
142 | pjaxService.onNewPageLoaded(this.newPage)
143 | }
144 | }
145 |
146 | /**
147 | * Entry point to create a custom Transition.
148 | * @abstract
149 | */
150 | start () {}
151 | }
152 |
--------------------------------------------------------------------------------
/src/abstracts/AbstractTransitionFactory.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017, Rezo Zero
3 | *
4 | * @file AbstractTransitionFactory.js
5 | * @author Adrien Scholaert
6 | */
7 |
8 | import AbstractService from './AbstractService'
9 |
10 | /**
11 | * Abstract Transition mapper class.
12 | *
13 | * This class maps your `data-transition` with your *ES6* classes.
14 | *
15 | * **You must define your own ClassFactory for each of your projects.**.
16 | * @abstract
17 | */
18 | export default class AbstractTransitionFactory extends AbstractService {
19 | /**
20 | * Get Transition
21 | *
22 | * @param {Object} previousState
23 | * @param {Object} state
24 | * @returns {AbstractTransition}
25 | * @abstract
26 | */
27 | getTransition (previousState, state) {}
28 | }
29 |
--------------------------------------------------------------------------------
/src/bundle.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * @name Starting Blocks
3 | * @license MIT
4 | * @copyright Copyright © 2018, Rezo Zero
5 | * @version 5.0.0
6 | * @author Adrien Scholaert
7 | * @author Ambroise Maupate
8 | */
9 |
10 | import * as EventTypes from './types/EventTypes'
11 | import StartingBlocks from './StartingBlocks'
12 |
13 | export { default as PageBuilder } from './services/PageBuilder'
14 | export { default as BlockBuilder } from './services/BlockBuilder'
15 | export { default as Pjax } from './services/Pjax'
16 | export { default as History } from './services/History'
17 | export { default as Prefetch } from './services/Prefetch'
18 | export { default as CacheProvider } from './services/CacheProvider'
19 | export { default as AbstractPage } from './abstracts/AbstractPage'
20 | export { default as AbstractBlock } from './abstracts/AbstractBlock'
21 | export { default as AbstractInViewBlock } from './abstracts/AbstractInViewBlock'
22 | export { default as AbstractBlockBuilder } from './abstracts/AbstractBlockBuilder'
23 | export { default as AbstractService } from './abstracts/AbstractService'
24 | export { default as AbstractSplashscreen } from './abstracts/AbstractSplashscreen'
25 | export { default as AbstractTransitionFactory } from './abstracts/AbstractTransitionFactory'
26 | export { default as AbstractTransition } from './abstracts/AbstractTransition'
27 | export { default as DefaultTransition } from './transitions/DefaultTransition'
28 | export { default as Utils } from './utils/Utils'
29 | export { default as Scroll } from './utils/Scroll'
30 | export { default as polyfills } from './utils/polyfills'
31 | export { default as gaTrackErrors } from './utils/gaTrackErrors'
32 | export { default as debounce } from './utils/debounce'
33 | export { default as BootstrapMedia } from './utils/BootstrapMedia'
34 | export { default as Dispatcher } from './dispatcher/Dispatcher'
35 | export { EventTypes }
36 |
37 | export default StartingBlocks
38 |
--------------------------------------------------------------------------------
/src/dispatcher/Dispatcher.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2016, Ambroise Maupate
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is furnished
9 | * to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 | * IN THE SOFTWARE.
21 | *
22 | * @file Events.js
23 | * @author Ambroise Maupate
24 | */
25 |
26 | import { debug } from '../utils/Logger'
27 |
28 | /**
29 | * Event dispatcher.
30 | */
31 | export default class Dispatcher {
32 | static commit (eventType, detail) {
33 | const event = new window.CustomEvent(eventType, { detail })
34 | debug('🚩 Dispatched ' + eventType)
35 | window.dispatchEvent(event)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/errors/DependencyNotFulfilledException.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017, Rezo Zero
3 | *
4 | * @file DependencyNotFulfilledException.js
5 | * @author Adrien Scholaert
6 | */
7 |
8 | export default class DependencyNotFulfilledException extends Error {
9 | constructor (firstServiceName, secondeServiceName) {
10 | super(`Object of type "${firstServiceName}" needs "${secondeServiceName}" service`)
11 | this.name = `DependencyNotFulfilledException`
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/errors/UnknownServiceException.js:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | * Copyright © 2017, Rezo Zero
4 | *
5 | * @file UnknownServiceException.js
6 | * @author Adrien Scholaert
7 | */
8 |
9 | export default class UnknownServiceException extends Error {
10 | constructor (id) {
11 | super(`Service "${id}" is not defined`)
12 | this.name = `UnknownServiceException`
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/pages/DefaultPage.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017, Rezo Zero
3 | *
4 | * @file DefaultPage.js
5 | * @author Adrien Scholaert
6 | */
7 |
8 | import AbstractPage from '../abstracts/AbstractPage'
9 |
10 | export default class DefaultPage extends AbstractPage {
11 | constructor (container) {
12 | super(container, 'DefaultPage')
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/services/BlockBuilder.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017, Rezo Zero
3 | *
4 | * @file BlockBuilder.js
5 | * @author Adrien Scholaert
6 | */
7 |
8 | import AbstractBlockBuilder from '../abstracts/AbstractBlockBuilder'
9 | import { debug } from '../utils/Logger'
10 |
11 | export default class BlockBuilder extends AbstractBlockBuilder {
12 | constructor (container, serviceName = 'BlockBuilder') {
13 | super(container, serviceName)
14 |
15 | debug(`☕️ ${serviceName} awake`)
16 | }
17 |
18 | /**
19 | * Returns an `AbstractBlock` child class instance
20 | * according to the nodeTypeName or an AbstractBlock as default.
21 | *
22 | * Comment out the default case if you don’t want a default block to be instantiated
23 | * for each block.
24 | *
25 | * @param {String} blockType
26 | * @return {AbstractBlock}
27 | */
28 | async getBlockInstance (blockType) {
29 | if (this.hasService(blockType)) {
30 | return this.getService(blockType).instance()
31 | }
32 |
33 | return null
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/services/CacheProvider.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2016, Ambroise Maupate
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is furnished
9 | * to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 | * IN THE SOFTWARE.
21 | *
22 | * @file CacheProvider.js
23 | * @author Ambroise Maupate
24 | * @author Adrien Scholaert
25 | */
26 |
27 | import AbstractService from '../abstracts/AbstractService'
28 | import { debug } from '../utils/Logger'
29 |
30 | /**
31 | * Cache provider class.
32 | *
33 | * This class stores Ajax response in memory.
34 | */
35 | export default class CacheProvider extends AbstractService {
36 | constructor (container, serviceName = 'CacheProvider') {
37 | super(container, serviceName)
38 |
39 | debug(`☕️ ${serviceName} awake`)
40 |
41 | this.data = {}
42 | }
43 |
44 | /**
45 | * @param {String} key
46 | * @return {Boolean}
47 | */
48 | exists (key) {
49 | return key in this.data
50 | }
51 |
52 | /**
53 | * @param {String} href
54 | * @return {Object}
55 | */
56 | get (href) {
57 | return this.data[href]
58 | }
59 |
60 | /**
61 | * @param {String} key
62 | * @param {Object} data
63 | * @return {CacheProvider} this
64 | */
65 | set (key, data) {
66 | this.data[key] = data
67 | return this
68 | }
69 |
70 | /**
71 | * Flush the cache
72 | */
73 | reset () {
74 | this.data = {}
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/services/Dom.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2017. Ambroise Maupate and Julien Blanchet
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is furnished
9 | * to do so, subject to the following conditions:
10 | * The above copyright notice and this permission notice shall be included in all
11 | * copies or substantial portions of the Software.
12 | *
13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19 | * IN THE SOFTWARE.
20 | *
21 | * Except as contained in this notice, the name of the ROADIZ shall not
22 | * be used in advertising or otherwise to promote the sale, use or other dealings
23 | * in this Software without prior written authorization from Ambroise Maupate and Julien Blanchet.
24 | *
25 | * @file Dom.js
26 | * @author Adrien Scholaert
27 | */
28 |
29 | import AbstractService from '../abstracts/AbstractService'
30 | import { debug } from '../utils/Logger'
31 | import Dispatcher from '../dispatcher/Dispatcher'
32 | import { AFTER_DOM_APPENDED } from '../types/EventTypes'
33 |
34 | /**
35 | * Class that is going to deal with DOM parsing/manipulation.
36 | */
37 | export default class Dom extends AbstractService {
38 | /**
39 | * Constructor.
40 | *
41 | * @param {Object} container
42 | * @param {String} serviceName
43 | */
44 | constructor (container, serviceName = 'Dom') {
45 | super(container, serviceName)
46 |
47 | debug(`☕️ ${serviceName} awake`)
48 |
49 | /**
50 | * Full HTML String of the current page.
51 | * By default is the innerHTML of the initial loaded page.
52 | *
53 | * Each time a new page is loaded, the value is the response of the ajax call.
54 | *
55 | * @type {String}
56 | * @default
57 | */
58 | this.currentHTML = document.documentElement.innerHTML
59 | }
60 |
61 | /**
62 | * Parse the responseText obtained from the ajax call.
63 | *
64 | * @param {String} responseText
65 | * @return {HTMLElement}
66 | */
67 | parseResponse (responseText) {
68 | this.currentHTML = responseText
69 |
70 | const wrapper = document.createElement('div')
71 | wrapper.innerHTML = responseText
72 |
73 | return this.getContainer(wrapper)
74 | }
75 |
76 | /**
77 | * Get the main wrapper by the ID `wrapperId`.
78 | *
79 | * @return {HTMLElement} element
80 | */
81 | getWrapper () {
82 | const wrapper = document.getElementById(this.getService('Config').wrapperId)
83 |
84 | if (!wrapper) {
85 | throw new Error('Starting Blocks: Wrapper not found!')
86 | }
87 |
88 | return wrapper
89 | }
90 |
91 | /**
92 | * Return node type.
93 | *
94 | * @param container
95 | * @returns {string}
96 | */
97 | getNodeType (container) {
98 | return container.getAttribute(this.getService('Config').objectTypeAttr)
99 | }
100 |
101 | /**
102 | * Get the container on the current DOM,
103 | * or from an HTMLElement passed via argument.
104 | *
105 | * @param {HTMLElement|null} element
106 | * @return {HTMLElement}
107 | */
108 | getContainer (element = null) {
109 | if (!element) { element = document.body }
110 |
111 | if (!element) {
112 | throw new Error('Starting Blocks: DOM not ready!')
113 | }
114 |
115 | const container = this.parseContainer(element)
116 |
117 | if (!container) {
118 | throw new Error(`Starting Blocks: container not found! Did you use at least
119 | one dom element with ".${this.getService('Config').pageClass}" class and "data-node-type" attribute?`)
120 | }
121 |
122 | return container
123 | }
124 |
125 | /**
126 | * Put the container on the page.
127 | *
128 | * @param {HTMLElement} element
129 | */
130 | putContainer (element) {
131 | element.style.visibility = 'hidden'
132 | const wrapper = this.getWrapper()
133 | wrapper.appendChild(element)
134 |
135 | // Dispatch an event
136 | Dispatcher.commit(AFTER_DOM_APPENDED, {
137 | element,
138 | currentHTML: this.getService('Dom').currentHTML
139 | })
140 | }
141 |
142 | /**
143 | * Get container selector.
144 | *
145 | * @param {HTMLElement} element
146 | * @return {HTMLElement} element
147 | */
148 | parseContainer (element) {
149 | return element.querySelector(`.${this.getService('Config').pageClass}[data-node-type]`)
150 | }
151 |
152 | /**
153 | * Update body attributes.
154 | *
155 | * @param {AbstractPage} page
156 | */
157 | updateBodyAttributes (page) {
158 | // Change body class and id
159 | if (page.name) {
160 | document.body.id = page.name
161 | document.body.classList.add(page.name)
162 | }
163 |
164 | document.body.classList.add(page.type)
165 |
166 | if (page.isHome) {
167 | document.body.setAttribute('data-is-home', '1')
168 | } else {
169 | document.body.setAttribute('data-is-home', '0')
170 | }
171 | }
172 |
173 | /**
174 | * Update page title.
175 | *
176 | * @param {AbstractPage} page
177 | */
178 | updatePageTitle (page) {
179 | if (page.metaTitle) {
180 | document.title = page.metaTitle
181 | }
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/src/services/History.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2016, Ambroise Maupate
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is furnished
9 | * to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 | * IN THE SOFTWARE.
21 | *
22 | * @file History.js
23 | * @author Adrien Scholaert
24 | */
25 |
26 | import AbstractService from '../abstracts/AbstractService'
27 | import { debug } from '../utils/Logger'
28 |
29 | /**
30 | * HistoryManager helps to keep track of the navigation.
31 | *
32 | * @type {Object}
33 | */
34 | export default class History extends AbstractService {
35 | constructor (container, serviceName = 'History') {
36 | super(container, serviceName)
37 |
38 | debug(`☕️ ${serviceName} awake`)
39 |
40 | /**
41 | * Keep track of the status in historic order.
42 | *
43 | * @readOnly
44 | * @type {Array}
45 | */
46 | this.history = []
47 | }
48 |
49 | /**
50 | * Add a new set of url and namespace.
51 | *
52 | * @param {String} url
53 | * @param {String} transitionName
54 | * @param {String} context (ajax, history)
55 | * @param {Object} data (optional data)
56 | *
57 | * @return {Object}
58 | */
59 | add (url, transitionName, context, data = {}) {
60 | const state = { url, transitionName, context, data }
61 | this.history.push(state)
62 | return state
63 | }
64 |
65 | /**
66 | * Return information about the current status.
67 | *
68 | * @return {Object}
69 | */
70 | currentStatus () {
71 | return this.history[this.history.length - 1]
72 | }
73 |
74 | /**
75 | * Return information about the previous status.
76 | *
77 | * @return {Object}
78 | */
79 | prevStatus () {
80 | const history = this.history
81 |
82 | if (history.length < 2) { return null }
83 |
84 | return history[history.length - 2]
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/services/PageBuilder.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file PageBuilder.js
3 | * @author Ambroise Maupate
4 | * @author Adrien Scholaert
5 | */
6 |
7 | import Dispatcher from '../dispatcher/Dispatcher'
8 | import {
9 | AFTER_PAGE_BOOT,
10 | BEFORE_SPLASHSCREEN_HIDE,
11 | AFTER_SPLASHSCREEN_HIDE
12 | } from '../types/EventTypes'
13 | import AbstractBootableService from '../abstracts/AbstractBootableService'
14 | import { debug } from '../utils/Logger'
15 |
16 | /**
17 | * PageBuilder.
18 | */
19 | export default class PageBuilder extends AbstractBootableService {
20 | constructor (container, serviceName = 'PageBuilder') {
21 | super(container, serviceName, ['Dom'])
22 |
23 | debug(`☕️ ${serviceName} awake`)
24 |
25 | if (!window.location.origin) {
26 | window.location.origin = window.location.protocol + '//' + window.location.host
27 | }
28 |
29 | /**
30 | * Page instance
31 | * @type {(AbstractPage|null)}
32 | */
33 | this.page = null
34 |
35 | // Bind methods
36 | this.buildPage = this.buildPage.bind(this)
37 | }
38 |
39 | boot () {
40 | super.boot()
41 |
42 | // Build first page with static context
43 | this.buildPage(this.getService('Dom').getContainer(), 'static')
44 | }
45 |
46 | /**
47 | * Build a new page instance.
48 | *
49 | * @param {HTMLElement} rootElement
50 | * @param {String} context
51 | * @returns {AbstractPage|null}
52 | */
53 | async buildPage (rootElement, context = 'ajax') {
54 | let nodeTypeName = this.getService('Dom').getNodeType(rootElement)
55 |
56 | if (this.hasService(nodeTypeName)) {
57 | this.page = this.getService(nodeTypeName).instance()
58 | } else {
59 | nodeTypeName = 'DefaultPage'
60 | this.page = this.getService('DefaultPage').instance()
61 | }
62 |
63 | // Set some values
64 | this.page.type = nodeTypeName
65 | this.page.context = context
66 | this.page.id = rootElement.id
67 | this.page.rootElement = rootElement
68 | this.page.name = rootElement.hasAttribute('data-node-name') ? rootElement.getAttribute('data-node-name') : ''
69 | this.page.metaTitle = rootElement.hasAttribute('data-meta-title') ? rootElement.getAttribute('data-meta-title') : ''
70 | this.page.isHome = rootElement.getAttribute('data-is-home') === '1'
71 |
72 | await this.page.init()
73 |
74 | // Dispatch an event to inform that the new page is ready
75 | Dispatcher.commit(AFTER_PAGE_BOOT, this.page)
76 |
77 | if (this.hasService('Splashscreen') && !this.getService('Splashscreen').splashscreenHidden) {
78 | Dispatcher.commit(BEFORE_SPLASHSCREEN_HIDE, this.page)
79 |
80 | this.getService('Splashscreen')
81 | .hide()
82 | .then(() => {
83 | Dispatcher.commit(AFTER_SPLASHSCREEN_HIDE, this.page)
84 | this.getService('Splashscreen').splashscreenHidden = true
85 | })
86 | }
87 |
88 | return this.page
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/services/Pjax.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2017, Ambroise Maupate
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is furnished
9 | * to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 | * IN THE SOFTWARE.
21 | *
22 | * @file Pjax.js
23 | * @author Adrien Scholaert
24 | */
25 |
26 | import Utils from '../utils/Utils'
27 | import Dispatcher from '../dispatcher/Dispatcher'
28 | import {
29 | CONTAINER_READY,
30 | AFTER_PAGE_LOAD,
31 | TRANSITION_START,
32 | TRANSITION_COMPLETE,
33 | BEFORE_PAGE_LOAD
34 | } from '../types/EventTypes'
35 | import AbstractBootableService from '../abstracts/AbstractBootableService'
36 | import DefaultTransition from '../transitions/DefaultTransition'
37 | import { debug } from '../utils/Logger'
38 |
39 | /**
40 | * Pjax.
41 | */
42 | export default class Pjax extends AbstractBootableService {
43 | constructor (container, serviceName = 'Pjax') {
44 | super(container, serviceName, ['Dom', 'Config', 'History', 'PageBuilder'])
45 |
46 | debug(`☕️ ${serviceName} awake`)
47 |
48 | /**
49 | * Indicate if there is an animation in progress.
50 | *
51 | * @readOnly
52 | * @type {Boolean}
53 | */
54 | this.transitionProgress = false
55 |
56 | /**
57 | * @type {(HTMLElement|null)}
58 | * The latest page content loaded
59 | */
60 | this.containerElement = null
61 |
62 | // Bind methods
63 | this.onNewPageLoaded = this.onNewPageLoaded.bind(this)
64 | this.onTransitionEnd = this.onTransitionEnd.bind(this)
65 | this.onLinkClick = this.onLinkClick.bind(this)
66 | this.onStateChange = this.onStateChange.bind(this)
67 | }
68 |
69 | /**
70 | * Init the events.
71 | *
72 | * @private
73 | */
74 | boot () {
75 | super.boot()
76 |
77 | const wrapper = this.getService('Dom').getWrapper()
78 | wrapper.setAttribute('aria-live', 'polite')
79 |
80 | this.currentState = this.getService('History').add(this.getCurrentUrl(), null, 'static')
81 |
82 | this.bindEvents()
83 | }
84 |
85 | /**
86 | * Attach event listeners.
87 | *
88 | * @private
89 | */
90 | bindEvents () {
91 | document.addEventListener('click', this.onLinkClick)
92 | window.addEventListener('popstate', this.onStateChange)
93 | }
94 |
95 | /**
96 | * Return the currentURL cleaned.
97 | *
98 | * @return {String} currentUrl
99 | */
100 | getCurrentUrl () {
101 | // TODO, clean from what? currenturl do not takes hash..
102 | return Utils.cleanLink(Utils.getCurrentUrl())
103 | }
104 |
105 | /**
106 | * Change the URL with push state and trigger the state change.
107 | *
108 | * @param {String} url
109 | * @param {String} transitionName
110 | * @param {HTMLElement} element The element
111 | * @param {Object} cursorPosition
112 | */
113 | goTo (url, transitionName, element, cursorPosition) {
114 | const currentPosition = window.scrollY
115 | window.history.pushState(null, null, url)
116 | window.scrollTo(0, currentPosition)
117 | this.onStateChange(transitionName, true, element, cursorPosition)
118 | }
119 |
120 | /**
121 | * Force the browser to go to a certain url.
122 | *
123 | * @param {String} url
124 | * @private
125 | */
126 | forceGoTo (url) {
127 | window.location = url
128 | }
129 |
130 | /**
131 | * Load an url, will start an ajax request or load from the cache.
132 | *
133 | * @private
134 | * @param {String} url
135 | * @return {Promise}
136 | */
137 | load (url) {
138 | const deferred = Utils.deferred()
139 |
140 | // Check cache
141 | let request = null
142 |
143 | if (this.hasService('CacheProvider')) {
144 | request = this.getService('CacheProvider').get(url)
145 | }
146 |
147 | // If no cache, make request
148 | if (!request) {
149 | request = Utils.request(url)
150 |
151 | // If cache provider, cache the request
152 | if (this.hasService('CacheProvider')) {
153 | this.getService('CacheProvider').set(url, request)
154 | }
155 | }
156 |
157 | // When data are loaded
158 | request
159 | .then(async data => {
160 | this.containerElement = this.getService('Dom').parseResponse(data)
161 |
162 | // Dispatch an event
163 | Dispatcher.commit(AFTER_PAGE_LOAD, {
164 | container: this.containerElement,
165 | currentHTML: this.getService('Dom').currentHTML
166 | })
167 |
168 | // Add new container to the DOM if manual DOM Append is disable
169 | if (!this.getService('Config').manualDomAppend) {
170 | this.getService('Dom').putContainer(this.containerElement)
171 |
172 | // Build page
173 | const page = await this.getService('PageBuilder').buildPage(this.containerElement)
174 |
175 | deferred.resolve(page)
176 | } else {
177 | deferred.resolve(null)
178 | }
179 | })
180 | .catch((err) => {
181 | console.error(err)
182 | this.forceGoTo(url)
183 | deferred.reject()
184 | })
185 |
186 | return deferred.promise
187 | }
188 |
189 | /**
190 | * Get the .href parameter out of a link element
191 | *
192 | * @private
193 | * @param {HTMLElement} el
194 | * @return {String|undefined} href
195 | */
196 | getHref (el) {
197 | if (!el) {
198 | return undefined
199 | }
200 |
201 | // Check if has a href and if it's a link element
202 | if (typeof el.href === 'string' && el.tagName.toUpperCase() === 'A') {
203 | return el.href
204 | }
205 |
206 | return undefined
207 | }
208 |
209 | /**
210 | * Get transition name from HTMLElement attribute (data-transition).
211 | *
212 | * @param {HTMLElement} el
213 | * @returns {String|undefined} The transition name
214 | */
215 | getTransitionName (el) {
216 | if (!el) {
217 | return null
218 | }
219 |
220 | if (el.getAttribute && typeof el.getAttribute('data-transition') === 'string') {
221 | return el.getAttribute('data-transition')
222 | }
223 |
224 | return null
225 | }
226 |
227 | /**
228 | * Callback called from click event.
229 | *
230 | * @private
231 | * @param {MouseEvent} evt
232 | */
233 | onLinkClick (evt) {
234 | /**
235 | * @type {HTMLElement|Node|EventTarget}
236 | */
237 | let el = evt.target
238 |
239 | // Go up in the node list until we
240 | // find something with an href
241 | while (el && !this.getHref(el)) {
242 | el = el.parentNode
243 | }
244 |
245 | if (this.preventCheck(evt, el)) {
246 | evt.preventDefault()
247 |
248 | this.linkHash = el.hash.split('#')[1]
249 |
250 | const href = this.getHref(el)
251 | const transitionName = this.getTransitionName(el)
252 | const cursorPosition = {
253 | x: evt.clientX,
254 | y: evt.clientY
255 | }
256 | this.goTo(href, transitionName, el, cursorPosition)
257 | }
258 | }
259 |
260 | /**
261 | * Determine if the link should be followed.
262 | *
263 | * @param {MouseEvent} evt
264 | * @param {HTMLElement} element
265 | * @return {Boolean}
266 | */
267 | preventCheck (evt, element) {
268 | if (!window.history.pushState) { return false }
269 |
270 | const href = this.getHref(element)
271 |
272 | // User
273 | if (!element || !href) { return false }
274 |
275 | // Middle click, cmd click, and ctrl click
276 | if (evt.which > 1 || evt.metaKey || evt.ctrlKey || evt.shiftKey || evt.altKey) { return false }
277 |
278 | // Ignore target with _blank target
279 | if (element.target && element.target === '_blank') { return false }
280 |
281 | // Check if it's the same domain
282 | if (window.location.protocol !== element.protocol || window.location.hostname !== element.hostname) { return false }
283 |
284 | // Check if the port is the same
285 | if (Utils.getPort() !== Utils.getPort(element.port)) { return false }
286 |
287 | // Ignore case when a hash is being tacked on the current URL
288 | // if (href.indexOf('#') > -1)
289 | // return false;
290 |
291 | // Ignore case where there is download attribute
292 | if (element.getAttribute && typeof element.getAttribute('download') === 'string') { return false }
293 |
294 | // In case you're trying to load the same page
295 | if (Utils.cleanLink(href) === Utils.cleanLink(window.location.href)) { return false }
296 |
297 | return !element.classList.contains(this.getService('Config').noAjaxLinkClass)
298 | }
299 |
300 | /**
301 | * Return a transition object.
302 | *
303 | * @param {object} prev historyManager
304 | * @param {object} current historyManager
305 | * @return {AbstractTransition} Transition object
306 | */
307 | getTransition (prev, current) {
308 | if (this.hasService('TransitionFactory')) {
309 | return this.getService('TransitionFactory').getTransition(prev, current)
310 | } else {
311 | return new DefaultTransition()
312 | }
313 | }
314 |
315 | /**
316 | * Method called after a 'popstate' or from .goTo().
317 | *
318 | * @private
319 | */
320 | onStateChange (transitionName = null, isAjax = false, el = null, cursorPosition = null) {
321 | const newUrl = this.getCurrentUrl()
322 |
323 | if (this.transitionProgress) { this.forceGoTo(newUrl) }
324 |
325 | if (this.getService('History').currentStatus().url === newUrl) { return false }
326 |
327 | // If transition name is a string, a link have been click
328 | // Otherwise back/forward buttons have been pressed
329 | if (typeof transitionName === 'string' || isAjax) {
330 | this.currentState = this.getService('History').add(newUrl, transitionName, 'ajax')
331 | } else {
332 | this.currentState = this.getService('History').add(newUrl, null, '_history')
333 | }
334 |
335 | // Dispatch an event to inform that the page is being load
336 | Dispatcher.commit(BEFORE_PAGE_LOAD, {
337 | currentStatus: this.getService('History').currentStatus(),
338 | prevStatus: this.getService('History').prevStatus()
339 | })
340 |
341 | // Load the page with the new url (promise is return)
342 | const newPagePromise = this.load(newUrl)
343 |
344 | // Get the page transition instance (from prev and current state)
345 | const transition = this.getTransition(
346 | this.getService('History').prevStatus(),
347 | this.getService('History').currentStatus()
348 | )
349 |
350 | this.transitionProgress = true
351 |
352 | // Dispatch an event that the transition is started
353 | Dispatcher.commit(TRANSITION_START, {
354 | transition: transition,
355 | currentStatus: this.getService('History').currentStatus(),
356 | prevStatus: this.getService('History').prevStatus()
357 | })
358 |
359 | // Start the transition (with the current page, and the new page load promise)
360 | const transitionPromise = transition.init(
361 | this.getService('PageBuilder').page,
362 | newPagePromise,
363 | el,
364 | cursorPosition
365 | )
366 |
367 | newPagePromise.then(this.onNewPageLoaded)
368 | transitionPromise.then(this.onTransitionEnd)
369 | }
370 |
371 | /**
372 | * Function called as soon the new page is ready.
373 | *
374 | * @private
375 | * @param {AbstractPage} page
376 | */
377 | onNewPageLoaded (page) {
378 | if (page) {
379 | const currentStatus = this.getService('History').currentStatus()
380 |
381 | // Update body attributes (class, id, data-attributes
382 | this.getService('Dom').updateBodyAttributes(page)
383 | // Update the page title
384 | this.getService('Dom').updatePageTitle(page)
385 | // Send google analytic data
386 | Utils.trackGoogleAnalytics()
387 |
388 | // Update the current state
389 | if (this.currentState && page) {
390 | if (!this.currentState.data.title && page.metaTitle) {
391 | this.currentState.data.title = page.metaTitle
392 | }
393 | }
394 |
395 | Dispatcher.commit(CONTAINER_READY, {
396 | currentStatus,
397 | prevStatus: this.getService('History').prevStatus(),
398 | currentHTML: this.getService('Dom').currentHTML,
399 | page
400 | })
401 | }
402 | }
403 |
404 | /**
405 | * Function called as soon the transition is finished.
406 | *
407 | * @private
408 | */
409 | onTransitionEnd () {
410 | this.transitionProgress = false
411 |
412 | if (this.linkHash) {
413 | window.location.hash = ''
414 | window.location.hash = this.linkHash
415 |
416 | this.linkHash = null
417 | }
418 |
419 | Dispatcher.commit(TRANSITION_COMPLETE, {
420 | currentStatus: this.getService('History').currentStatus(),
421 | prevStatus: this.getService('History').prevStatus()
422 | })
423 | }
424 | }
425 |
--------------------------------------------------------------------------------
/src/services/Prefetch.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2017, Ambroise Maupate
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is furnished
9 | * to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 | * IN THE SOFTWARE.
21 | *
22 | * @file Prefetch.js
23 | * @author Adrien Scholaert
24 | */
25 |
26 | import Utils from '../utils/Utils'
27 | import AbstractBootableService from '../abstracts/AbstractBootableService'
28 | import { debug } from '../utils/Logger'
29 |
30 | /**
31 | * Prefetch.
32 | *
33 | * @type {Object}
34 | */
35 | export default class Prefetch extends AbstractBootableService {
36 | constructor (container, serviceName = 'Prefetch') {
37 | super(container, serviceName, ['Pjax', 'Config'])
38 |
39 | debug(`☕️ ${serviceName} awake`)
40 | }
41 |
42 | boot () {
43 | super.boot()
44 |
45 | if (!window.history.pushState) {
46 | return false
47 | }
48 |
49 | document.body.addEventListener('mouseover', this.onLinkEnter.bind(this))
50 | document.body.addEventListener('touchstart', this.onLinkEnter.bind(this))
51 | }
52 |
53 | onLinkEnter (evt) {
54 | let el = evt.target
55 |
56 | while (el && !this.getService('Pjax').getHref(el)) {
57 | el = el.parentNode
58 | }
59 |
60 | if (!el || el.classList.contains(this.getService('Config').noPrefetchClass)) {
61 | return
62 | }
63 |
64 | let url = this.getService('Pjax').getHref(el)
65 |
66 | // Check if the link is eligible for Pjax
67 | if (this.getService('Pjax').preventCheck(evt, el)) {
68 | if (this.hasService('CacheProvider') && this.getService('CacheProvider').get(url)) {
69 | return
70 | }
71 |
72 | let xhr = Utils.request(url)
73 |
74 | if (this.hasService('CacheProvider')) {
75 | this.getService('CacheProvider').set(url, xhr)
76 | }
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/transitions/DefaultTransition.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2016, Ambroise Maupate
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is furnished
9 | * to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 | * IN THE SOFTWARE.
21 | *
22 | * @file DefaultTransition.js
23 | * @author Quentin Neyraud
24 | * @author Adrien Scholaert
25 | */
26 | import AbstractTransition from '../abstracts/AbstractTransition'
27 |
28 | /**
29 | * Default Transition. Show / Hide content.
30 | *
31 | * @extends {AbstractTransition}
32 | */
33 | export default class DefaultTransition extends AbstractTransition {
34 | start () {
35 | Promise.all([this.newPageLoading])
36 | .then(this.finish.bind(this))
37 | }
38 |
39 | finish () {
40 | document.body.scrollTop = 0
41 | this.done()
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/types/EventTypes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2016, Ambroise Maupate
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is furnished
9 | * to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 | * IN THE SOFTWARE.
21 | *
22 | * @file EventTypes.js
23 | * @author Ambroise Maupate
24 | * @author Adrien Scholaert
25 | */
26 |
27 | /**
28 | * Before initialize XHR request to load new page.
29 | *
30 | * @type {String}
31 | */
32 | export const BEFORE_PAGE_LOAD = 'SB_BEFORE_PAGE_LOAD'
33 |
34 | /**
35 | * After XHR request succeeded.
36 | *
37 | * @type {String}
38 | */
39 | export const AFTER_PAGE_LOAD = 'SB_AFTER_PAGE_LOAD'
40 |
41 | /**
42 | * After Dom service appended new page DOM to page-container.
43 | *
44 | * @type {String}
45 | */
46 | export const AFTER_DOM_APPENDED = 'SB_AFTER_DOM_APPENDED'
47 |
48 | /**
49 | * When new page container is ready.
50 | *
51 | * @type {String}
52 | */
53 | export const CONTAINER_READY = 'SB_CONTAINER_READY'
54 |
55 | /**
56 | * After PageBuilder create new page instance.
57 | *
58 | * @type {String}
59 | */
60 | export const AFTER_PAGE_BOOT = 'SB_AFTER_PAGE_BOOT'
61 |
62 | /**
63 | * Before page transition begin.
64 | *
65 | * @type {String}
66 | */
67 | export const TRANSITION_START = 'SB_TRANSITION_START'
68 |
69 | /**
70 | * After page transition completed.
71 | *
72 | * @type {String}
73 | */
74 | export const TRANSITION_COMPLETE = 'SB_TRANSITION_COMPLETE'
75 |
76 | /**
77 | * Before splashscreen begin to hide.
78 | *
79 | * @type {String}
80 | */
81 | export const BEFORE_SPLASHSCREEN_HIDE = 'SB_BEFORE_SPLASHSCREEN_HIDE'
82 |
83 | /**
84 | * When splashscreen start to hide.
85 | *
86 | * @type {String}
87 | */
88 | export const START_SPLASHSCREEN_HIDE = 'SB_START_SPLASHSCREEN_HIDE'
89 |
90 | /**
91 | * After splashscreen hiding animation.
92 | *
93 | * @type {String}
94 | */
95 | export const AFTER_SPLASHSCREEN_HIDE = 'SB_AFTER_SPLASHSCREEN_HIDE'
96 |
--------------------------------------------------------------------------------
/src/utils/BootstrapMedia.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright REZO ZERO 2016
3 | *
4 | *
5 | *
6 | * @file bootstrapMedia.js
7 | * @copyright REZO ZERO 2016
8 | * @author Ambroise Maupate
9 | * @author Adrien Scholaert
10 | */
11 |
12 | import Utils from './Utils'
13 |
14 | /**
15 | * Static class to get bootstrap breakpoints.
16 | */
17 | export default class BootstrapMedia {
18 | constructor () {
19 | // Values
20 | this.viewportSize = null
21 | this.breakpoints = {
22 | xs: 480,
23 | sm: 768,
24 | md: 992,
25 | lg: 1200,
26 | xl: 1920
27 | }
28 |
29 | // Binded methods
30 | this.setValues = this.setValues.bind(this)
31 |
32 | // Init
33 | this.init()
34 | }
35 |
36 | init () {
37 | window.addEventListener('resize', this.setValues)
38 | this.setValues()
39 | }
40 |
41 | setValues () {
42 | this.viewportSize = Utils.getViewportSize()
43 | }
44 |
45 | resize () {
46 | this.setValues()
47 | }
48 |
49 | isMin (breakpoint) {
50 | if (!this.breakpoints[breakpoint]) {
51 | const errorMessage = `Breakpoint '${breakpoint}' do not exist`
52 | console.error(errorMessage)
53 | throw new Error(errorMessage)
54 | }
55 |
56 | return this.viewportSize.width >= this.breakpoints[breakpoint]
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/utils/Logger.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017, Rezo Zero
3 | *
4 | * @file debug.js
5 | * @author Adrien Scholaert
6 | */
7 |
8 | export function log (...args) {
9 | if (window.startingBlocksDebugLevel === 1) {
10 | console.log('[SB]', ...args)
11 | }
12 | }
13 |
14 | export function debug (message, color = '') {
15 | if (window.startingBlocksDebugLevel === 1) {
16 | console.debug(`%c[SB] %c${message}`, 'color:#749f73', 'color:debug', color)
17 | }
18 | }
19 |
20 | export function info (...args) {
21 | if (window.startingBlocksDebugLevel === 1) {
22 | console.info('[SB]', ...args)
23 | }
24 | }
25 |
26 | export function warn (...args) {
27 | if (window.startingBlocksDebugLevel === 1) {
28 | console.warn('[SB]', ...args)
29 | }
30 | }
31 |
32 | export function error (...args) {
33 | if (window.startingBlocksDebugLevel === 1) {
34 | console.error('[SB]', ...args)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/utils/Scroll.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2016, Ambroise Maupate
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is furnished
9 | * to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 | * IN THE SOFTWARE.
21 | *
22 | * @file Scroll.js
23 | * @author Ambroise Maupate
24 | */
25 |
26 | /**
27 | * @static
28 | */
29 | export default class Scroll {
30 | /**
31 | *
32 | * @param e
33 | * @private
34 | */
35 | static _preventDefault (e) {
36 | e = e || window.event
37 | if (e.preventDefault) { e.preventDefault() }
38 | e.returnValue = false
39 | }
40 |
41 | /**
42 | *
43 | * @param e
44 | * @private
45 | */
46 | static _keydown (e) {
47 | // left: 37, up: 38, right: 39, down: 40, spacebar: 32, pageup: 33, pagedown: 34, end: 35, home: 36
48 | let keys = [37, 38, 39, 40, 33, 34, 35]
49 | for (let i = keys.length; i--;) {
50 | if (e.keyCode === keys[i]) {
51 | Scroll._preventDefault(e)
52 | return
53 | }
54 | }
55 | }
56 |
57 | /**
58 | *
59 | * @param e
60 | * @private
61 | */
62 | static _wheel (e) {
63 | Scroll._preventDefault(e)
64 | }
65 |
66 | /**
67 | * Disable scroll.
68 | *
69 | * @return {void}
70 | */
71 | static disable () {
72 | if (window.addEventListener) {
73 | window.addEventListener('DOMMouseScroll', Scroll._wheel, false)
74 | }
75 | window.onmousewheel = document.onmousewheel = Scroll._wheel
76 | document.onkeydown = Scroll._keydown
77 | }
78 |
79 | /**
80 | * Enable scroll again.
81 | *
82 | * @return {void}
83 | */
84 | static enable () {
85 | if (window.removeEventListener) {
86 | window.removeEventListener('DOMMouseScroll', Scroll._wheel, false)
87 | }
88 | window.onmousewheel = document.onmousewheel = document.onkeydown = null
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/utils/Utils.js:
--------------------------------------------------------------------------------
1 | import { debug } from './Logger'
2 |
3 | /**
4 | * Utils class
5 | */
6 | export default class Utils {
7 | /**
8 | * @param {String} str
9 | * @return {String}
10 | */
11 | static stripTrailingSlash (str) {
12 | if (str.substr(-1) === '/') {
13 | return str.substr(0, str.length - 1)
14 | }
15 | return str
16 | }
17 |
18 | /**
19 | * Get port
20 | *
21 | * @param p
22 | * @returns {*}
23 | */
24 | static getPort (p) {
25 | const port = typeof p !== 'undefined' ? p : window.location.port
26 | const protocol = window.location.protocol
27 |
28 | if (port !== '') { return parseInt(port) }
29 | if (protocol === 'http:') { return 80 }
30 | if (protocol === 'https:') { return 443 }
31 | }
32 |
33 | static cleanLink (url) {
34 | return url.replace(/#.*/, '')
35 | }
36 |
37 | /**
38 | * Get current url
39 | *
40 | * @returns {string}
41 | */
42 | static getCurrentUrl () {
43 | return window.location.protocol + '//' +
44 | window.location.host +
45 | window.location.pathname +
46 | window.location.search
47 | }
48 |
49 | /**
50 | * Request timeout (in ms)
51 | *
52 | * @returns {number}
53 | */
54 | static requestTimeout () {
55 | return 10000
56 | }
57 |
58 | /**
59 | * Start a fetch request
60 | *
61 | * @param {String} url
62 | * @return {Promise}
63 | */
64 | static request (url) {
65 | const dfd = Utils.deferred()
66 | const timeout = window.setTimeout(() => {
67 | window.clearTimeout(timeout)
68 | dfd.reject('timeout!')
69 | }, Utils.requestTimeout())
70 |
71 | const headers = new window.Headers()
72 | headers.append('X-Starting-Blocks', 'yes')
73 | headers.append('X-Allow-Partial', 'yes')
74 | headers.append('X-Requested-With', 'XMLHttpRequest')
75 |
76 | window.fetch(url, {
77 | method: 'GET',
78 | headers: headers,
79 | cache: 'default',
80 | credentials: 'same-origin'
81 | }).then(res => {
82 | window.clearTimeout(timeout)
83 |
84 | if (res.status >= 200 && res.status < 300) {
85 | return dfd.resolve(res.text())
86 | }
87 |
88 | const err = new Error(res.statusText || res.status)
89 | return dfd.reject(err)
90 | }).catch(err => {
91 | window.clearTimeout(timeout)
92 | dfd.reject(err)
93 | })
94 |
95 | return dfd.promise
96 | }
97 |
98 | /**
99 | * Log credits to console for code lovers.
100 | *
101 | * @param {String} siteName
102 | * @param {String} bgColor
103 | * @param {Array} creditsList
104 | * @param {Array} thanksList
105 | * @param {String} textColor (optional)
106 | */
107 | static logCredits (siteName, bgColor, creditsList, thanksList, textColor) {
108 | let color = '#fff'
109 | if (typeof textColor !== 'undefined') color = textColor
110 |
111 | console.log('%c ', 'font-size:3px;')
112 | console.log('%c' + siteName, 'background:' + bgColor + '; color: ' + color + '; font-size:14px; padding:5px 10px;')
113 | console.log('%c ', 'font-size:3px;')
114 |
115 | if (creditsList !== null) {
116 | let creditsLength = creditsList.length
117 | if (creditsLength) {
118 | for (let indexCredit = 0; indexCredit < creditsLength; indexCredit++) {
119 | console.log(creditsList[indexCredit].name + ' - ' + creditsList[indexCredit].website)
120 | }
121 | }
122 | }
123 |
124 | if (thanksList !== null) {
125 | let thanksLength = thanksList.length
126 | if (thanksLength) {
127 | console.log('-')
128 | console.log('Thanks to')
129 | for (let indexThanks = 0; indexThanks < thanksLength; indexThanks++) {
130 | console.log(thanksList[indexThanks].name + ' (' + thanksList[indexThanks].website + ')')
131 | }
132 | }
133 | }
134 |
135 | console.log('-')
136 | console.log(' ')
137 | }
138 |
139 | /**
140 | * Get random number.
141 | *
142 | * @param {Number} min [min value]
143 | * @param {Number} max [max value]
144 | * @param {Number} decimal
145 | * @return {Number}
146 | */
147 | static getRandomNumber (min, max, decimal) {
148 | const result = Math.random() * (max - min) + min
149 |
150 | if (typeof decimal !== 'undefined') {
151 | return Number(result.toFixed(decimal))
152 | } else return result
153 | }
154 |
155 | /**
156 | * Get random integer.
157 | *
158 | * @param {Number} min [min value]
159 | * @param {Number} max [max value]
160 | * @return {Number}
161 | */
162 | static getRandomInt (min, max) {
163 | return Math.floor(Math.random() * (max - min + 1)) + min
164 | }
165 |
166 | /**
167 | * Send a GA page view event when context is AJAX.
168 | */
169 | static trackGoogleAnalytics () {
170 | if (typeof window.ga !== 'undefined') {
171 | debug('🚩 Push Analytics for: ' + window.location.pathname)
172 | window.ga('send', 'pageview', {
173 | 'page': window.location.pathname,
174 | 'title': document.title
175 | })
176 | }
177 | }
178 |
179 | /**
180 | * Match CSS media queries and JavaScript window width.
181 | *
182 | * @see http://stackoverflow.com/a/11310353
183 | * @return {Object}
184 | */
185 | static getViewportSize () {
186 | let e = window
187 | let a = 'inner'
188 | if (!('innerWidth' in window)) {
189 | a = 'client'
190 | e = document.documentElement || document.body
191 | }
192 | return { width: e[ a + 'Width' ], height: e[ a + 'Height' ] }
193 | }
194 |
195 | /**
196 | * Get a css property with the vendor prefix.
197 | *
198 | * @param {String} property the css property
199 | * @return {String} the prefixed property
200 | */
201 | static prefixProperty (property) {
202 | const prefixes = ['', 'ms', 'Webkit', 'Moz', 'O']
203 | const numPrefixes = prefixes.length
204 | const tmp = document.createElement('div')
205 |
206 | for (let i = 0; i < numPrefixes; i++) {
207 | let prefix = prefixes[i]
208 | property = prefix === '' ? property : property.charAt(0).toUpperCase() + property.substring(1).toLowerCase()
209 | const prop = prefix + property
210 |
211 | if (typeof tmp.style[prop] !== 'undefined') {
212 | return prop
213 | }
214 | }
215 | }
216 |
217 | /**
218 | * Gets normalized ratio of value inside range.
219 | *
220 | * from https://github.com/mout/mout/blob/master/src/math/norm.js
221 | *
222 | * @param {Number} val
223 | * @param {Number} min
224 | * @param {Number} max
225 | * @return {Number}
226 | */
227 | static getNormRatio (val, min, max) {
228 | if (val < min) return 0
229 | if (val > max) return 1
230 |
231 | return val === max ? 1 : (val - min) / (max - min)
232 | }
233 |
234 | /**
235 | * Return a new "Deferred" object
236 | * https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm/Deferred
237 | *
238 | * @return {Deferred}
239 | */
240 | static deferred () {
241 | return new function () {
242 | this.resolve = null
243 | this.reject = null
244 |
245 | this.promise = new Promise((resolve, reject) => {
246 | this.resolve = resolve
247 | this.reject = reject
248 | })
249 | }()
250 | }
251 | }
252 |
--------------------------------------------------------------------------------
/src/utils/debounce.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns a function, that, as long as it continues to be invoked, will not
3 | * be triggered.
4 | *
5 | * The function will be called after it stops being called for
6 | * N milliseconds. If `immediate` is passed, trigger the function on the
7 | * leading edge, instead of the trailing.
8 | *
9 | * @see http://davidwalsh.name/javascript-debounce-function
10 | * @param {Function} func [function to debounce]
11 | * @param {Number} wait [time to wait]
12 | * @param {Boolean} immediate []
13 | */
14 | export default function debounce (func, wait, immediate) {
15 | let timeout
16 | return function () {
17 | let context = this
18 | let args = arguments
19 | let later = function () {
20 | timeout = null
21 | if (!immediate) func.apply(context, args)
22 | }
23 | const callNow = immediate && !timeout
24 | clearTimeout(timeout)
25 | timeout = setTimeout(later, wait)
26 | if (callNow) func.apply(context, args)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/utils/gaTrackErrors.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Method to track JS errors if your Google Analytics account.
3 | */
4 | export default function gaTrackErrors () {
5 | if (typeof ga !== 'undefined') {
6 | // Pure JavaScript errors handler
7 | window.addEventListener('error', function (err) {
8 | const lineAndColumnInfo = err.colno ? ' line:' + err.lineno + ', column:' + err.colno : ' line:' + err.lineno
9 | window.ga(
10 | 'send',
11 | 'event',
12 | 'JavaScript Error',
13 | err.message,
14 | err.filename + lineAndColumnInfo + ' -> ' + navigator.userAgent,
15 | 0,
16 | true
17 | )
18 | })
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/utils/polyfills.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2016, Ambroise Maupate
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is furnished
9 | * to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 | * IN THE SOFTWARE.
21 | *
22 | * @file polyfills.js
23 | * @author Ambroise Maupate
24 | */
25 |
26 | /**
27 | * Execute some polyfill for older and crappy browsers.
28 | *
29 | * - window.requestAnimFrame
30 | * - window.cancelAnimFrame
31 | * - Avoid `console` errors in browsers that lack a console.
32 | */
33 | export default function polyfills () {
34 | window.requestAnimFrame = (function () {
35 | return (
36 | window.requestAnimationFrame ||
37 | window.webkitRequestAnimationFrame ||
38 | window.mozRequestAnimationFrame ||
39 | window.oRequestAnimationFrame ||
40 | window.msRequestAnimationFrame ||
41 | function (/* function */ callback) {
42 | window.setTimeout(callback, 1000 / 60) // 60fps
43 | }
44 | )
45 | }())
46 |
47 | window.cancelAnimFrame = (function () {
48 | return (
49 | window.cancelAnimationFrame ||
50 | window.webkitCancelAnimationFrame ||
51 | window.mozCancelAnimationFrame ||
52 | window.oCancelAnimationFrame ||
53 | window.msCancelAnimationFrame ||
54 | function (id) {
55 | window.clearTimeout(id)
56 | }
57 | )
58 | }())
59 |
60 | /*
61 | * Avoid `console` errors in browsers that lack a console.
62 | * @return {[type]} [description]
63 | */
64 | {
65 | let method
66 | const noop = () => {}
67 | const methods = [
68 | 'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error',
69 | 'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log',
70 | 'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd',
71 | 'timeStamp', 'trace', 'warn'
72 | ]
73 | let length = methods.length
74 | const console = (window.console = window.console || {})
75 |
76 | while (length--) {
77 | method = methods[length]
78 |
79 | // Only stub undefined methods.
80 | if (!console[method]) {
81 | console[method] = noop
82 | }
83 | }
84 | }
85 |
86 | /**
87 | * Array.from polyfill
88 | */
89 | if (!Array.from) {
90 | Array.from = function (object) {
91 | 'use strict'
92 | return [].slice.call(object)
93 | }
94 | }
95 |
96 | /**
97 | * Array.find polyfill
98 | */
99 | if (!Array.prototype.find) {
100 | // eslint-disable-next-line no-extend-native
101 | Object.defineProperty(Array.prototype, 'find', {
102 | value: function (predicate) {
103 | if (this === null) {
104 | throw new TypeError('Array.prototype.find called on null or undefined')
105 | }
106 |
107 | if (typeof predicate !== 'function') {
108 | throw new TypeError('predicate must be a function')
109 | }
110 |
111 | let list = Object(this)
112 | let length = list.length >>> 0
113 | let thisArg = arguments[1]
114 | let value
115 |
116 | for (let i = 0; i < length; i++) {
117 | value = list[i]
118 | if (predicate.call(thisArg, value, i, list)) {
119 | return value
120 | }
121 | }
122 |
123 | return undefined
124 | }
125 | })
126 | }
127 |
128 | /**
129 | * FindIndex polyfill
130 | */
131 | if (!Array.prototype.findIndex) {
132 | // eslint-disable-next-line no-extend-native
133 | Object.defineProperty(Array.prototype, 'findIndex', {
134 | value: function (predicate) {
135 | // 1. Let O be ? ToObject(this value).
136 | if (this === null) {
137 | throw new TypeError('"this" is null or not defined')
138 | }
139 |
140 | let o = Object(this)
141 |
142 | // 2. Let len be ? ToLength(? Get(O, "length")).
143 | let len = o.length >>> 0
144 |
145 | // 3. If IsCallable(predicate) is false, throw a TypeError exception.
146 | if (typeof predicate !== 'function') {
147 | throw new TypeError('predicate must be a function')
148 | }
149 |
150 | // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
151 | let thisArg = arguments[1]
152 |
153 | // 5. Let k be 0.
154 | let k = 0
155 |
156 | // 6. Repeat, while k < len
157 | while (k < len) {
158 | // a. Let Pk be ! ToString(k).
159 | // b. Let kValue be ? Get(O, Pk).
160 | // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)).
161 | // d. If testResult is true, return k.
162 | let kValue = o[k]
163 | if (predicate.call(thisArg, kValue, k, o)) {
164 | return k
165 | }
166 | // e. Increase k by 1.
167 | k++
168 | }
169 |
170 | // 7. Return -1.
171 | return -1
172 | }
173 | })
174 | }
175 |
176 | /**
177 | * Custom event support for IE
178 | */
179 | (function () {
180 | if (typeof window.CustomEvent === 'function') return false
181 |
182 | function CustomEvent (event, params) {
183 | params = params || { bubbles: false, cancelable: false, detail: undefined }
184 | let evt = document.createEvent('CustomEvent')
185 | evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail)
186 | return evt
187 | }
188 |
189 | CustomEvent.prototype = window.Event.prototype
190 |
191 | window.CustomEvent = CustomEvent
192 | })()
193 | }
194 |
--------------------------------------------------------------------------------
/webpack.config.babel.js:
--------------------------------------------------------------------------------
1 | import getConfig from './examples/webpack/config'
2 | import getWebpackConfig from './examples/webpack/build'
3 |
4 | module.exports = getWebpackConfig(getConfig())
5 |
--------------------------------------------------------------------------------