!!!! Work in progress, contains bugs !!!
Easily create block based websites!
The block system uses the vue-transition-component to handle all component transitions. If you want to read more about this see the documentation. All blocks are transitioned when they enter the viewport. This is done using the seng-scroll-tracker.
All examples below are based on the vue-skeleton by hjeti.
yarn add vue-block-system
npm i -S vue-block-system
The repository contains a demo setup for the block system. It's based on the 0.3.1 version of the vue-skeleton. Since the block system is a plugin it should still work with newer versions of the skeleton. You can preview the demo either downloading the demo folder and running yarn
to install all dependencies and then run yarn dev
to start the dev server.
To use the block system there are a couple of steps to follow:
The block system uses mixins to extend the desired functionality. Extending the data could be a manual thing by copy and pasting the extends and creating the transition controllers manually. But to make it a little bit easier you should use the seng-generator. If you followed the steps as described in the seng-generator install instructions you can follow these steps to add the custom templates for generating blocks and content pages!
Open the terminal and change directory to the root of your project. After you've changed the working directory you can run the following:
sg init
After this you can modify the seng-generator configuration. The thing we want to change here is the template path so when it comes up you can modify it to be the following:
./template,./node_modules/vue-block-system/template
After configuring the seng-generator you have to generate your vue page that will be used to output the pages. To do this we will use the seng-generator. So change directory to the root of you project and run the following:
sg wizard
This will give you a couple of template options, choose the that is labeled: block-content-page. The seng-generator will now prompt you with a name for your new page, I would something abstract like:
ContentPage
You now need to add the new page to the vue router. You can open the src/router/routes.js
file and add the new page to the object
import ContentPage from 'page/ContentPage';
...
{
path: Pages.CONTENT_PAGE,
component: ContentPage,
name: PageNames.CONTENT_PAGE,
props: { componentId: PageNames.CONTENT_PAGE },
}
...
Make sure the path for the new page is set to * when you open the src/data/enum/Pages.js
file.
export default {
CONTENT_PAGE: '*',
};
If you open op the src/page/ContentPage/ContentPage.vue
file you can see where the magic happens. here you can disable the debug label if you want to.
To start using the block system you have to enable the Vue plugin in the src/control/startUp.js
file
import BlockSystem from 'vue-block-system';
import block from 'block';
...
Vue.use(BlockSystem, {
store,
block,
config: {
api: {
pageCall: 'static/api/page/{page}.json',
initCall: 'static/api/init.json',
layoutCache: true,
},
debugLabelStyling: {
backgroundColor: 'blue',
},
},
});
...
The store option is the reference to the vuex store.
The block option is the collection of blocks components that can be used on the page. Create a folder called block in the root of your applications src directory (src/block
). This file should contain an index.js
file that exports the block components just like any other component.
import BlockBar from './BlockBar';
import BlockFoo from './BlockFoo';
export default {
BlockBar,
BlockFoo,
};
The config object contains the configuration for the block-system
The block system uses axios to do XHR requests, there are two types of requests that will be made. The first one is the initCall
, this call should return at all the basic information that is required for the site to work.
By default every pageCall is cached, this can be disabled by changing the boolean to false.
{
"data": {
"routes": {
"landing": "/home",
"notFound": "/page-not-found"
}
}
}
After the init call is completed the block-system will try and load the page layout using the pageCall
. The response should be in the following stucture.
{
"statusCode": 200,
"data": {
"title": "Home",
"disableCache": false,
"data": {
"headerTheme": "red"
},
"blocks": [
{
"id": "BlockFoo",
"data": {}
},
{
"id": "BlockBar",
"data": {}
}
]
}
}
As you can see the root contains the main information about the page. The title being the title displayed in the tab-bar, the disableCache can be set to false to disable caching of this specific layout(default is set to true) and the data is extra page related data that you might need on page level coding. After the data the array of blocks is provided and as you can see blocks can be recursively nested. You can see more about this on the block example section
To make sure the block system is ready there is a promise available that will be resolved when the init call is completed. You can use the promise Vue.blockSystemReady
to check if the system is ready. Simply add it to the Startup.js
in the Promise.all[]
check
...
// Add async methods to the Promise.all array
return Promise.all([
Vue.blockSystemReady,
configManager.getVariable(VariableNames.LOCALE_ENABLED) ? waitForLocale(store) : Promise.resolve(),
]);
...
Since your site might have a lot of different blocks nested within each other it might be usefull to display their name so you can easily track them in your block folder. Therefore the debug label was added, by default the debug label is located at the top left corner in red with white text. If you want to change any of this styling you can use this object to add all your CSS styling.
Creating blocks is just as easy as generating the content page, just run
sg wizard
from the root of your project. This will give you a couple of template options, choose the one that is named: block-component and choose your desired name, I would suggest starting blocks with the Block
prefix.
Note: The first time you generate a button you need to make sure src/component/block
exists. If it doesn't you'll have to create it manually or run the seng enerator in forced mode
Note 2: make sure to add the newly generated block to the src/block/index.js
When a block is created you get a [blockNam]Data.js
file in your block folder, you should use this file to predefine all the data that your block will receive from the backend in the page call. This typing is done using VueTypes which is included in the vue-skeleton. Typing your data is a way to avoid data mismatches with the backend and for generating documentation.
import VueTypes from 'vue-types';
import { ButtonType, LinkType } from 'vue-block-system';
export default {
heading: VueTypes.string.isRequired,
link: VueTypes.shape({
label: VueTypes.string.isRequired,
title: VueTypes.string.isRequired,
type: VueTypes.oneOf([
ButtonType.ACTION,
ButtonType.LINK,
]),
link: VueTypes.shape({
type: VueTypes.oneOf([
LinkType.INTERNAL,
LinkType.EXTERNAL,
LinkType.EXTERNAL_BLANK,
]).isRequired,
target: VueTypes.string.isRequired,
}),
}),
};
Beside the basic usage there are a couple of extra features added which make it faster to build block websites.
You can nest blocks within blocks in two different ways. One would be an array of blocks which could be anything. Another way would be an object with keys as the block id and the data as the value
When nesting blocks with an array you must provide an array with blocks like shown below, in this case the BlockBar has a child block which is named BlockFoo. When nesting blocks within other blocks while using an array you cannot type the contents of the array since its dynamic, when you want to force certain blocks as a child you you should use the object method of nesting blocks.
{
"statusCode": 200,
"data": {
"title": "Home",
"data": {},
"blocks": [
{
"id": "BlockBar",
"data": {
"blocks": [
{
"id": "BlockFoo",
"data": {}
}
]
}
}
]
}
}
You should update the BlockBarData.js
file to let it expect blocks as data, since it's an array it could be anything so we can use the following:
import VueTypes from 'vue-types';
export default {
blocks: VueTypes.array,
};
To make sure the BlockFoo is dynamically rendered within BlockBar you have to add the following piece of code to the blockBar template file:
<component
v-for="(block, index) in data.blocks"
@isReady="handleBlockComponentReady"
:scrollId="block.scrollId"
:data="block.data"
:debugLabel="true"
:is="block.id"
:componentId="block.id + '.' + block.blockIndex"
:key="index" />
This will make sure the data is passed to the new block, all the callback methods are properly set and the right component is loaded. Everything should be pretty much plug and play!
When nesting blocks within an object you must provide an object with blocks like shown below, in this case the BlockBar has a child block which is named BlockFoo
{
"statusCode": 200,
"data": {
"title": "Home",
"data": {},
"blocks": [
{
"id": "BlockBar",
"data": {
"blocks": {
"BlockFoo": {
"data": {}
}
}
}
}
]
}
}
You should update the BlockBarData.js
file to let it expect blocks as data, since it's an object you could use the following:
import VueTypes from 'vue-types';
export default {
blocks: VueTypes.shape({
BlockBar: VueTypes.shape({
blockIndex: VueTypes.number,
data: VueTypes.object
});
}),
};
Note: The blockIndex has to be added when we use object style of nesting blocks
To make sure the BlockFoo is dynamically rendered within BlockBar you have to add the following piece of code to the blockBar template file:
<component
v-for="(block, key) in data.blocks"
@isReady="handleBlockComponentReady"
:class="$style[camelCase(key)]"
:scrollId="block.scrollId"
:data="block.data"
:is="key"
:componentId="key + '.' + block.blockIndex"
:key="key" />
This will make sure the data is passed to the new block, all the callback methods are properly set and the right component is loaded. Everything should be pretty much plug and play!
Buttons are pretty common in fancy websites to make sure you keep them structured and in the same directory I've added a template for buttons.
Once you've completed step 1 the button-component should be visible once you run
sg wizard
Note: The first time you generate a button you need to make sure src/component/button
exists. If it doesn't you'll have to create it manually or run the seng enerator in forced mode
When creating a button you need to provide a couple of props:
- type
- Type:
LinkType
- Required:
true
- Type:
- label
- Type:
string
- Required:
true
- Type:
- link
- Type
ILink
- required:
false
- the link is only required if the type is set to LinkType.LINK
- Type
Action example:
<ButtonFoo
label="Click me"
:type="ButtonType.ACTION"
@click="handleClick"></ButtonFoo>
Link example:
<ButtonFoo
label="Click me"
:type="ButtonType.LINK"
:link="{
type: LinkType.INTERNAL,
target: 'path/to/page',
title: 'Click me',
}"></ButtonFoo>
The vue-router offers a couple of in-component guards, these guards are used for detecting page changes and updating the current layout.
If you want to hijack the before route update you can use the beforeRouteUpdate
method to prepend your application specific code (for example: adding a full page mask to do pretty page transitions).
export default {
name: 'HomePage',
extends: AbstractContentPageComponent,
beforeRouteUpdate(to, from, next) {
// Add your awesome project specific code here.
console.log('Awesome!');
// When you are done, you should call the next method!
next();
},
};
After a route change is completed a method is triggered, this can be used to hide your full page mask or trigger page tracking code.
export default {
name: 'HomePage',
extends: AbstractContentPageComponent,
methods: {
handleRouteChangeComplete() {
// Route change is completed.
// This means all new blocks are registered and in the DOM.
},
},
};
The init call should contain the routes object, this object defines the default landing route and the not found route. Sometimes you might want to add custom data to this init call that is project specific. This can be done by subscribing to the init mutation and passing along your data. Your example init call response could look like this:
{
"statusCode": 200,
"data": {
"routes": {
"landing": "/home",
"notFound": "/page-not-found"
},
"user": {
"firstName": "John",
"lastName": "Doe"
}
}
}
In the src/control/startUp.js
file you can add the following piece of code before the vue-block-system plugin is initialized:
...
// Subscribe to the mutation so we can store the init data in other stores as well!
const unSubscribe = store.subscribe((mutation) => {
if (mutation.type === 'init/setData') {
// un-subscribe after we received the setData mutation
unSubscribe();
// commit the data to the desired stores
store.commit(`user/${SET_USER}`, mutation.payload.user);
}
});
...
In order to build vue-block-system, ensure that you have Git and Node.js installed.
Clone a copy of the repo:
git clone https://github.com/larsvanbraam/vue-block-system.git
Change to the vue-block-system directory:
cd vue-block-system
Install dev dependencies:
yarn
Use one of the following main scripts:
yarn build # build this project
yarn dev # run dev-watch mode, serving example/index.html in the browser
yarn generate # generate all artifacts (compiles ts, webpack, docs and coverage)
yarn test:unit # run the unit tests
yarn validate # runs validation scripts, including test, lint and coverage check
yarn lint # run tslint on this project
yarn doc # generate typedoc documentation
When installing this module, it adds a pre-push hook, that runs the validate
script before committing, so you can be sure that everything checks out.
If you want to create the distribution files yourself, you can run the
build-dist
script, and the following files will get generated in the
dist
folder:
- /dist/vue-block-system.js: bundled with webpack, can be loaded from
a script tag, available as
window.SengScrollTracker
- /dist/vue-block-system.min.js: same as above, but minified
- /dist/vue-block-system-amd.js: bundled with webpack, can be used with e.g. requirejs
- /dist/vue-block-system-commonjs.js: bundled with webpack, can be used in systems that support commonjs, but you should just use npm
- /dist/vue-block-system-umd.js: bundled with webpack, works in the browser, with requirejs, and in a commonjs system
- /dist/vue-block-system-umd.min.js: same as above, but minified
- /dist/vue-block-system-system.js: bundled with typescript, can be used in systems that support systemjs
- /dist/vue-block-system-es6.zip: transpiled with typescript, only types are removed from the source files
View AUTHORS.md
View CONTRIBUTING.md
MIT © Lars van Braam