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

view-component

v2.1.1

Published

Lightweight javascript view component library

Downloads

11

Readme

View component

Build Status Coverage Status NPM Status

Library for building user interfaces with smart event system, strict component props and clear way of defining view hierarchy. Works great with server side rendered html. Weighs around 3.7 KB.

Designed to be versatile for small widgets and complete web applications. Best companion of server rendered applications where frameworks like Vue and React are difficult to adopt. Requires no build setup and can be easily integrated to existing project. Supports all browsers that are ES5-compliant (IE8 and below are not supported).


Features

  • delegated event listeners with automatic cleanup
  • one time event listeners for lazy loading anything
  • props definition and validation with simple schema setup
  • publish and subscribe api for component to component communication
  • hierarchy api for mapping child views to parent view html tree
  • extending api for sub-classing components with prototype and static properties
  • mixins for sharing reusable functionality across components

npm install view-component --save

Examples


Dropdown component with toggle UI

Component html:

<nav class="dropdown">
    <button type="button" class="toggleBtn"></button>
    <ul>
        <li>...</li>
    </ul>
</nav>

Component javascript:

// define Dropdown component
var Dropdown = ViewComponent.extend({
    props: {
        activeClass: {
            type: String,
            default: 'opened'
        }
    },
    events: {
        'click .toggleBtn': 'toggle'
    },
    toggle: function() {
        this.isOpened() ? this.close() : this.open();
    },
    isOpened: function() {
        return this.el.classList.contains(this.activeClass);
    },
    open: function() {
        this.el.classList.add(this.activeClass);
    },
    close: function() {
        this.el.classList.remove(this.activeClass);
    }
});

// create dropdown instance
new Dropdown({el: '.dropdown'});

List component with view tree

Component html:

<ul>
    <li>...</li>
    <li>...</li>
</ul>
// define List item component
var ListItem = ViewComponent.extend({...});

// define List component
var List = ViewComponent.extend({
    initialize: function() {
        this.mapViews('li', ListItem);
    }
});

// create list instance
new List({el: 'ul'});

Api and options


ViewComponent.extend(prototypeProperties, [staticProperties])

Used to define view types / constructor functions. Define prototype methods for view via object hash. Static properties are optional.

var View = ViewComponent.extend({
    prototypeAttribute: 42,
    prototypeMethod: function() {}
}, {
    staticAttribute: 'foo',
    staticMethod: function() {}
});

// prototype method call
new View().prototypeMethod();

// static method call
View.staticMethod();

Subclassing components:

var Animal = ViewComponent.extend({
    cuddle: function() {
        console.log('Cuddling...');
    }
});

var Dog = Animal.extend({
    cuddle: function() {
        Animal.prototype.cuddle.call(this);
        console.log('And barking');
    }
});

var Cat = Animal.extend({
    cuddle: function() {
        throw new Error('Meeeow');
    }
});

mixins: Array

Used to inject reusable functionality into components. Mixins can contain hooks (initialize, beforeRemove and afterRemove), events, and prototype properties. Multiple mixins can be attached to component.

var dropdownMixin = {
    initialize: function() {
        console.log('Dropdown mixin inside');
    },
    events: 'click .dropdown': 'toggle',
    toggle: function() {
        this.el.classList.contains('opened')
            ? this.close()
            : this.open()
        ;
    },
    open: function() {
        this.el.classList.add('opened');
    },
    close: function() {
        this.el.classList.remove('opened');
    }
};

var View = ViewComponent.extend({
    mixins: [dropdownMixin]
});

new View(); // outputs "Dropdown mixin inside"

props: object

Validate component props. Set expectations for valid property types. Mark properties as required and provide default values. Custom validation logic can also be used when simple type checks are not enough.

ViewComponent.extend({
    props: {
        firstName: String,
        lastName: {
            type: String,
            required: true
        },
        address: String,
        zipCode: [String, Number],
        age: {
            type: Number,
            validator: age => age > 17
        },
        acceptsCookies: {
            type: Boolean,
            default: false
        }
    }
});

initialize()

Method / hook called on view bootstrap.

var View = ViewComponent.extend({
    initialize: function() {
        // do some work
    }
});

beforeRemove()

Method / hook called before view removal.

var View = ViewComponent.extend({
    initialize: function() {
        this.plugin = new Plugin();
    },
    beforeRemove: function() {
        this.plugin.destroy();
    }
});

events: object | function

Declare events with object hash or custom function. Event delegation is used so there is no need to rebind events when view html content is updated (with ajax or otherwise). All declared listeners are removed when view is removed.

// with object definition
ViewComponent.extend({
    events: {
        'click .selector': 'handler', // bound to this.handler
        'one:submit form': 'oneSubmit', // listener will fire only once
        'click button': function(e) {}, // inline handler
        'resize window': 'onWindowResize', // window events
        'keyup document': 'onDocumentKeyup' // document events
    }
});
// with function definition
ViewComponent.extend({
    events: function() {
        return {'click .selector': 'handler'};
    }
});

addDismissListener(listener)

When escape key is pressed or element outside view is clicked listener will be called.

var dismissListener = require('view-component/lib/mixins/dismissListener');

ViewComponent.extend({
    mixins: [dismissListener],
    open: function() {
        // after open logic
        this.addDismissListener(this.close);
    },
    close: function() {
        // after close logic
        this.removeDismissListener(this.close);
    }
});

removeDismissListener(listener)

Remove dismiss listener.


mapView(selector, View, [props])

Create View instance with first html element inside current view found via selector. Returns mapped instance. Optional component props can be set as object or function returning object.

<body>
    <header class="mainHeader"></header>
</body>
var Controller = ViewComponent.extend({
    initialize: function() {
        var headerView = this.mapView('.mainHeader', MainHeader);
    }
});

var MainHeader = ViewComponent.extend({});

new Controller({el: 'body'});

View components can be lazyloaded via view provider function which will be executed only if element is found via selector. mapView will return promise which resolves to view intance (if element is found) or undefined if not. Requires promise browser support or polyfill.

ViewComponent.extend({
    initialize: function() {
        this.mapView('.mainHeader', () => import(./MainHeader))
            .then(headerView => console.log(headerView));
    }
});

mapViews(selector, View, [props])

Map View instances to all elements inside current view found via selector. Returns mapped instances array. Optional component props can be set as object or function returning object.

<ul>
    <li>...</li>
    <li>...</li>
</ul>
// define List component
var List = ViewComponent.extend({
    initialize: function() {
        this.mapViews('li', ListItem);
    }
});

// define List item component
var ListItem = ViewComponent.extend();

// create list instance
new List({el: 'ul'});

View components can be lazyloaded via view provider function which will be executed only if elements are found via selector. mapViews will return promise which resolves to view intances array (if elements are found) or empty array if not. Requires promise browser support or polyfill.

ViewComponent.extend({
    initialize: function() {
        this.mapViews('li', () => import(./ListItem));
            .then(listViews => console.log(listViews));
    }
});

addView(viewInstance)

Adds view instance to sub view registry. This binding enables effective cleanup of view hierarchy.

ViewComponent.extend({
    initialize: function() {
        var view = this.addView(new ViewComponent());
    }
});

trigger(eventName, [data])

Trigger custom event and optionally provide data to handler callback.

ViewComponent.extend({
    initialize: function() {
        this.trigger('componentInitialized');
    }
});

on(eventName, callback)

Subscribe to custom view events

ViewComponent.extend({
    initialize: function() {
        this.on('foo', function(data) {
            console.log(data);
        });
        this.trigger('foo', {foo: 'bar'});
    }
});

once(eventName, callback)

Subscribe to one time custom view events

ViewComponent.extend({
    initialize: function() {
        this.once('foo', () => console.log(foo));
        this.trigger('foo').trigger('foo');
    }
});

off([eventName, [callback]])

Removes listeners to custom view events.

ViewComponent.extend({
    initialize: function() {
        this.off('foo');
    }
});

listenTo(publisher, eventName, callback)

Listen to other object events.

ViewComponent.extend({
    initialize: function() {
        initialize: function() {
            var headerView = this.mapView('.mainHeader', HeaderView);
            this.listenTo(headerView, 'customEvent' function(data) {
                console.log(data);
            });
        }
    }
});

listenToOnce(publisher, eventName, callback)

Listen to other object events one time.

ViewComponent.extend({
    initialize: function() {
        initialize: function() {
            var headerView = this.mapView('.mainHeader', HeaderView);
            this.listenToOnce(headerView, 'customEvent' function(data) {
                console.log(data);
            });
        }
    }
});

stopListening([publisher, [eventName, [callback]]])

Removes listeners to custom publisher events.

ViewComponent.extend({
    initialize: function() {
        initialize: function() {
            var headerView = this.mapView('.mainHeader', HeaderView);
            this.listenTo(headerView, 'customEvent' function(data) {
                console.log(data);
            });
            this.stopListening(headerView, 'customEvent');
        }
    }
});

find(selector)

Returns single html element found via selector inside current component element context.

ViewComponent.extend({
    initialize: function() {
       var mainHeaderElement = this.find('.mainHeader');
    }
});

findAll(selector)

Returns array of html elements found via selector inside current component element context.

ViewComponent.extend({
    initialize: function() {
       var listElements = this.findAll('ul li');
    }
});

validateData(schema, obj)

Validate object properties against schema (identical to props validation)


removeViews()

Removes all registered sub views (added with mapView, mapViews and addView methods).


remove()

Removes view from DOM and does cleanup of all bound events.

Installation

View component is packaged as UMD library and can be used in client and server environments.

// install via npm
npm install view-component --save

// if you use bundler
var ViewComponent = require('view-component');

// or use browser globals
var ViewComponent = window.ViewComponent;