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

laces

v0.1.4

Published

Provides the M in MVC, while you tie the rest.

Downloads

8

Readme

laces.js

Laces.js provides the M in MVC, while you tie the rest.

Rationale

While there are plenty of MVC frameworks available for JavaScript, most of them dictate a variety of other application design choices on you. For example, Backbone.js requires that you use underscore.js, Ember.js comes with its own templating system, AngularJS requires you to extend the DOM, and so on. A few frameworks require (or strongly encourage) you to use CoffeeScript, and many carry significant overhead.

Laces.js by contrast provides you with a Model, but nothing more. It provides you with the laces to tie your model to whatever View or Controller you prefer. It consists of about 700 lines of JavaScript code, including whitespace and comments (7K minified). Optionally, you can add one or more add-ons for extra features like two-way data binding with templates.

The project was created because I wanted a good model to use with an HTML5 map editor for a game engine I'm working on. The map editor has a canvas view and uses a custom WebSockets-based API for server communication, leaving me with little use for templating engines and XHR integration most other MVC frameworks provide.

Basic Usage

Laces.js works as a model with automatic data binding. First, you create a model:

var model = new LacesModel();

You can set properties using the set() method:

model.set("firstName", "Arend");
model.set("lastName", "van Beelen");

Once a property is set, it can be accessed using dot notation on the model object:

model.firstName; // "Arend"

As a shorthand form, properties can also be set using nothing but the constructor:

var model = new LacesModel({
    firstName: "Arend",
    lastName: "van Beelen"
});

You can also define properties that reference other properties. We call these computed properties:

model.set("fullName", function() { return this.firstName + " " + this.lastName; });

model.fullName; // "Arend van Beelen"

When a property is updated, any computed properties that depend on its value are also updated:

state.firstName = "Arie";

state.fullName; // "Arie van Beelen"

As you can see, changes to the value of a single property now automatically update all properties that depend on that property. This behavior can also be chained, so that a property that depends on fullName for example, also gets updated when firstName or lastName is modified.

Nested Properties

It is also possible to use nested properties within your model. Example:

model.set("user", null);
model.set("displayName", function() { return this.user && this.user.name || "Anonymous"; });

There are now several ways of modifying the state.user.name property, each of which will reflect on the displayName properly:

model.user = { name: "Arend" };

model.displayName; // "Arend"

model.user.name = "Arie";

model.displayName; // "Arie"

model.user = null;

model.displayName; // "Anonymous"

Maps and Arrays

The properties of a Laces Model can contain more than just primitives. They also support Maps (also known as dictionaries) and Arrays.

You may not have realized it, but you've already seen a Laces Map in action. The example above, with the nested properties, used a Laces Map. Every time you assign assign a plain JavaScript object to a Laces property, the value gets converted automatically to a Map. The assignment of the user object above could thus also have been written as:

model.user = new LacesMap({ name: "Arend" });

The API for a Laces Map is the same as a Laces Model. If you want to add a previously unknown property to a Map, you have to use set(). If you want to remove a property from a Map, you should use remove():

model.user.remove("name");

You can iterate over the properties in a Map in exactly the same way as a plain JavaScript object. Just don't forget to use the hasOwnProperty() guard (which you should do anyway, according to jslint):

for (var propertyName in model.user) {
    if (model.user.hasOwnProperty(propertyName)) {
        console.log("Property " + propertyName + " has value: " + model.user[propertyName]);
    }
}

Unlike a Model, a Map does not support computed properties, and does not fire events of the type "change:<propertyName>". Assigning a function to a property would simply set the value to be that function. If you really want computed properties in nested objects, it is possible to nest Models:

model.user = new LacesModel({ name: "Arend" });

Arrays are supported too, and a Laces Array is created implicitly when you assign an array to a Laces property:

model.user.friends = [];

You may also assign a Laces Array explicitly:

model.user.friends = new LacesArray();

The API for a Laces Array is exactly the same as for a regular JavaScript array, but when setting values in the array you should use the set() method rather than bracket notation to make sure the changes are properly registered.

Arrays can be bound to in the same way as a Laces Map or Model:

mode.user.friends.bind("change", function(event) { console.log("Friends changed"); });

Read on to the next section for more about bindings.

Custom Bindings and Templates

You may be interested in binding a custom callback to whenever one of those properties changes value:

model.bind("change:fullName", function(event) { $(".full-name").text(event.value); });

You can also watch the whole model instead of a specific property. This is an effective way to integrate with template systems. For example, the following code shows how to render a Hogan.js template when the model changes:

var addressCardTemplate = Hogan.compile("<div class=\"address-card\">" +
                                        "<p>{{fullName}}</p>" +
                                        "<p>{{address}}</p>" +
                                        "<p>{{postalCode}} {{cityName}}</p>" +
                                        "<p>{{countryName}}</p>" +
                                        "</div>");
model.bind("change", function(event) { addressCardTemplate.render(model); });

The listener callback function gets executed in the scope of the model to which it is bound.

An interesting feature of the bindings is that the listener callback can be fired immediately after binding. This may be useful for initialization and can save you from some code duplication:

model.bind("change:fullName", function(event) { $(".full-name").text(event.value); }, { initialFire: true });

Note that you can bind and unbind events using the bind()/unbind() methods as well as the on()/off() methods to match the style you prefer.

Events

As you saw above, Laces objects generate change events whenever something changes. Here's an overview of all the events that get generated.

All events carry a payload, which is passed as the event object to the listener callback function.

If the object is a Laces Map or Model, the event object contains the following properties:

If the object is a Laces Array, the event object contains the following properties:

Usage with Node.js or Require.js

If you want to use Laces.js with Node.js or Require.js, you can do so easily. Just import the module as you would expect. The module will have Model, Map and Array properties defined on it.

Example with Node.js:

var Laces = require("laces.js");
var model = new Laces.Model({
    firstName: "Arend",
    lastName: "van Beelen"
});

The Laces.js package for Node.js can be installed with npm:

$ npm install laces.js

Example with Require.js:

require(["laces"], function(Laces) {
     var model = new Laces.Model({
          firstName: "Arend",
          lastName: "van Beelen"
     });
});

Add-ons

Laces.js Tie

Laces.js Tie is an add-on that adds two-way data bindings between a Laces Model and a rendered template. Laces.js Tie uses HTML5 data attributes to create its bindings and is template-engine agnostic. It has already been confirmed to work in tandem with Handlebars.js, Hogan.js and Underscore.js's built-in templates, in addition to plain HTML strings.

For example, here's how to tie a Hogan.js template to a Laces Model:

var template = Hogan.compile(myTemplateString);
var tie = new LacesTie(model, template);
someDomElement.appendChild(tie.render());

The render() function returns a DocumentFragment which you can insert anywhere into the DOM. It is important you don't convert this fragment into a string before adding it to the DOM, as it would result in losing all live bindings.

If the template iterates over an array or other list, you may want to rerender it when the list changes. In such case you can use something like the following instead of the last line from above: target

model.someArray.bind("add remove", function() {
     someDomElement.innerHTML = "";
     someDomElement.appendChild(tie.render());
}, { initialFire: true });

An actual template might look something like this (using Hogan.js as example):

<ul>
     {{#someArray}}
     <li>
          <span data-tie="text: someArray[{{index}}].name, editable: true"></span>
     </li>
     {{/someArray}}
</ul>

Once the fragment is inserted in the DOM, it maintains bi-directional bindings for user-generated events. This means the fragment will automatically update when the model is updated, but when an input element is bound to a model property, any changes made by the user to the value of the element will also be saved back into the model.

The data-tie attribute may contain multiple key-value pairs, separated by commas. Here is an overview of all the supported keys:

You can combine multiple ties in a single element. Example:

<span data-tie="text: someArray[{{index}}].name, class: someArray[{{index}}].class"></span>

Finally, the LacesTie constructor also takes an optional options argument. It supports the following options:

Changelog

Saturday, December 7, 2013: The Tie add-on has been heavily rewritten, breaking backwards compatibility. Instead of various data-laces-* attributes, the add-on now expects a single data-tie attribute. This update also adds support for binding radio buttons and CSS classes. For more information, please read the documentation above.

Laces.js Local

Laces.js Local provides a very simple extension over the default Laces Model. A Local Model will automatically sync with LocalStorage:

var model = new LacesLocalModel('my-model'));

Any properties you set on the model will still be there when the page is reloaded.

Demo

There's a bunch of demos included in this repository, just check them out.

For real-world examples, check out these projects:

You might also be interested in the TodoMVC example using Laces.js.

And there's even a variation on the TodoMVC example that uses the Laces.js Tie and Laces.js Local add-ons.

Tests

After checkout, install dependencies with:

$ npm install

Then open test/index.html in the browser.

Compatibility

  • Chrome 5+
  • IE 9+
  • Firefox 4+
  • Safari 5+
  • Opera 11.60+