px2

1.1.38 • Public • Published

PX2 - a Backbone style framework

PX2 is an MV*, Backbone-like framework with a more consistent design:

  • objects are also containers, of type Model or View

  • named getters and setters, including loud getters

  • containers can be specialized so they only contain a specific type of object and also contain members

  • Views derived from Model, so they can have members and getter/setters and be containers

  • objects that 'contain' other objects (by using 'set', 'add', 'push' or 'insertAt' methods) are passed messages from the objects they contain, just like Backbone collections but more general.

  • iterators (start,end,next,prev,current), standard mapping functions (map, each, forIn) and sorting.

If you don't like the fact you can't have collections of collections or that views and collections can't have settable members in Backbone, then PX2 is for you.

Installation

npm install --save px2
bower install --save px2

Dependencies

jQuery if you use Views, none otherwise.

Examples

 See the examples/ folder.

Model methods

 this.create(name, value, silent):

   Creates a new member variable called 'name' with initial value
   of 'value'. Also creates named getters/setters, this.name() and
   this.name(value). Sends 'create' and 'create:$name' messages
   with value as event.value unless silent is true.

 this.copy():

   Returns a deep copy of this.

 this.set(name, value, silent)

   Standard setter for Model objects. Sends "change" and
   "change:$name" events when called, unless silent is true.  The
   value of this.name() is the new value and the event.value
   (passed as the first part of 'change' trigger handler) is the
   old value that this.name() was set to.

 this.get(name, loud)

   Standard getter for Model objects, sends 'get' and 'get:$name'
   messages. Will send 'get' and 'get:$name' messages if 'loud' is
   true, with the event value being the previous value of
   this.name(). This message is sent before the value is returned
   allowing the opportuntity to change the value that is going to
   be returned to the calling function.  This allows for the
   creation of named variables that return a different value
   everytime they are called:

      // Create a random variable
      obj.create('random');
      obj.on('get:random', function (e) {
          return this.random(Math.random());
      });
      var a = obj.get('random', true);
      expect(obj.get('random', true)).to.not.equal(a);

 this.add(value, silent)

   Add value to the collection if it is not already in the
   collection and sends "add" with event.target being the value
   added and "modified" with event.target being an array with the
   value added, unless silent is true.
   
 this.insertAt(i, value, silent)

   Add value to the collection at index i. Sends "add" with
   event.target being the value added and "modified" with
   event.target being an array with the value added, unless silent
   is true.
   
 this.push(value, silent)

   Add value to the head of the collection. Sends "add" with
   event.target being the value added and "modified" with
   event.target being an array with the value added, unless silent
   is true. Much faster than add, since it doesn't have to scan
   the entire storage looking for a duplicate.
   
 this.swap(i, j, silent)

   Exhanges two values in the collection. Sends "modified" with
   event.target being an array with the values swapped, unless
   silent is true.
   
 this.remove(value, silent)

   Remove value from the collection if it has been added to it
   using add/push/insertAt. Sends "add" with event.target being
   the value added and "modified" with event.target being an array
   with the value added, unless silent is true. Returns true if
   the object was removed, false otherwise.

 this.at(index)

   Return an element in the collection located at index.  Throws
   Error if index is out of range.
   
 this.indexOf(obj)

   Return the integer index of obj in storage (for use with at), or
   undefined if the object is not in the collection.
   
 this.length:

   The number of elements contained in the object storage.

 this.clear(silent)

   Remove all items from the collection and sets length to 0;
   sends "modified" message with all items from storage before
   clearing, unles silent is true.

 this.on(message, function (event) { ... }, self)

   Intercept message with handler, setting this to self during
   call of handler.  If 'self' is omitted, 'this' is used.
   'event' contains event.value, which is the value passed to
   trigger, and event.target which will be the object that
   triggered the message, in most cases 'this'.

   If the event handler returns true, messages will continue to
   propagate up the containment chain, else it will stop at the
   handling object.

 this.once(message, function (event) { ... }, self)

   Like this.on, but the handler is only called once, after which
   it is removed from the handler action table.

 this.trigger(message, value)

   Send the message to this, and then all containing parents,
   recursively up the containment tree.  Propagation stops when
   the message is intercepted and handled, unless the handler
   returns true (and only true, not truthy).

 this.each(function (value) { ... }, self)

   Side effects only iteration over elements. 'self' defaults to
   'this' if not provided.

 this.map(function (value) {...}, self)

   Returns an array of returned values from function. 'self'
   defaults to 'this' if not provided.

 this.forIn(function (key, value) { ... }, self)

   Side effects only mapping over members (created with
   this.create()); 'self' defaults to 'this' if not provided.

 this.find(object || function (value) ...)

   Find a specific object in the collection.  If the argument is
   an object, === is used, if a function it is called with each
   element of the collection until it returns true. Returns the
   object, if it is found.

 this.sort(fun, silent)

    Destructively sorts the internal storage array using fun,
    returns 'this' so you can chain with map. Sends "modified"
    event with the sorted array, unless silent is true.

Iterators

  All iterators send "change" and "change:current" messages unless
  silent is true, with the event.value being the previous value of
  this.current().

  Note: the pattern for iterators is slightly different than other
  languages:

      for(var i = this.start(); i; i = this.next()) { ... }

  or in reverse:
  
      for(var i = this.end(); i; i = this.prev()) { ... }

  this.start(silent)

    Select the first element of storage as current.

  this.end(silent)

    Select the last element of storage as current.

  this.current(objOrNumber, silent)

    Return the current element with no aruments.  With obj as an
    argument, select that object as the current object.  With a
    number, select the object at that index in storage as current.

 this.next(loop, silent)

    Return the next element and set it as current, or undefined if
    at the end of storage.  If loop is true, loop around to the
    start and return the first element.
    
 this.prev(loop, silent)

    Return the previous element and set it as current, or
    undefined if at the start of storage.  If loop is true, loop
    around to the end and return the last element.

Events

  An event is passed to a trigger handler.  An event includes the
  value and target fields; value is the value passed to
  set/add/push/etc, and target is the object that initiated the
  event.

Serialization

   Serialization is working but some parts are experiemntal, so
   don't expect it to work perfectly yet with all/complex
   Models/Views.  Try it at first with simple objects that have
   only members and storage and not complex actions (trigger that
   set setf to something other than this) See test/serialize.js
   for more info on what works and what doesn't..

   this.serialize()

      Return an object containing all members, storage and actions
      that can be possibly passed to JSON.stringify() for storage.

   this.load(object, load-actions)

      Load 'object' into this.  'Object' should be something that
      was returned from this.serialize(), probably after being
      JSONified and back. If load actions is false, actions are
      not loaded into this, hopefully keeping them from getting
      trashed during the load (needs more testing and
      experimentation).

Example of defining and using a Model as an object and a container

var Model = require("px2").Model;

// Declare object class data
var Point = Model({
    // Optional, but good for debugging and containers
    type: 'Point'

    // optional, create members and set default values 
    defaults: {
        system: "cartesian"
    },

    // Constructor, called during instatiation
    init: function (x, y) {
        this.create('x', x);
        this.create('y', y);
    }

    // A random method
    toString: function () {
        return "x: " + this.x() + ", y: " + this.y() + " " + this.system();
    }
})

// Delcare a container
var Points = Model({
    type: 'Points',

    // This container contatins type...
    contains: 'Point'

    init: function () {
        this.create('currentPoint'); // undefined initially

        this.on('add', function (e) {
            // Use a named setter to set the current value of member 'currentPoint'.
            // e is passed to all trigger functions and it contains:
            //          e.value: the value that was added/removed/updated/etc
            //          e.target: the object that was updated, in this case it would be 'this'
            this.currentPoint(e.value);
        });
        this.on('remove', function (e) {
            if(e.value === this.currentPoint()) {
                this.currenPoint(undefined);
            }
        });
    }
});

// Instantiate a Points collection
var points = new Points();

points.add(new Point(100, 200));

var point = points.at(0);

var xs = points.map(function (point) {
    return point.x();
}); // => [100]

Views

Views are derived Model and contain all features and methods that Models give.

View example

var PointsView = View({
    // optional, but handy for debugging and is the default class if
    // className is not defined (below)
    type: 'PointsView',

    // Set the name of the variable used to reference the model,
    // in this case this.points rather than this.model
    model "points"

    // optional, sets default tag name of this.$el, else is "div"
    tagName: "span",  

    // optional, sets default class of this.$el. If not provided
    // the class name is set the the value of type (above). If the
    // string starts with a space, the provided class name(s) will be
    // appended to the value of type. ie: a value of className: " window"
    // will result in a final class name of "PointsView window"
    // in this case.
    className: " window",

    // optional, name for this.model. In this case, you could use
    // this.points to reference your model. The model is not
    // enclosed in the object, so messages from the model are
    // not propagated into this View.
    model: 'points',

    // optional css initial style, using jquery syntax
    style: {
           "font-weight": "bold"
    }

    // events, just like backbone
    events: {
        'click': function (e) {
                 alert(this.message()); // 'this' is bound for you automagically
        }
    }

    init: function (model, value) {
          this.create('value', value);
          this.create('message', "Hello World!");

          // eithefq this.model or this.points can be used, due to
          // the 'model' option used above
          this.points.on('add', this.render); // 'this' is bound automagically

          // this.render() is automatically called at the end of init for Views.
    }

    // render, which if you don't provide a default is given that
    // just returns this.$el
    render: {
            return this.$el.html(this.points.map(function (v) {
                return $('<div>').text(v.toString());
            }));
    }
})

var points = new Points();

// A View constructor is similar to a Model constructor, but the
// first argument is *always* the model for the view.
var pointsView = new PointsView(points, 1);
$('document').ready(function () {
    $('body').html(pointsView.$el);
});

WTF is this written in?

PX2 is written in Parenscript: https://github.com/vsedach/Parenscript

To build from source you need sigil: https://github.com/burtonsamograd/sigil

--

Burton Samograd burton.samograd@gmail.com

Twitter: @kruhft

2015

Package Sidebar

Install

npm i px2

Weekly Downloads

4

Version

1.1.38

License

MIT

Last publish

Collaborators

  • burtonsamograd