npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

starting-blocks

v5.0.7

Published

A JS page and blocks framework written in ES6 to go along Roadiz CMS or any other great CMS.

Downloads

28

Readme

Starting Blocks

Starting Blocks is a framework for ajax page, transitions and blocks written in vanilla JS, made by Rezo Zero.

npm npm Build Status

Features

  • Pjax : fetch pages and handling History state
  • Transition manager : enhance your website with custom page transitions
  • Page/Blocks pattern : build dynamic and interactive sections (map, slideshow, Ajax forms, canvas…)

And more...

  • In view detection
  • Splashscreen
  • Prefetch
  • Cache

Install with Yarn or NPM

yarn add starting-blocks
npm install starting-blocks --save

Usage

Starting Blocks is bundled with NPM in order to use native ES6 import syntax. We highly recommend to use a module bundler and to write your code in ES6 syntax. If you use a bundler like 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.

Minimal configuration :

import StartingBlocks from 'starting-blocks'
import HomePage from './pages/HomePage'
import UsersBlock from './blocks/UsersBlock'

// Create a new Starting Blocks instance
const startingBlocks = new StartingBlocks({
    // ...options
})

// For each page or block sections: you must map the service name 
// with the data-node-type attribute (see DOM Structure section).
// «c» parameter stands for Starting-Blocks service container

// Register page services factory
startingBlocks.instanceFactory('HomePage', c => {
    return new HomePage(c)
})

// Register block services factory
startingBlocks.instanceFactory('UsersBlock', c => {
    return new UsersBlock(c)
})

// 🚀 Boot the whole thing
startingBlocks.boot()

Services

Starting Blocks use a dependency injection container to delegate object creation and handle dependencies. You can use or provide these standard services to enhance your website :

| Service name | Init type | Ready to use | Dependencies | Description | --- | --- | --- | --- | --- | | PjaxbootableProvider | true | History | Enable Ajax navigation on all your website internal links | Historyprovider | true | | | Helps to keep track of the navigation state | PrefetchbootableProvider | true | Pjax | Prefetch links on mouse enter (useful with Pjax) | CacheProviderprovider | true | | Cache ajax requests (useful with Pjax) | SplashscreenbootableProvider | false | | Add a splash screen for the first init | TransitionFactorybootableProvider | false | | Instantiate page transitions objects according to your Pjax context

Pjax

import StartingBlocks, { History, Pjax } from 'starting-blocks'

// ...Instantiate starting blocks

// Add service
startingBlocks.provider('History', History)
startingBlocks.bootableProvider('Pjax', Pjax)

// ...Boot

⚠️ Don't forget to prepare your DOM adding specific data attributes and required classes, see DOM structure section

Splashscreen

We implemented a Splashscreen service that is triggered at the first website launch. Create your own class that extends our AbstractSplashscreen:

import { AbstractSplashscreen, Dispatcher, EventTypes } from 'starting-blocks'

export default class Splashscreen extends AbstractSplashscreen {
    constructor (container) {
        super(container, 'Splashscreen')
        //... custom values
    }

    // You need to override this method
    hide () {
        return new Promise(resolve => {
            // custom logic, animations...
            resolve()
        })
    }
}
import Splashscreen from './Splashscreen'

// ...Instantiate starting blocks

// Add service
startingBlocks.bootableProvider('Splashscreen', Splashscreen)

// ...Boot

TransitionFactory

When AJAX navigation is enabled, transitions are triggered to manage animations between pages. To choose a custom transition, you can set data-transition attribute with a transition name on each link:

<a href="/contact" data-transition="fade">Contact</a>

Then, create a TransitionFactory class and register it into Starting Blocks as a service. In this class, you must implement getTransition (previousState, state) method. This method will be called on each transition and will give you access to history state informations:

  • previousState and state
    • transitionName : data-transition attributes of the clicked link
    • context : equal to "history", "link"

Example:

// src/factories/TransitionFactory.js
import DefaultTransition from './transitions/DefaultTransition';
import FadeTransition from './transitions/FadeTransition';

export default class TransitionFactory {
    getTransition (previousState, state) {
        switch (state.transitionName) {
            case 'fade':
                return new FadeTransition()
            default:
                return new DefaultTransition()
        }
    }
}

How to register your Transition factory service?

import TransitionFactory from '../factories/TransitionFactory'

// ...Instantiate starting blocks

// Add service
startingBlocks.provider('TransitionFactory', TransitionFactory)

// ...Boot

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. ⚠️ Be careful, you have to wait for this.newPageLoading Promise resolution to make sure the new page DOM is ready. Then, you have access to old and new Page instances.

Example with fade animation (we use TweenLite from gsap for this example):

// src/transitions/FadeTransition.js
import AbstractTransition from '../AbstractTransition'
import { TweenLite } from 'gsap'

/**
 * Fade Transition example. Fade Out / Fade In between old and new pages.
 */
export default class FadeTransition extends AbstractTransition {
    /**
     * Entry point of the animation
     * Automatically called on init()
     */
    start () {
        // Wait new content and the end of fadeOut animation
        // this.newPageLoading is a Promise which is resolved when the new content is loaded
        Promise.all([this.newPageLoading, this.fadeOut()])
            // then fadeIn the new content
            .then(this.fadeIn.bind(this))
    }

    /**
     * Fade out the old content.
     * @returns {Promise}
     */
    fadeOut () {
        return new Promise(resolve => {
            TweenLite.to(this.oldPage.rootElement, 0.4, {
                alpha: 0,
                onComplete: resolve
            })
        })
    }

    /**
     * Fade in the new content
     */
    fadeIn () {
       // Add display: none on the old container
       this.oldPage.rootElement.style.display = 'none'

       // Prepare new content css properties for the fade animation
       this.newPage.rootElement.style.visibility = 'visible'
       this.newPage.rootElement.style.opacity = '0'
        
        // Scroll to the top
        document.documentElement.scrollTop = 0
        document.body.scrollTop = 0

        // fadeIn the new content container
        TweenLite.to(this.newPage.rootElement, 0.4, {
            autoAlpha: 1,
            onComplete: () => {
                // IMPORTANT: Call this method at the end
                this.done()
            }
        })
    }
}

DOM structure

This ES6 javascript framework has been designed to handle either complete HTML responses or partial HTML responses to lighten backend process and bandwidth. One of the most useful Page and Block property is rootElement that will always refer to the current page/block main-section.

In a page context, rootElement is the DOM element which is extracted at the end of each completed AJAX requests. 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.

In a block context, rootElement will store the DOM element with [data-node-type] attribute.

When Pjax service is used and window.fetch supported, all links inside document body are listened to fetch pages asynchronously and make transitions.

To declare a partial DOM section as the rootElement you must add some classes and data to your HTML tags.

<!-- Page (HomePage.js) -->
<div id="page-content-home"
     class="page-content"
     data-node-type="HomePage"
     data-node-name="home"
     data-is-home="1"
     data-meta-title="Home page">
    
    <!-- Block (GalleryBlock.js)  -->
    <div id="home-page-gallery"
         class="page-block"
         data-node-type="GalleryBlock">
        <img src="https://media.giphy.com/media/6LBVNUvqzY3tu/giphy.gif" alt="Display me…">
    </div>
</div>
  • 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.
  • page-content class is essential in order to extract your DOM element during AJAX request. You can customize this class name in StartingBlock options (pageClass: "page-content").
  • 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.
  • data-node-name is used to name your page object and to rename body class and ID after it.
  • data-is-home: 0 or 1
  • 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.

You’ll find index.html and page1.html examples files. You can even test them by spawning a simple server with npm run serve command. Then go to your favorite browser and type http://localhost:8080.

Page

Each custom page needs to extend our AbstractPage class to be registered as a service in your Starting Blocks instance. Be careful that data-node-type attribute matches with your service declaration. By default Starting Blocks will instantiate the DefaultPage class if no data-node-type attribute is found.

Best practice : Create your own DefaultPage with your common features then override the default service to use it as a common base for your custom pages :

import { AbstractPage } from 'starting-blocks'

export default class CustomDefaultPage extends AbstractPage {
    //... common methods
}

// Custom page example
export default class HomePage extends CustomDefaultPage {
    //... home page custom methods
}
import CustomDefaultPage from './pages/CustomDefaultPage'

// ...Instantiate starting blocks

// Override the DefaultPage service with your own
startingBlocks.instanceFactory('DefaultPage', c => {
    return new CustomDefaultPage(c)
})
// ...Boot

Page overridable methods

| Method | Description | | --- | --- | | onResize | On viewport resize, this method is debounced. |

Block

A block is a section of your page. It can be a Slideshow, an ajax form, a map... Starting Blocks automatically maps those DOM elements with a custom ES6 class in the same way the future CustomElementRegistry will perform. Create your own class extending our AbstractBlock or AbstractInViewBlock then register them as a service. data-node-type attribute content and your service name must match.

Common block overridable methods in AbstractBlock

| Method | Description | | --- | --- | | onResize | On viewport resize, this method is debounced of 50ms. | | onPageReady | Triggered once all page blocks have been created. |

In-view block overridable methods in AbstractInViewBlock

| Method | Description | | --- | --- | | onIntersectionCallback | Triggered when in view block state changed (in or out). | | onScreen | Called when block is in the viewport. | | offScreen | Called when block is out of the viewport. |

AbstractInViewBlock extends AbstractBlock and thus implements each of its methods too.

Options

You can pass some options when instantiating StartingBlocks object:

| Parameter | Type | Default | Description | | --- | --- | --- | --- | | wrapperId | string | 'sb-wrapper' | | pageBlockClass | string | 'page-block' | | pageClass | string | 'page-content' | | objectTypeAttr | string | 'data-node-type' | | noAjaxLinkClass | string | 'no-ajax-link' | | noPrefetchClass | string | 'no-prefetch' | | manualDomAppend | boolean | false | To manually manage page build directly in transition instances

Events

| Const name | Event name | Description | | --- | --- | --- | | BEFORE_PAGE_LOAD | SB_BEFORE_PAGE_LOAD | Before Router initialize XHR request to load new page. | | AFTER_PAGE_LOAD | SB_AFTER_PAGE_LOAD | After window.fetch XHR request succeeded. | | AFTER_DOM_APPENDED | SB_AFTER_DOM_APPENDED | After Router appended new page DOM to wrapperId. | | AFTER_PAGE_BOOT | SB_AFTER_PAGE_BOOT | After Router create new page instance. | | CONTAINER_READY | SB_CONTAINER_READY | | | TRANSITION_START | SB_TRANSITION_START | | | TRANSITION_COMPLETE | SB_TRANSITION_COMPLETE | | | BEFORE_SPLASHSCREEN_HIDE | SB_BEFORE_SPLASHSCREEN_HIDE | | | AFTER_SPLASHSCREEN_HIDE | SB_AFTER_SPLASHSCREEN_HIDE | |

Docs

To generate documentation, you’ll at least NodeJS v4.4 and to install ESDoc.

npm run doc;

Documentation will be available in doc/ folder.

Naming conventions

We dropped jQuery in Starting-blocks v5 and we changed several variables names. We suffixed every DOMElement variable with Element, Container, Elements or Containers.

Examples:

let mainContainer = document.getElementById('main-container')
let imageContainer = document.getElementById('image-container')
let imageElements = imageContainer.querySelectorAll('.image')

Improving Starting Blocks

To work locally on Starting Blocks, you’ll find some HTML files in examples/ folder.

  • Install dependencies: yarn.
  • Type npm run dev to improve Starting Blocks locally.
  • Type npm run build to optimize project in one file as: main.js.
  • Type npm run demo to build demo project in examples/ folder.

Compatibility

Starting Blocks use native Promise, fetch, IntersectionObserver and MutationObserver browser features. Don't forget to use some polyfills for old browsers.

Demo

To launch the example you need to change the examples/srv/js/config/config.example.js file with your own informations.

Go further with Starting Blocks

If you use Webpack you will be able to dynamically lazy-load your blocks with a custom BlockBuilder. Create a custom BlockBuilder and override the default one:

import { AbstractBlockBuilder } from 'starting-blocks'

export default class WebpackAsyncBlockBuilder extends AbstractBlockBuilder {
    // Dynamic import
    async getBlockInstance (nodeTypeName) {
        try {
            const Block = await this.getModule(nodeTypeName)

            if (!this.hasService(nodeTypeName)) {
                this.container.$register({
                    $name: nodeTypeName,
                    $type: 'instanceFactory',
                    $value: c => {
                        return new Block(c)
                    }
                })
            }

            return this.getService(nodeTypeName).instance()
        } catch (e) {
            console.error(e.message)
            return null
        }
    }

    async getModule (nodeTypeName) {
        return import(`../blocks/${nodeTypeName}` /* webpackChunkName: "block-" */)
            .then(block => {
                return block.default
            })
    }
}

Then override the default PageBuilder service:

// Custom block builder (dynamic import)
startingBlocks.provider('BlockBuilder', WebpackAsyncBlockBuilder)

Contributors