GithubHelp home page GithubHelp logo

constraint.js's Introduction

Build Status

constraint.js

Simplifies using cassowary.js constraint solver by wrapping existing object and class such they automatically create appropriate c.Variable and c.Expression instances. Adding constraints between objects is also simplified by passing a function contain code describing the constraints to addConstraints.

Example

var c = require("constraint");

var r1 = { x: 50, y: 50, w: 100, h: 25 };
var r2 = { x: 50, y: 50, w: 75, h: 75 };

var desc = { x: "var", y: "var", w: "fixed", h: "fixed" };

c.wrapObject(r1, desc);
c.wrapObject(r2, desc);

c.addConstraints(function (r1, r2) {
    r2.x + r2.h / 2 == r1.x + r1.h / 2; // align horizontally along the center
    r2.y - 10 == r1.x + r1.h;           // create a 10px vertical gap
}, r1, r2);
    
console.log(r1);    // 
console.log(r2);    //

Motivation

It's much easier to describe a relation between expressions than it is to figure the code to enforce that relationship each time one of the variables in the relation changes. Describe the relationships between properties or expressions also makes the intent clearer.

The problem however, is that constraint solvers (at least cassowary) requires a developer to write a lot of boilerplate code to not only set up constraints but also update them and update variables on existing objects.

Some of this boilerplate can be avoided in client code by embedded library classes with the boilerplate. Unfortunately, if you want to use existing libraries this would require modifying the libraries. Moreover, it doesn't fix the situation where you have POJOs (Plain Old Javascript Objects).

Documentation

Descriptors specify which properties to create variables or expressions for within the constraint solver. Each key should match a property on the object or class being wrapped.

  • "var" creates a variable which is affected by other variables
  • "fixed" creates a variable which is unaffected by other variables

Both of these can only be created for simple properties, not computed properties.

  • "comp" creates an expression which is affected by the variables it depends on

This can only be used with computed properties.

var c = require("constraint");
class Rect {
    constructor(x, y, w, h) {
        Object.assign(self, {x, y, w, h});
    }
    get right() {
        return this.x + this.w;
    }
    get bottom() {
        return this.y + this.h;
    }
}

Rect = c.wrapClass(Rect, { 
    x: "var", y: "var", 
    w: "fixed", h: "fixed", 
    right: "comp", bottom: "comp" 
});

var r1 = new Rect(50, 50, 100, 25);
var r2 = new Rect(50, 50, 75, 75);

c.addConstraints(function (r1, r2) {
    r2.y == r1.bottom;
    r2.x == r1.right;
}, r1, r2);

console.log(r1);    // {x:0, y:0, w:100, h:25}
console.log(r2);    // {x:100, y:25, w:75, h:75}

Note that x and y are initialized to 0. All properties marked as "var" will default to some initial value after the constraints have been setup. In order to "fix" this, simply set the value on the properties you want to update.

r1.x = 200;

console.log(r1);    // {x:200, y:0, w:100, h:25}
console.log(r2);    // {x:300, y:25, w:75, h:75}

When setting properties to values that conflict with each other, the last property to be set wins.

r1.x = 200;
r2.x = 200;
    
console.log(r1);    // {x:100, y:0, w:100, h:25}
console.log(r2);    // {x:200, y:25, w:75, h:75}

Notes about addConstraints: argument order matters. The function being passed in is parsed and the the names of the arguments are and their order is extracted from that function. The last two arguments to addConstraints must be in the same order.

c.addConstraints(function (r1, r2) {
    r2.y == r1.bottom;
    r2.x == r1.right;
}, r1, r2); // GOOD

c.addConstraints(function (r1, r2) {
    r2.y == r1.bottom;
    r2.x == r1.right;
}, r2, r1); // BAD - r1 and r2 have been swapped

The second call to addConstraints is equivalent to:

c.addConstraints(function (r1, r2) {
    r1.y == r2.bottom;
    r1.x == r2.right;
}, r1, r2);

There's also a handy decorator syntax that you can use if you're using a browser or transpiler that supports it.

var desc = { 
    x: "var", y: "var", 
    w: "fixed", h: "fixed", 
    right: "comp", bottom: "comp" 
};

@c.decorateClass(desc)
class Rect {
    constructor(x, y, w, h) {
        Object.assign(self, {x, y, w, h});
    }
    get right() {
        return this.x + this.w;
    }
    get bottom() {
        return this.y + this.h;
    }
}

The system supports all of the same linear constraints as cassowary.js. This includes: >, <, >=, <=, ==. Here's an example using the > operator.

c.addConstraints(function (r1, r2) {
    r2.x > r1.x + r1.w;     // r2 is always to the left of r1
}, r1, r2);

Expressions can include linear operators, constants, and properties on objects that have been wrapped by constraint.js.

Limitations

The system cannot wrap variables that aren't objects. If there variables that you would like to create constraints for, create an object containing the variables as properties, wrap that object, and create constraints.

var x = 5;
var y = 10;

var obj = c.wrapObject({x, y});

c.addConstraints(function(obj) {
    obj.y = 2 * obj.x;
});

console.log(obj);                   // { x:0, y: 0 }
console.log(`x = ${x}, y = ${y}`);  // x = 5, y = 10

obj.x = 50;

console.log(obj);                   // { x:50, y:100 }

Computed properties containing only a single return statement are supported.

Implmentation Details

Symbols

The wrap functions modify existing objects and classes in such a way that they won't interfere with their operation. This is done by adding properties using Symbols as computed property names. Symbols prevent the possibility of collision with existing properties will protect them from be accidentally clobbered by other code.

Code as data

Calling .toString() on a function reference returns the source code for that function. The source code for individual functions is parsed using esprima. Contraints are derived from expressions within the AST. This technique is used for "comp" properties when wrapping objects/classes and by addConstraints.

Future Work

  • moar tests
  • comparison operators listed above
  • decorators for methods (which should replace the need for the descriptor)
  • support more expressions within addConstraint
    • array indexing
    • looping constructs
  • improve error handling
  • get current constraints
    • all
    • for a particular property (on an object)
  • ability to remove constraints
  • allow different objects to be in separate solvers
    • disallow constraints between objects in separate solvers

constraint.js's People

Contributors

kevinbarabash avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

Forkers

mcanthony

constraint.js's Issues

Code example not working as indicated

I tried your simple object example under "Limitations". After fixing the numArguments error in the c.addConstraints call, the code ran, but the output is not what it indicates. It prints out x=5,y=10 then { x: 50, y: 10 }.

I think I may be missing something about how this is supposed to work.

var x = 5;
var y = 10;

var obj = c.wrapObject({x, y});

c.addConstraints(function(obj) {
    obj.y = 2 * obj.x;
}, obj);

console.log(obj);                   // { x:0, y: 0 }
console.log(`x = ${x}, y = ${y}`);  // x = 5, y = 10

obj.x = 50;

console.log(obj);                   // { x:50, y:100 }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.