expressionjs-scope

0.1.0 • Public • Published

Scope

A clean way to listen for changes on JavaScript expressions.

Install

$ component install expressionjs/scope

Overview

The scope is an object that takes some arbitrary JavaScript object and a set of JavaScript expressions, and notifies you when any one of those expressions need to be re-evaluated.

How does it work?

Expressions have "paths"

It boils down to the fact that an expression contains a set of properties, or paths. For example, you might have expressions like this:

count > 10
isLoggedIn ? "Log out" : "Log in"
user.name
user.address.city

Each of those expressions contain a set of paths (the top-level ones you can think of as just "properties"):

[ 'count' ]
[ 'loggedIn' ]
[ 'user', 'user.name' ]
[ 'user', 'user.address', 'user.address.city' ]

Some expressions can also have dynamic properties:

user[attr]

which is basically this:

[ 'user', 'attr', function() { return this.data.user[attr] } ]

A scope is a set of expressions

When you define a scope, you are giving it expressions:

var scope = new Scope;
var a = new Expression('count > 10');
var b = new Expression('isLoggedIn ? "Log out" : "Log in"');
a.bind(scope);
b.bind(scope);

So now, the scope has 2 expressions, each which have their own sets of paths. This means a scope knows the set of all paths for all it's expressions. This makes it easy to compute which expressions should be updated when one path changes.

An expression is a function and a set of paths

Each of those expressions just has a set of paths, and a compiled function to get or set the value of the expression:

var exp = new Expression('count > 10');
exp.paths; // [ 'count' ]

An expression is evaluated in reference to a scope

In your everyday JavaScript, you are creating expressions that are in a scope, such as a function scope:

function() {
  var count = 5;
  function evaluate() {
    return count > 10;
  }
  return evaluate();
}

Each function above defines a scope, so the expression count > 10 is inside the second scope, but the value of count comes from the first scope. So basically, you have a tree of scopes, and expressions that are interpreted in reference to those scopes.

In the same way, the scope object in this repo is used to evaluate expressions. So basically, we have (to some degree) reimplemented scope/expression functionality in JavaScript.

Why did we do this?

Watching expressions for changes

In many HTML template engines, one of the goal is to watch some data for changes, and update the DOM when that data changes. Long-story short, there is no clean/easy way to do this in JavaScript without introducing a bunch of magic.

So what we have done is create essentially a data structure for you that will tell you if your expressions need to be re-evaluated, without too much magic (such as overriding properties with getters/setters). That's what this scope is; a container for expressions, that will notify you when a specific expression needs to be re-evaluated.

This is a much simpler way of managing it that manually controlling observers, or having to extend custom objects like with Ember.

A base for template engines

This component is a low-level foundation for template engines to build on top of. It makes it so you can get the full power of understanding and evaluating custom expressions (not just JavaScript expressions, you can also write your own using custom grammars!).

As a reference implementation, we have creating templatejs on top of this, our ideal template engine.

Usage

On a day-to-day basis, you probably won't ever need to go down to the level of understanding the scope. Things like template engines will build simpler abstractions on top of it.

But in case you're interested, here is how you use it:

var Scope = require('expressionjs-scope');
var Expression = require('expressionjs-expression');
 
// initialize your expressions
var a = new Expression('isLoggedIn && count > 10');
var b = new Expression('isLoggedIn ? "Log out" : "Log in"');
 
// initialize your scope
var scope = new Scope;
 
// bind expressions to scope
a.bind(scope);
b.bind(scope);
 
// arbitrary data
var data = {
  count: 20,
  loggedIn: false
};
 
// bind the data your scope
scope.bind(data);
 
// watch for changes on an expression
a.on('change', function(){
  console.log('a:', a.fn());
});
 
b.on('change', function(){
  console.log('b:', a.fn());
});
 
// set the value of a path
scope.set('isLoggedIn', true);
// a: true
// b: "Log out"
scope.set('isLoggedIn', false);
// a: false
// b: "Log in"

Scopes can also be nested:

var scope = new Scope;
var child = new Scope(scope);
var grand = new Scope(child);

If you just want to re-execute all your expressions, then rebind your data:

scope.bind(data);

Notes

License

MIT

Readme

Keywords

none

Package Sidebar

Install

npm i expressionjs-scope

Weekly Downloads

1

Version

0.1.0

License

MIT

Last publish

Collaborators

  • viatropos