<commit_before>'use strict';

/**
 * restposten is a data restposten module for flatiron. It's an adaptation of
 * resourceful and is intended to be used as a replacement for Resourceful. It
 * uses JSON Schema for data validation. It also has support for JSON Schema
 * links, which are directly mapped to relations in the internal data model.
 *
 * @module restposten
 */

var events = require('events');
var clone = require('clone');
var append = require('append');
var errs = require('errs');
var inflection = require('i')();
var pluralize = inflection.pluralize;
var camelize = inflection.camelize;
var errors = require('./errors.js');

exports.resources = {};
exports.deferredRelations = {};
exports.database;
exports.validator;

/**
 * Registers a resource.
 *
 * @param {String}
 *                name
 * @param {Resource}
 *                resource
 */
exports.register = function(name, resource) {
  exports.resources[name] = resource;
};

/**
 * Unregisters a resource.
 *
 * @param {String}
 *                name
 */
exports.unregister = function(name) {
  delete this.resources[name];
};

/**
 * Defines a new schema factory for creating instances of schemas.
 *
 * @param {String}
 *                name lower case singular name of the schema (e.g. `'address'`)
 * @param {Object}
 *                schema JSON schema object
 *
 * @returns factory function for creating new instances
 */
exports.define = function(name, schema) {
  var resource = new Resource(name, schema);

  // register resource
  exports.register(name, resource);

  // check if any deferred relationships from previously defined resources are
  // related to this resource
  if (typeof exports.deferredRelations[name] != 'undefined') {
    // for every deferredRelationship we find for our current resource, resolve
    // it
    exports.deferredRelations[name].forEach(function(r) {
      resolveRelations(r.resource);
    });
  }

  // resolve the relations for the newly created resource
  resolveRelations(resource);
  resource.init();

  return resource;
};

/**
 * Resolves the relations for a resource.
 *
 * @param {Resource}
 *                resource
 *
 * @private
 */
function resolveRelations(resource) {
  var props = resource.schema.properties;

  // go through the schema's properties and check for links with "rel": "full"
  Object.keys(props).forEach(function (key) {
    var property = props[key];
    if (typeof property.links == 'undefined')
      return;

    var links = property.links;
    var len = links.length;
    var link;
    for (var i = 0; i < len; i++) {
      link = links[i];
      if (link.rel === 'full')
        foreignKey(resource, key, link.href);
    }
  });
}

/**
 * Creates a foreign key relationship. (One-To-Many)
 *
 * @param {Resource}
 *                from
 *
 * @private
 */
function foreignKey(from, propertyName, href) {
  var components = href.split('/');

  // this method will only work for standard values like href = 'author/{@}'
  if (components.length != 2 && components[1] !== "{@}")
    return;

  var otherResourceName = components[0];

  // if other schema is not yet defined, defer relationship
  if (typeof exports.resources[otherResourceName] == 'undefined') {
    var rel = {
      name: from.name,
      resource: from,
      property: propertyName
    };

    if (typeof exports.deferredRelations[otherResourceName] == 'undefined')
      exports.deferredRelations[otherResourceName] = [];

    exports.deferredRelations[otherResourceName].push(rel);

    return;
  }

  var getAll = 'get' + camelize(pluralize(from.name));

  // define function to get the referenced collection
  // e.g. getBooks()
  var other = exports.resources[otherResourceName];
  other.Instance.prototype[getAll] = function(callback) {
    var query = {};
    query[propertyName] = this.properties._id;
    from.get(query, callback);
  };

  var getOne = 'get' + camelize(otherResourceName);

  // define function to get the referenced document
  // e.g. getAuthor()
  from.Instance.prototype[getOne] = function(callback) {
    other.getOne(this.properties[propertyName], callback);
  }
};

/**
 * Creates a resource with a name and a schema.
 *
 * @constructor
 * @classdesc Resources are used to abstract away data management and
 *            validation.
 *
 * @param {String}
 *                name name of the resource
 * @param {Object}
 *                schema JSON Schema for validation of instances
 */
function Resource(name, schema) {
  var self = this;
  this.schema = schema;
  this.name = name;

  // constructor for instances of this resource
  this.Instance = function(properties) {
    var inst = this;

    // define not-enumerable properties
    Object.defineProperty(this, 'resource', {
      enumerable: false,
      value: self
    });

    Object.defineProperty(this, 'properties', {
      enumerable: false,
      set: function (properties) {
        // remove old keys
        Object.keys(inst).forEach(function (k) {
          delete inst[k];
        });

        // append new
        append(inst, properties);
      },
      get: function () {
        return properties;
      }
    });

    // mix all properties from properties into this
    append(this, properties);
  };
  this.Instance.prototype = new ResourceInstance;
}
Resource.prototype = new events.EventEmitter;
exports.Resource = Resource;

/**
 * Emits the 'init' event.
 *
 * @private
 */
Resource.prototype.init = function() {
  this.emit('init', this);
};

/**
 * Creates a new instance of the resource and saves it.
 *
 * @param {Object}
 *                properties properties, the resulting instance will have
 * @param {Function(err,instance)}
 *                callback
 */
Resource.prototype.create = function(properties, callback) {
  var instance = this.instantiate(properties);
  instance.save(callback);
};

/**
 * Creates an instance with some properties.
 *
 * @param {Object}
 *                properties properties, the resulting instance will have
 *
 * @returns {ResourceInstance} new instance
 */
Resource.prototype.instantiate = function(properties) {
  return new (this.Instance)(properties);
};

Resource.prototype.__defineGetter__('_resource', function() {
  return this.name;
});

Resource.prototype.__defineGetter__('lowerResource', function() {
  return pluralize(this.name);
});

/**
 * Creates an instance of a resource. Instances are usually created via
 * `Resource.create()`.
 *
 * @constructor
 * @classdesc Resources are used to abstract away data management and
 *            validation.
 *
 * @param {String}
 *                name name of the resource
 * @param {Object}
 *                schema JSON Schema for validation of instances
 */
function ResourceInstance() {
}
exports.ResourceInstance = ResourceInstance;

/**
 * Validates itself by using the global JSON Schema validator.
 *
 * @returns {Object[]} an array of objects that contain error information
 */
ResourceInstance.prototype.validate = function() {
  return exports.validator.validate(this.properties, this.resource.schema);
};

/**
 * Get an array of matching instances.
 *
 * @param {String|Object}
 *                query _id or query object that all resulting instances match
 * @param {Object}
 *                [options]
 * @param {Function(err,instances)}
 *                callback
 */
Resource.prototype.get = function (query, options, callback) {
  if (arguments.length == 2) {
    callback = options;
    options = {};
  }

  var resource = this;

  if (typeof query == 'string')
    query = { _id: query };

  var collName = pluralize(this.name);

  exports.database.getCollection(collName, function(err, coll) {
    if (err)
      return callback(err);

    coll.find(query, options, function (err, results) {
      if (err)
        return callback(err);

      // instantiate all objects
      var len = results.length;
      for (var i = 0; i < len; i++) {
        results[i] = resource.instantiate(results[i]);
      }
      callback(null, results);
    });
  });
};

/**
 * Get the first matching instance.
 *
 * @param {String|Object}
 *                query _id or query object that all resulting instances match
 * @param {Object}
 *                [options]
 * @param {Function(err,instance)}
 *                callback
 */
Resource.prototype.getOne = function (query, options, callback) {
  if (arguments.length == 2) {
    callback = options;
    options = {};
  }

  var resource = this;

  if (typeof query == 'string')
    query = { _id: query };

  var collName = pluralize(this.name);

  exports.database.getCollection(collName, function(err, coll) {
    if (err)
      return callback(err);

    coll.findOne(query, options, function (err, result) {
      if (err)
        return callback(err);

      // instantiate object
      result = resource.instantiate(result);

      callback(null, result);
    });
  });
};

/**
 * Get an array of all instances of the schema.
 *
 * @param {Object}
 *                [options]
 * @param {Function(err,instances)}
 *                callback
 */
Resource.prototype.all = function(options, callback) {
  if (arguments.length == 1) {
    callback = options;
    options = {};
  }

  // query all schema instances
  this.get({}, options, callback);
};

/**
 * Saves itself to the database. If the instance already exists in the database,
 * the old instance is replaced.
 *
 * @param {Object}
 *                [options]
 * @param {Function(err,saved)}
 *                callback
 */
ResourceInstance.prototype.save = function(options, callback) {
  if (arguments.length == 1) {
    callback = options;
    options = {};
  }

  var instance = this;

  // validate instance
  var errors = this.validate();
  if (errors.length > 0) {
    return callback(errors);
  }

  // collection name is the plural of resource name
  var collName = pluralize(this.resource.name);

  // get collection and save to the database
  exports.database.getCollection(collName, function(err, coll) {
    if (err)
      return callback(err);

    // FIXME callback must receive new instance as a result
    coll.save(instance.properties, options, function(err, saved) {
      if (typeof saved == 'number')
        return callback(null, instance);

      instance.properties = saved;
      callback(null, instance);
    });
  });
};

/**
 * Deletes the instance.
 *
 * @param {String}
 *                id id of the instance
 * @param {Function(err,deleted)}
 *                callback
 */
Resource.prototype.delete = function(id, callback) {
  var collName = pluralize(this.name);

  exports.database.getCollection(collName, function (err, coll) {
    if (err)
      return callback(err);

    coll.delete({ _id: id }, callback);
  });
};
<commit_msg>Fix error messages<commit_after>'use strict';

/**
 * restposten is a data restposten module for flatiron. It's an adaptation of
 * resourceful and is intended to be used as a replacement for Resourceful. It
 * uses JSON Schema for data validation. It also has support for JSON Schema
 * links, which are directly mapped to relations in the internal data model.
 *
 * @module restposten
 */

var events = require('events');
var clone = require('clone');
var append = require('append');
var errs = require('errs');
var inflection = require('i')();
var pluralize = inflection.pluralize;
var camelize = inflection.camelize;
var errors = require('./errors.js');

exports.resources = {};
exports.deferredRelations = {};
exports.database;
exports.validator;

/**
 * Registers a resource.
 *
 * @param {String}
 *                name
 * @param {Resource}
 *                resource
 */
exports.register = function(name, resource) {
  exports.resources[name] = resource;
};

/**
 * Unregisters a resource.
 *
 * @param {String}
 *                name
 */
exports.unregister = function(name) {
  delete this.resources[name];
};

/**
 * Defines a new schema factory for creating instances of schemas.
 *
 * @param {String}
 *                name lower case singular name of the schema (e.g. `'address'`)
 * @param {Object}
 *                schema JSON schema object
 *
 * @returns factory function for creating new instances
 */
exports.define = function(name, schema) {
  var resource = new Resource(name, schema);

  // register resource
  exports.register(name, resource);

  // check if any deferred relationships from previously defined resources are
  // related to this resource
  if (typeof exports.deferredRelations[name] != 'undefined') {
    // for every deferredRelationship we find for our current resource, resolve
    // it
    exports.deferredRelations[name].forEach(function(r) {
      resolveRelations(r.resource);
    });
  }

  // resolve the relations for the newly created resource
  resolveRelations(resource);
  resource.init();

  return resource;
};

/**
 * Resolves the relations for a resource.
 *
 * @param {Resource}
 *                resource
 *
 * @private
 */
function resolveRelations(resource) {
  var props = resource.schema.properties;

  // go through the schema's properties and check for links with "rel": "full"
  Object.keys(props).forEach(function (key) {
    var property = props[key];
    if (typeof property.links == 'undefined')
      return;

    var links = property.links;
    var len = links.length;
    var link;
    for (var i = 0; i < len; i++) {
      link = links[i];
      if (link.rel === 'full')
        foreignKey(resource, key, link.href);
    }
  });
}

/**
 * Creates a foreign key relationship. (One-To-Many)
 *
 * @param {Resource}
 *                from
 *
 * @private
 */
function foreignKey(from, propertyName, href) {
  var components = href.split('/');

  // this method will only work for standard values like href = 'author/{@}'
  if (components.length != 2 && components[1] !== "{@}")
    return;

  var otherResourceName = components[0];

  // if other schema is not yet defined, defer relationship
  if (typeof exports.resources[otherResourceName] == 'undefined') {
    var rel = {
      name: from.name,
      resource: from,
      property: propertyName
    };

    if (typeof exports.deferredRelations[otherResourceName] == 'undefined')
      exports.deferredRelations[otherResourceName] = [];

    exports.deferredRelations[otherResourceName].push(rel);

    return;
  }

  var getAll = 'get' + camelize(pluralize(from.name));

  // define function to get the referenced collection
  // e.g. getBooks()
  var other = exports.resources[otherResourceName];
  other.Instance.prototype[getAll] = function(callback) {
    var query = {};
    query[propertyName] = this.properties._id;
    from.get(query, callback);
  };

  var getOne = 'get' + camelize(otherResourceName);

  // define function to get the referenced document
  // e.g. getAuthor()
  from.Instance.prototype[getOne] = function(callback) {
    other.getOne(this.properties[propertyName], callback);
  }
};

/**
 * Creates a resource with a name and a schema.
 *
 * @constructor
 * @classdesc Resources are used to abstract away data management and
 *            validation.
 *
 * @param {String}
 *                name name of the resource
 * @param {Object}
 *                schema JSON Schema for validation of instances
 */
function Resource(name, schema) {
  var self = this;
  this.schema = schema;
  this.name = name;

  // constructor for instances of this resource
  this.Instance = function(properties) {
    var inst = this;

    // define not-enumerable properties
    Object.defineProperty(this, 'resource', {
      enumerable: false,
      value: self
    });

    Object.defineProperty(this, 'properties', {
      enumerable: false,
      set: function (properties) {
        // remove old keys
        Object.keys(inst).forEach(function (k) {
          delete inst[k];
        });

        // append new
        append(inst, properties);
      },
      get: function () {
        return properties;
      }
    });

    // mix all properties from properties into this
    append(this, properties);
  };
  this.Instance.prototype = new ResourceInstance;
}
Resource.prototype = new events.EventEmitter;
exports.Resource = Resource;

/**
 * Emits the 'init' event.
 *
 * @private
 */
Resource.prototype.init = function() {
  this.emit('init', this);
};

/**
 * Creates a new instance of the resource and saves it.
 *
 * @param {Object}
 *                properties properties, the resulting instance will have
 * @param {Function(err,instance)}
 *                callback
 */
Resource.prototype.create = function(properties, callback) {
  var instance = this.instantiate(properties);
  instance.save(callback);
};

/**
 * Creates an instance with some properties.
 *
 * @param {Object}
 *                properties properties, the resulting instance will have
 *
 * @returns {ResourceInstance} new instance
 */
Resource.prototype.instantiate = function(properties) {
  return new (this.Instance)(properties);
};

Resource.prototype.__defineGetter__('_resource', function() {
  return this.name;
});

Resource.prototype.__defineGetter__('lowerResource', function() {
  return pluralize(this.name);
});

/**
 * Creates an instance of a resource. Instances are usually created via
 * `Resource.create()`.
 *
 * @constructor
 * @classdesc Resources are used to abstract away data management and
 *            validation.
 *
 * @param {String}
 *                name name of the resource
 * @param {Object}
 *                schema JSON Schema for validation of instances
 */
function ResourceInstance() {
}
exports.ResourceInstance = ResourceInstance;

/**
 * Validates itself by using the global JSON Schema validator.
 *
 * @returns {Object[]} an array of objects that contain error information
 */
ResourceInstance.prototype.validate = function() {
  var errs = exports.validator.validate(this.properties, this.resource.schema);

  // transform the errors into a more convenient format
  var len = errs.length;
  var i, e1, e2;
  for (i = 0; i < len; i++) {
    e1 = errs[i];

    e2 = {
      errorType: e1.kind,
      errorMessage: e1.desc
    };

    var context = e1.instanceContext;

    if (context !== "#")
      e2.property = context.replace(/^#\//, '').replace(/\//, '.');

    e2.requirement = {};
    e2.requirement[e1.constraintName] = e1.constraintValue;
    e2.given = e1.value;

    errs[i] = e2;
  }

  return errs;
};

/**
 * Get an array of matching instances.
 *
 * @param {String|Object}
 *                query _id or query object that all resulting instances match
 * @param {Object}
 *                [options]
 * @param {Function(err,instances)}
 *                callback
 */
Resource.prototype.get = function (query, options, callback) {
  if (arguments.length == 2) {
    callback = options;
    options = {};
  }

  var resource = this;

  if (typeof query == 'string')
    query = { _id: query };

  var collName = pluralize(this.name);

  exports.database.getCollection(collName, function(err, coll) {
    if (err)
      return callback(err);

    coll.find(query, options, function (err, results) {
      if (err)
        return callback(err);

      // instantiate all objects
      var len = results.length;
      for (var i = 0; i < len; i++) {
        results[i] = resource.instantiate(results[i]);
      }
      callback(null, results);
    });
  });
};

/**
 * Get the first matching instance.
 *
 * @param {String|Object}
 *                query _id or query object that all resulting instances match
 * @param {Object}
 *                [options]
 * @param {Function(err,instance)}
 *                callback
 */
Resource.prototype.getOne = function (query, options, callback) {
  if (arguments.length == 2) {
    callback = options;
    options = {};
  }

  var resource = this;

  if (typeof query == 'string')
    query = { _id: query };

  var collName = pluralize(this.name);

  exports.database.getCollection(collName, function(err, coll) {
    if (err)
      return callback(err);

    coll.findOne(query, options, function (err, result) {
      if (err)
        return callback(err);

      // instantiate object
      result = resource.instantiate(result);

      callback(null, result);
    });
  });
};

/**
 * Get an array of all instances of the schema.
 *
 * @param {Object}
 *                [options]
 * @param {Function(err,instances)}
 *                callback
 */
Resource.prototype.all = function(options, callback) {
  if (arguments.length == 1) {
    callback = options;
    options = {};
  }

  // query all schema instances
  this.get({}, options, callback);
};

/**
 * Saves itself to the database. If the instance already exists in the database,
 * the old instance is replaced.
 *
 * @param {Object}
 *                [options]
 * @param {Function(err,saved)}
 *                callback
 */
ResourceInstance.prototype.save = function(options, callback) {
  if (arguments.length == 1) {
    callback = options;
    options = {};
  }

  var instance = this;

  // validate instance
  var errors = this.validate();
  if (errors.length > 0) {
    return callback(errors);
  }

  // collection name is the plural of resource name
  var collName = pluralize(this.resource.name);

  // get collection and save to the database
  exports.database.getCollection(collName, function(err, coll) {
    if (err)
      return callback(err);

    // FIXME callback must receive new instance as a result
    coll.save(instance.properties, options, function(err, saved) {
      if (typeof saved == 'number')
        return callback(null, instance);

      instance.properties = saved;
      callback(null, instance);
    });
  });
};

/**
 * Deletes the instance.
 *
 * @param {String}
 *                id id of the instance
 * @param {Function(err,deleted)}
 *                callback
 */
Resource.prototype.delete = function(id, callback) {
  var collName = pluralize(this.name);

  exports.database.getCollection(collName, function (err, coll) {
    if (err)
      return callback(err);

    coll.delete({ _id: id }, callback);
  });
};
<|endoftext|>


<commit_before>
$(function() {

    function syncValue(args) {
        var el = $('#' + args.key);
        el.val(args.chart.get('metadata.visualize.'+args.key));
        function save() {
            args.chart.set('metadata.visualize.'+args.key, el.val());
        }
        el.change(save).keyup(save);
    }

    function syncCheckbox(args) {
        var el = $('#'+args.key);
        if (args.chart.get('metadata.visualize.'+args.key))
            el.prop('checked', 'checked');
        else
            el.removeAttr('checked');
        el.change(function() {
            args.chart.set('metadata.visualize.'+args.key, el.is(':checked'));
        });
    }

    function syncRadio(args) {
        var curVal = args.chart.get('metadata.visualize.'+args.key);
        if (_.isBoolean(curVal)) {
            curVal = curVal ? 'yes' : 'no';
        }
        $('input:radio[name='+args.key+'][value='+curVal+']').prop('checked', true);
        $('input:radio[name='+args.key+']').change(function() {
            var val = $('input:radio[name='+args.key+']:checked').val();
            if (val === 'yes') val = true;
            else if (val === 'no') val = false;
            args.chart.set('metadata.visualize.'+args.key, val);
        });
    }

    function syncSelectAxisColumn(args) {
        _.each(args.option.axes, function(axis) {
            var select = $('select#'+args.key+'-'+axis.id).html(''),
                chart = args.chart,
                dataset = chart.dataset(),
                axisMeta = args.vis.meta.axes[axis.id],
                defCol = args.vis.axes()[axis.id];

            if (axisMeta.optional) {
                $('<option />').text('--').appendTo(select);
            }

            // populate select with columns that match accepted types for axis
            dataset.eachColumn(function(column) {
                if (_.indexOf(axisMeta.accepts, column.type()) > -1) {
                    $('<option />')
                        .attr('value', column.name())
                        .text(column.title())
                        .prop('selected', column.name() == defCol)
                        .appendTo(select);
                }
            });

            select.change(function() {
                var axes = _.clone(chart.get('metadata.axes', {}));
                axes[axis.id] = select.val();
                chart.set('metadata.axes', axes);
            });

        });
    }

    function syncColumnSelect(args) {

         var select = $('select#'+args.key).html(''),
            chart = args.chart,
            opts = args.option,
            dataset = chart.dataset(),
            accepts = opts.accepts || ['number', 'text', 'date'],
            curVal = args.chart.get('metadata.visualize.'+args.key),
            defCol = curVal ? curVal : (opts.optional ? '{{(none)}}' : dataset.column(0).name());

        if (opts.optional) {
            // add 'no selection' option
            $('<option value="{{(none)}}">—</option>')
                .prop('selected', defCol == '{{(none)}}')
                .appendTo(select);
        }
        // populate select with columns that match accepted types
        dataset.eachColumn(function(column) {
            if (_.indexOf(accepts, column.type()) > -1) {
                $('<option />')
                    .attr('value', column.name())
                    .html(column.title())
                    .prop('selected', column.name() == defCol)
                    .appendTo(select);
            }
        });
        // listen to user changes to this control
        select.change(function() {
            var v = select.val();
            chart.set('metadata.visualize.'+args.key, v == '{{(none)}}' ? null : v);
        });
    }

    function syncCustomRange(args) {
        var curVal = args.chart.get('metadata.visualize.'+args.key) || ['',''],
            ui = $('#vis-options-'+args.key),
            min = $('.min', ui),
            max = $('.max', ui);
        // propagate
        min.val(curVal[0]);
        max.val(curVal[1]);
        // listen
        $('input', ui).change(function() {
            var v = [tonum(min.val()), tonum(max.val())];
            if (v[0] === '' && v[1] === '') v = null;
            args.chart.set('metadata.visualize.'+args.key, v);
        });

        function tonum(v) {
            return v.trim() === '' ? '' : +v;
        }
    }

    function syncCustomFormat(args) {
        var curVal = args.chart.get('metadata.visualize.'+args.key),
            formats = [],
            colids = args.vis.axes()[args.option.axis],
            colid = _.isArray(colids) ? colids[0] : colids,
            axesCol = args.chart.dataset().column(colid);

        if (axesCol.type() == 'number') {
            formats = [
                { l: '0', f: 'd' },
                { l: '0.0', f: '.1f' },
                { l: '0.00', f: '.2f' },
                { l: '0.000', f: '.3f' },
                { l: '1 sign. digit', f: '.1r' },
                { l: '2 sign. digits', f: '.2r' },
                { l: '3 sign. digits', f: '.3r' },
                { l: '0%', f: '%' },
                { l: '0k', f: '.0s' },
                { l: '0.0k', f: '.1s' },
            ];
        } else if (axesCol.type() == 'date') {
            formats = [
                { l: '2015', f: '%Y' },
                { l: '’15', f: '’%y' },
                { l: 'April', f: '%B' },
                { l: 'Apr', f: '%b' },
                { l: 'Apr ’15', f: '%b ’%y' }
            ];
        }

        var select = d3.select('#'+args.key);

        select.selectAll('option')
            .data(formats)
            .enter()
            .append('option')
            .attr('value', function(d) { return d.f; })
            .text(function(d) { return d.l; });

        select.on('change', function(d) {
            args.chart.set('metadata.visualize.'+args.key, select.node().value);
        });

        select.node().value = curVal || formats[0].f;
    }

    dw.backend.on('sync-option:select', syncValue);
    dw.backend.on('sync-option:text', syncValue);
    dw.backend.on('sync-option:textarea', syncValue);
    dw.backend.on('sync-option:checkbox', syncCheckbox);
    dw.backend.on('sync-option:radio', syncRadio);
    dw.backend.on('sync-option:radio-left', syncRadio);
    dw.backend.on('sync-option:custom-range', syncCustomRange);
    dw.backend.on('sync-option:custom-format', syncCustomFormat);

    // column select
    dw.backend.on('sync-option:select-axis-column', syncSelectAxisColumn);
    dw.backend.on('sync-option:column-select', syncColumnSelect);

});

<commit_msg>failing gracefully if no axis column found<commit_after>
$(function() {

    function syncValue(args) {
        var el = $('#' + args.key);
        el.val(args.chart.get('metadata.visualize.'+args.key));
        function save() {
            args.chart.set('metadata.visualize.'+args.key, el.val());
        }
        el.change(save).keyup(save);
    }

    function syncCheckbox(args) {
        var el = $('#'+args.key);
        if (args.chart.get('metadata.visualize.'+args.key))
            el.prop('checked', 'checked');
        else
            el.removeAttr('checked');
        el.change(function() {
            args.chart.set('metadata.visualize.'+args.key, el.is(':checked'));
        });
    }

    function syncRadio(args) {
        var curVal = args.chart.get('metadata.visualize.'+args.key);
        if (_.isBoolean(curVal)) {
            curVal = curVal ? 'yes' : 'no';
        }
        $('input:radio[name='+args.key+'][value='+curVal+']').prop('checked', true);
        $('input:radio[name='+args.key+']').change(function() {
            var val = $('input:radio[name='+args.key+']:checked').val();
            if (val === 'yes') val = true;
            else if (val === 'no') val = false;
            args.chart.set('metadata.visualize.'+args.key, val);
        });
    }

    function syncSelectAxisColumn(args) {
        _.each(args.option.axes, function(axis) {
            var select = $('select#'+args.key+'-'+axis.id).html(''),
                chart = args.chart,
                dataset = chart.dataset(),
                axisMeta = args.vis.meta.axes[axis.id],
                defCol = args.vis.axes()[axis.id];

            if (axisMeta.optional) {
                $('<option />').text('--').appendTo(select);
            }

            // populate select with columns that match accepted types for axis
            dataset.eachColumn(function(column) {
                if (_.indexOf(axisMeta.accepts, column.type()) > -1) {
                    $('<option />')
                        .attr('value', column.name())
                        .text(column.title())
                        .prop('selected', column.name() == defCol)
                        .appendTo(select);
                }
            });

            select.change(function() {
                var axes = _.clone(chart.get('metadata.axes', {}));
                axes[axis.id] = select.val();
                chart.set('metadata.axes', axes);
            });

        });
    }

    function syncColumnSelect(args) {

         var select = $('select#'+args.key).html(''),
            chart = args.chart,
            opts = args.option,
            dataset = chart.dataset(),
            accepts = opts.accepts || ['number', 'text', 'date'],
            curVal = args.chart.get('metadata.visualize.'+args.key),
            defCol = curVal ? curVal : (opts.optional ? '{{(none)}}' : dataset.column(0).name());

        if (opts.optional) {
            // add 'no selection' option
            $('<option value="{{(none)}}">—</option>')
                .prop('selected', defCol == '{{(none)}}')
                .appendTo(select);
        }
        // populate select with columns that match accepted types
        dataset.eachColumn(function(column) {
            if (_.indexOf(accepts, column.type()) > -1) {
                $('<option />')
                    .attr('value', column.name())
                    .html(column.title())
                    .prop('selected', column.name() == defCol)
                    .appendTo(select);
            }
        });
        // listen to user changes to this control
        select.change(function() {
            var v = select.val();
            chart.set('metadata.visualize.'+args.key, v == '{{(none)}}' ? null : v);
        });
    }

    function syncCustomRange(args) {
        var curVal = args.chart.get('metadata.visualize.'+args.key) || ['',''],
            ui = $('#vis-options-'+args.key),
            min = $('.min', ui),
            max = $('.max', ui);
        // propagate
        min.val(curVal[0]);
        max.val(curVal[1]);
        // listen
        $('input', ui).change(function() {
            var v = [tonum(min.val()), tonum(max.val())];
            if (v[0] === '' && v[1] === '') v = null;
            args.chart.set('metadata.visualize.'+args.key, v);
        });

        function tonum(v) {
            return v.trim() === '' ? '' : +v;
        }
    }

    function syncCustomFormat(args) {
        var curVal = args.chart.get('metadata.visualize.'+args.key),
            formats = [],
            colids = args.vis.axes()[args.option.axis],
            colid = _.isArray(colids) ? colids[0] : colids,
            axesCol = colid ? args.chart.dataset().column(colid) : null;

        if (!colid) return;
        if (axesCol.type() == 'number') {
            formats = [
                { l: '0', f: 'd' },
                { l: '0.0', f: '.1f' },
                { l: '0.00', f: '.2f' },
                { l: '0.000', f: '.3f' },
                { l: '1 sign. digit', f: '.1r' },
                { l: '2 sign. digits', f: '.2r' },
                { l: '3 sign. digits', f: '.3r' },
                { l: '0%', f: '%' },
                { l: '0k', f: '.0s' },
                { l: '0.0k', f: '.1s' },
            ];
        } else if (axesCol.type() == 'date') {
            formats = [
                { l: '2015', f: '%Y' },
                { l: '’15', f: '’%y' },
                { l: 'April', f: '%B' },
                { l: 'Apr', f: '%b' },
                { l: 'Apr ’15', f: '%b ’%y' }
            ];
        }

        var select = d3.select('#'+args.key).html('');

        select.selectAll('option')
            .data(formats)
            .enter()
            .append('option')
            .attr('value', function(d) { return d.f; })
            .text(function(d) { return d.l; });

        select.on('change', function(d) {
            args.chart.set('metadata.visualize.'+args.key, select.node().value);
        });

        select.node().value = curVal || formats.length ? formats[0].f : '';
    }

    dw.backend.on('sync-option:select', syncValue);
    dw.backend.on('sync-option:text', syncValue);
    dw.backend.on('sync-option:textarea', syncValue);
    dw.backend.on('sync-option:checkbox', syncCheckbox);
    dw.backend.on('sync-option:radio', syncRadio);
    dw.backend.on('sync-option:radio-left', syncRadio);
    dw.backend.on('sync-option:custom-range', syncCustomRange);
    dw.backend.on('sync-option:custom-format', syncCustomFormat);

    // column select
    dw.backend.on('sync-option:select-axis-column', syncSelectAxisColumn);
    dw.backend.on('sync-option:column-select', syncColumnSelect);

});

<|endoftext|>


<commit_before>/**
* User interface for tagging content.
* @class
* @name $.boom.tagger
*/
$.widget( 'boom.tagger', {
/** @lends $.boom.tagger */

	/**
	default config
	@property
	*/
	options: {
		/** URL prefix for ajax calls */
		base_url : '/cms/tags/',
		/** Type. One of asset|page */
		type : 'asset',
		/** ID of the item being tagged */
		id : null
	},

	/**
	@property
	*/
	items : {},

	_create : function(){

		$.boom.log( 'tag widget created' );

		var
			self = this,
			type = this.options.type,
			id = this.options.id;

		self._bind();

		self.tag_list()
			.progress( function( $link ){

				self.remove( $link.attr( 'href' ) );

				$link.closest('li').remove();

			});

		self.picker()
			.progress( function ( tag ) {
				self.add( tag );
			});
	},

	_init : function(){

	},

	_destroy : function(){

	},

	_bind : function(){
		var self = this;

		// The add tag input box is hidden when the modal window opens.
		// Show it and give it focus when the add button is clicked.
		this.element
			.on('click', '#b-tags-add', function(){
				self.element.find( '.b-filter-input' ).show().focus();
			})
			// Hide the add tag input box when it loses focus.
			.on('blur', '.b-filter-input', function(){
				$( this ).val('').hide();
			});

	},

	/**
	Add a tag to an item.
	@param {String} tag Tag name
	*/
	add : function( tag ) {
		var
			self = this,
			type = this.options.type,
			id = this.options.id;

		$.boom.loader.show();

		$.post(
			self.options.base_url + type + '/add/' + id,
			{
				tag : tag.label
			}
			)
			.done(function(){

				$.get( self.options.base_url + type + '/list/' + id )
				.done( function( response ){
					var list = $( response ).find( 'ul' ).ui();
					self.element
					.find( 'ul' )
					.replaceWith( list );

				});
			});

		$.boom.loader.hide();
		$('#b-tags-add-name').val('').hide();
	},

	/**
	Remove a tag from an item.
	@param {String} tag Tag name
	*/
	remove : function( tag ) {
		var
			self = this,
			type = this.options.type,
			id = this.options.id;

			return $.post(
				self.options.base_url + type + '/remove/' + id,
				{tag : tag}
				);
	},

	/**
	Set up the tag list
	@param {String} selector jQuery selector for the list container
	@returns {Deferred} sends progress notfications to handle remove callbacks.
	*/
	tag_list : function() {
		var remove = new $.Deferred();

		this.element
			// When hovering over an existing tag show a button to remove the tag from the page.
			// Then hide the button again when the mouse moves away.
			.on('mouseenter', '.b-tags-list li', function(){
				// If the ui-icon and ui-icon-close clases are added in the HTML then the crosses aren't hidden when the modal opens.
				// So we only add these classes when we need to show them.
				$(this)
					.find('a')
					.addClass('ui-icon ui-icon-close')
					.show()
					.end()
					.find('span')
					.addClass('active');
				// Adding the active class changes the margin-left of the text so that it doesn't shift to the right when then delete url becomes visible.
			})
			.on('mouseleave', '.b-tags-list li', function(){
				$(this)
					.find('a')
					.hide()
					.end()
					.find('span')
					.removeClass('active');
			})
			// Remove a tag from the page.
			.on('click', '.b-tags-remove', function(event){
				event.preventDefault();

				remove.notify( $(this) );
			});

			remove.progress( function(){
				$.boom.loader.hide();
			});

			return remove;
	},

	/**
	Autocomplete UI for finding and picking tags.
	@param {Array} tags optional array of tags to filter the search by
	@returns {Deferred} Promise which resolves with the chosen tag as {label : {tag_name}, value : {tag_id} }
	*/
	picker : function( tags ){

		var
			type = this.options.type,
			add_input = this.element.find( '.b-filter-input' );

		var complete = new $.Deferred();
		tags = ( tags ) ? tags : [];

		add_input
			.autocomplete({
				delay: 200, // Time to wait after keypress before making the AJAX call.
				source: function( request, response ){
					$.ajax({
						url: '/cms/autocomplete/tags',
						dataType: 'json',
						data: {
							text : add_input.val(),
							type : (type == 'asset')? 1 : 2,
							tags : tags
						}
					})
					.done(function(data) {
						var suggestions = [];
						for ( name in data ) {
							suggestions.push({
								label : name,
								value : data[ name ]
							});
						}
						response( suggestions );
					});
				},
				select: function( event, ui ){
					event.preventDefault();
					add_input.val( '' );
					complete.notify( ui.item );
				}
			})
			.on( 'keypress', function( e ){
				// Add a tag when the enter key is pressed.
				// This allows us to add a tag which doesn't already exist.
				if (e.which == 13) {
					complete.notify( { label: add_input.val(), value: -1 } );
				}
			});

		return complete;
	}

});

/**
* User interface for searching by tag.
* @class
* @name $.boom.tagger_search
* @extends $.boom.tagger
*/
$.widget( 'boom.tagger_search', $.boom.tagger, {
	/** @lends $.boom.tagger_search */

	/**
	default config
	@property
	*/
	options: {
		/** selected tags for this search */
		selected_tag_ids : [],
		/** item type for URL fragments */
		type : 'tag'
	},

	_bind : function(){

	},

	/**
	Add a tag to the search.
	@param {String} tag Tag name
	*/
	add : function( tag ) {
		var
			self = this,
			selected_tag_ids = this.options.selected_tag_ids,
			tag_filter_list = this.element.find( 'ul.b-tags-list' );

		selected_tag_ids.push( tag.value );
		var link = $( '<a>', {
			href : '#',
			"class" : 'b-tags-remove',
			"data-tag_id" : tag.value
		});
		var label = $( '<span>').text( tag.label );

		$( '<li>' )
			.append( link )
			.append( label )
			.appendTo( tag_filter_list );

		self.do_search();
	},

	/**
	Remove a tag from the search.
	@param {String} tag Tag name
	*/
	remove : function( tag ) {
		var
			self = this,
			selected_tag_ids = this.options.selected_tag_ids;

			selected_tag_ids.splice( selected_tag_ids.indexOf( tag.value ), 1);
			self.do_search();
	},

	/**
	Autocomplete, filtering by all selected tags
	*/
	picker : function() {
		var
			self = this,
			selected_tag_ids = this.options.selected_tag_ids;

		return $.boom.tagger.prototype.picker.call( this, selected_tag_ids );
	},

	/** Perform a search based on currently selected tags */
	do_search : function() {
		var
			self = this,
			selected_tag_ids = this.options.selected_tag_ids;

		$.boom.history.load( self.options.type + '/' + selected_tag_ids.join( '-' ) );
	}

});

/**
* Deferred tagger for file uploads.
* @class
* @name $.boom.tagger_deferred
* @extends $.boom.tagger
*/
$.widget( 'boom.tagger_deferred', $.boom.tagger, {
	/** @lends $.boom.tagger_deferred */

	/**
	default config
	@property
	*/
	options: {
		/** selected tags */
		tags : [],
		/** URL prefix for ajax calls */
		base_url : '/cms/tags/',
		/** Type. One of asset|page */
		type : 'asset'
	},

	_bind : function(){

	},

	/**
	Add a tag.
	@param {String} tag Tag name
	*/
	add : function( tag ) {
		var
			self = this,
			tags = this.options.tags,
			tag_list = this.element.find( 'ul.b-tags-list' );

			tags.push( tag.label );

			var link = $( '<a>', {
				href : '#',
				"class" : 'b-tags-remove',
				"data-tag_id" : tag.value
			});
			var label = $( '<span>').text( tag.label );

			$( '<li>' )
				.append( link )
				.append( label )
				.appendTo( tag_list );
	},

	/**
	Remove a tag.
	@param {String} tag Tag name
	*/
	remove : function( tag ) {
		var
			self = this,
			tags = this.options.tags;

			tags.splice( tags.indexOf( tag.value ), 1);
	}

});<commit_msg>bugfix<commit_after>/**
* User interface for tagging content.
* @class
* @name $.boom.tagger
*/
$.widget( 'boom.tagger', {
/** @lends $.boom.tagger */

	/**
	default config
	@property
	*/
	options: {
		/** URL prefix for ajax calls */
		base_url : '/cms/tags/',
		/** Type. One of asset|page */
		type : 'asset',
		/** ID of the item being tagged */
		id : null
	},

	/**
	@property
	*/
	items : {},

	_create : function(){

		$.boom.log( 'tag widget created' );

		var
			self = this,
			type = this.options.type,
			id = this.options.id;

		self._bind();

		self.tag_list()
			.progress( function( $link ){

				self.remove( $link.attr( 'href' ) );

				$link.closest('li').remove();

			});

		self.picker()
			.progress( function ( tag ) {
				self.add( tag );
			});
	},

	_init : function(){

	},

	_destroy : function(){

	},

	_bind : function(){
		var self = this;

		// The add tag input box is hidden when the modal window opens.
		// Show it and give it focus when the add button is clicked.
		this.element
			.on('click', '#b-tags-add', function(){
				self.element.find( '.b-filter-input' ).show().focus();
			})
			// Hide the add tag input box when it loses focus.
			.on('blur', '.b-filter-input', function(){
				$( this ).val('').hide();
			});

	},

	/**
	Add a tag to an item.
	@param {String} tag Tag name
	*/
	add : function( tag ) {
		var
			self = this,
			type = this.options.type,
			id = this.options.id;

		$.boom.loader.show();

		$.post(
			self.options.base_url + type + '/add/' + id,
			{
				tag : tag.label
			}
			)
			.done(function(){

				$.get( self.options.base_url + type + '/list/' + id )
				.done( function( response ){
					var list = $( response ).find( 'ul' ).ui();
					self.element
					.find( 'ul' )
					.replaceWith( list );

				});
			});

		$.boom.loader.hide();
		$('#b-tags-add-name').val('').hide();
	},

	/**
	Remove a tag from an item.
	@param {String} tag Tag name
	*/
	remove : function( tag ) {
		var
			self = this,
			type = this.options.type,
			id = this.options.id;

			return $.post(
				self.options.base_url + type + '/remove/' + id,
				{tag : tag}
				);
	},

	/**
	Set up the tag list
	@param {String} selector jQuery selector for the list container
	@returns {Deferred} sends progress notfications to handle remove callbacks.
	*/
	tag_list : function() {
		var remove = new $.Deferred();

		this.element
			// When hovering over an existing tag show a button to remove the tag from the page.
			// Then hide the button again when the mouse moves away.
			.on('mouseenter', '.b-tags-list li', function(){
				// If the ui-icon and ui-icon-close clases are added in the HTML then the crosses aren't hidden when the modal opens.
				// So we only add these classes when we need to show them.
				$(this)
					.find('a')
					.addClass('ui-icon ui-icon-close')
					.show()
					.end()
					.find('span')
					.addClass('active');
				// Adding the active class changes the margin-left of the text so that it doesn't shift to the right when then delete url becomes visible.
			})
			.on('mouseleave', '.b-tags-list li', function(){
				$(this)
					.find('a')
					.hide()
					.end()
					.find('span')
					.removeClass('active');
			})
			// Remove a tag from the page.
			.on('click', '.b-tags-remove', function(event){
				event.preventDefault();

				remove.notify( $(this) );
			});

			remove.progress( function(){
				$.boom.loader.hide();
			});

			return remove;
	},

	/**
	Autocomplete UI for finding and picking tags.
	@param {Array} tags optional array of tags to filter the search by
	@returns {Deferred} Promise which resolves with the chosen tag as {label : {tag_name}, value : {tag_id} }
	*/
	picker : function( tags ){

		var
			type = this.options.type,
			add_input = this.element.find( '.b-filter-input' );

		var complete = new $.Deferred();
		tags = ( tags ) ? tags : [];

		console.log( type );

		add_input
			.autocomplete({
				delay: 200, // Time to wait after keypress before making the AJAX call.
				source: function( request, response ){
					$.ajax({
						url: '/cms/autocomplete/tags',
						dataType: 'json',
						data: {
							text : add_input.val(),
							type : (type == 'asset')? 1 : 2,
							tags : tags
						}
					})
					.done(function(data) {
						var suggestions = [];
						for ( name in data ) {
							suggestions.push({
								label : name,
								value : data[ name ]
							});
						}
						response( suggestions );
					});
				},
				select: function( event, ui ){
					event.preventDefault();
					add_input.val( '' );
					complete.notify( ui.item );
				}
			})
			.on( 'keypress', function( e ){
				// Add a tag when the enter key is pressed.
				// This allows us to add a tag which doesn't already exist.
				if (e.which == 13) {
					complete.notify( { label: add_input.val(), value: -1 } );
				}
			});

		return complete;
	}

});

/**
* User interface for searching by tag.
* @class
* @name $.boom.tagger_search
* @extends $.boom.tagger
*/
$.widget( 'boom.tagger_search', $.boom.tagger, {
	/** @lends $.boom.tagger_search */

	/**
	default config
	@property
	*/
	options: {
		/** selected tags for this search */
		selected_tag_ids : [],
		/** item type for URL fragments */
		tag : 'tag'
	},

	_bind : function(){

	},

	/**
	Add a tag to the search.
	@param {String} tag Tag name
	*/
	add : function( tag ) {
		var
			self = this,
			selected_tag_ids = this.options.selected_tag_ids,
			tag_filter_list = this.element.find( 'ul.b-tags-list' );

		selected_tag_ids.push( tag.value );
		var link = $( '<a>', {
			href : '#',
			"class" : 'b-tags-remove',
			"data-tag_id" : tag.value
		});
		var label = $( '<span>').text( tag.label );

		$( '<li>' )
			.append( link )
			.append( label )
			.appendTo( tag_filter_list );

		self.do_search();
	},

	/**
	Remove a tag from the search.
	@param {String} tag Tag name
	*/
	remove : function( tag ) {
		var
			self = this,
			selected_tag_ids = this.options.selected_tag_ids;

			selected_tag_ids.splice( selected_tag_ids.indexOf( tag.value ), 1);
			self.do_search();
	},

	/**
	Autocomplete, filtering by all selected tags
	*/
	picker : function() {
		var
			self = this,
			selected_tag_ids = this.options.selected_tag_ids;

		return $.boom.tagger.prototype.picker.call( this, selected_tag_ids );
	},

	/** Perform a search based on currently selected tags */
	do_search : function() {
		var
			self = this,
			selected_tag_ids = this.options.selected_tag_ids;

		$.boom.history.load( self.options.tag + '/' + selected_tag_ids.join( '-' ) );
	}

});

/**
* Deferred tagger for file uploads.
* @class
* @name $.boom.tagger_deferred
* @extends $.boom.tagger
*/
$.widget( 'boom.tagger_deferred', $.boom.tagger, {
	/** @lends $.boom.tagger_deferred */

	/**
	default config
	@property
	*/
	options: {
		/** selected tags */
		tags : [],
		/** URL prefix for ajax calls */
		base_url : '/cms/tags/',
		/** Type. One of asset|page */
		type : 'asset'
	},

	_bind : function(){

	},

	/**
	Add a tag.
	@param {String} tag Tag name
	*/
	add : function( tag ) {
		var
			self = this,
			tags = this.options.tags,
			tag_list = this.element.find( 'ul.b-tags-list' );

			tags.push( tag.label );

			var link = $( '<a>', {
				href : '#',
				"class" : 'b-tags-remove',
				"data-tag_id" : tag.value
			});
			var label = $( '<span>').text( tag.label );

			$( '<li>' )
				.append( link )
				.append( label )
				.appendTo( tag_list );
	},

	/**
	Remove a tag.
	@param {String} tag Tag name
	*/
	remove : function( tag ) {
		var
			self = this,
			tags = this.options.tags;

			tags.splice( tags.indexOf( tag.value ), 1);
	}

});<|endoftext|>


<commit_before>/* global XMLHttpRequest */
import Promise from 'bluebird'
import toPairs from 'lodash/toPairs'
import mapKeys from 'lodash/mapKeys'
import camelCase from 'lodash/camelCase'
import find from 'lodash/find'
import keys from 'lodash/keys'
import values from 'lodash/values'
import sum from 'lodash/sum'
import isEqual from 'lodash/isEqual'
import every from 'lodash/every'
import * as layers from './layers'
import Tensor from './Tensor'

/**
 * Model class
 */
export default class Model {
  /**
   * create new Model class
   * @param {object} config.filepaths
   * @param {string} config.filepaths.modelFilepath - path to model architecture configuration (json)
   * @param {string} config.filepaths.weightsFilepath - path to weights data (arraybuffer)
   * @param {string} config.filepaths.metadataFilepath - path to weights metadata (json)
   * @param {object} [config.headers] - any additional HTTP headers required for resource fetching
   * @param {boolean} [config.GPU] - enable GPU
   */
  constructor (config = {}) {
    const {
      filepaths = {},
      headers = {},
      gpu = false
    } = config

    if (!filepaths.model || !filepaths.weights || !filepaths.metadata) {
      throw new Error('File paths must be declared for model, weights, and metadata.')
    }
    this.filepaths = filepaths
    this.filetypes = {
      model: 'json',
      weights: 'arraybuffer',
      metadata: 'json'
    }

    // HTTP(S) headers used during data fetching
    this.headers = headers

    // flag to enable GPU where possible
    this.gpu = gpu

    this.data = {
      // object representing the model architecture configuration,
      // directly from the to_json() method in Keras
      model: {},
      // ArrayBuffer of all the weights, sequentially concatenated
      // see encoder.py for construction details - essentially the raw flattened
      // numerical data from the HDF5 file is extracted sequentially and concatenated.
      weights: null,
      // array of weight tensor metadata, used to reconstruct tensors from the raw
      // weights ArrayBuffer above.
      metadata: []
    }

    // keep track of XHR requests
    this.xhrs = {
      model: null,
      weights: null,
      metadata: null
    }

    // XHR progress
    this.xhrProgress = {
      model: 0,
      weights: 0,
      metadata: 0
    }

    // map of model layers
    this.modelLayersMap = new Map()

    // array of model layer names with result
    this.layersWithResults = []

    // directed acyclic graph of model network
    this.modelDAG = {}

    // input tensors
    this.inputTensors = {}

    // Promise for when Model class is initialized
    this._ready = this.initialize()

    // flag while computations are being performed
    this.isRunning = false
  }

  /**
   * Promise for when model data is loaded and layers are initialized.
   * @returns {Promise}
   */
  ready () {
    return this._ready
  }

  /**
   * Cancels any existing XHR requests
   */
  interrupt () {
    const dataTypes = ['model', 'weights', 'metdata']
    dataTypes.forEach(type => {
      if (this.xhrs[type]) {
        this.xhrs[type].abort()
        this.xhrs[type] = null
      }
    })
  }

  /**
   * Model initialization
   * @returns {Promise}
   */
  initialize () {
    const dataTypes = ['model', 'weights', 'metadata']
    return Promise.all(
      dataTypes.map(type => this.dataRequest(type, this.headers))
    )
    .then(() => {
      this.createLayers()
      return Promise.resolve()
    })
    .catch(err => {
      console.log(err)
      this.interrupt()
    })
  }

  /**
   * Makes XHR request
   * @async
   * @param {string} type - type of requested data, one of `model`, `weights`, or `metadata`.
   * @param {Object} [headers] - any XHR headers to be passed along with request
   * @returns {Promise}
   */
  dataRequest (type, headers = {}) {
    return new Promise((resolve, reject) => {
      let xhr = new XMLHttpRequest()
      xhr.open('GET', this.filepaths[type], true)
      xhr.responseType = this.filetypes[type]
      for (const [h, v] of toPairs(headers)) {
        xhr.setRequestHeader(h, v)
      }
      xhr.onload = e => {
        this.data[type] = xhr.response
        this.xhrs[type] = null
        this.xhrProgress[type] = 100
        resolve()
      }
      xhr.onprogress = e => {
        if (e.lengthComputable) {
          const percentComplete = Math.round(100 * e.loaded / e.total)
          this.xhrProgress[type] = percentComplete
        }
      }
      xhr.onerror = e => reject(e)
      xhr.send(null)
      this.xhrs[type] = xhr
    })
  }

  /**
   * Loading progress calculated from all the XHRs combined.
   * @returns {number} progress
   */
  getLoadingProgress () {
    const progressValues = values(this.xhrProgress)
    return Math.round(sum(progressValues) / progressValues.length)
  }

  /**
   * Builds network layer DAG
   *
   * For Keras models of class Sequential, we still convert the list into DAG format
   * for straightforward interoperability with graph models. We must first create an
   * Input layer as the initial layer, however.
   *
   * For class Model, the network DAG is constructed from the configuration inbound
   * and outbound nodes.
   *
   * Layer constructors take an `attrs` object, which contain layer parameters among
   * other information. Note that in the Keras model config object variables are
   * in snake_case. We convert the variable names to camelCase here.
   */
  createLayers () {
    const modelClass = this.data.model.class_name

    let modelConfig = []
    if (modelClass === 'Sequential') {
      modelConfig = this.data.model.config
    } else if (modelClass === 'Model') {
      modelConfig = this.data.model.config.layers
    }

    modelConfig.forEach((layerDef, index) => {
      const layerClass = layerDef.class_name
      const layerConfig = layerDef.config

      if (!(layerClass in layers)) {
        throw new Error(`Layer ${layerClass} specified in model configuration is not implemented!`)
      }

      // create InputLayer node for Sequential class (which is not explicitly defined in config)
      // create input tensor for InputLayer specified in Model class (layer itself created later)
      if (modelClass === 'Sequential' && index === 0) {
        const inputName = 'input'
        const inputShape = layerConfig.batch_input_shape.slice(1)
        const layer = new layers.InputLayer({
          name: inputName,
          shape: inputShape
        })
        this.modelLayersMap.set(inputName, layer)
        this.modelDAG[inputName] = {
          layerClass: 'InputLayer',
          name: inputName,
          inbound: [],
          outbound: []
        }
        this.inputTensors[inputName] = new Tensor([], inputShape, { gpu: this.gpu })
      } else if (modelClass === 'Model' && layerClass === 'InputLayer') {
        const inputShape = layerConfig.batch_input_shape.slice(1)
        this.inputTensors[layerConfig.name] = new Tensor([], inputShape, { gpu: this.gpu })
      }

      const attrs = mapKeys(layerConfig, (v, k) => camelCase(k))
      if ('activation' in attrs) {
        attrs.activation = camelCase(attrs.activation)
      }
      const layer = new layers[layerClass](attrs)

      // layer weights
      if (layer.params && layer.params.length) {
        const weights = layer.params.map(param => {
          const paramMetadata = find(this.data.metadata, meta => {
            const weightRE = new RegExp(`^${layerConfig.name}_${param}`)
            return meta.layer_name === layerConfig.name &&
              weightRE.test(meta.weight_name)
          })
          if (!paramMetadata) {
            throw new Error(`[Model] error loading weights.`)
          }

          const { offset, length, shape } = paramMetadata
          return new Tensor(new Float32Array(this.data.weights, offset, length), shape)
        })
        layer.setWeights(weights)
      }

      this.modelLayersMap.set(layerConfig.name, layer)
      this.modelDAG[layerConfig.name] = {
        layerClass,
        name: layerConfig.name,
        inbound: [],
        outbound: []
      }

      if (modelClass === 'Sequential') {
        if (index === 0) {
          const inputName = 'input'
          this.modelDAG[inputName].outbound.push(layerConfig.name)
          this.modelDAG[layerConfig.name].inbound.push(inputName)
        } else {
          const prevLayerConfig = modelConfig[index - 1].config
          this.modelDAG[layerConfig.name].inbound.push(prevLayerConfig.name)
          this.modelDAG[prevLayerConfig.name].outbound.push(layerConfig.name)
        }
      } else if (modelClass === 'Model') {
        if (layerDef.inbound_nodes && layerDef.inbound_nodes.length) {
          layerDef.inbound_nodes[0].forEach(node => {
            const inboundLayerName = node[0]
            this.modelDAG[layerConfig.name].inbound.push(inboundLayerName)
            this.modelDAG[inboundLayerName].outbound.push(layerConfig.name)
          })
        }
      }
    })
  }

  /**
   * Async function for recursively traversing the DAG
   * Graph object is stored in `this.modelDAG`, keyed by layer name.
   * Layers are retrieved from Map object `this.modelLayersMap`.
   * @async
   * @param {[]string} nodes - array of layer names
   * @returns {Promise.<boolean>}
   */
  async traverseDAG (nodes) {
    if (nodes.length === 0) {
      // Stopping criterion:
      // an output node will have 0 outbound nodes.
      return true
    } else if (nodes.length === 1) {
      // Where computational logic lives for a given layer node
      // - Makes sure results are available from inbound layer nodes
      // - Keeps generator going until results are available from inbound layer nodes
      //   (important for Merge layer nodes where multiple inbound nodes may
      //    complete asynchronously)
      // - Runs computation for current layer node: .call()
      // - Starts new generator function for outbound nodes
      const node = nodes[0]
      const { layerClass, inbound, outbound } = this.modelDAG[node]
      if (layerClass !== 'InputLayer') {
        let currentLayer = this.modelLayersMap.get(node)
        if (currentLayer.visited) {
          return false
        }

        const inboundLayers = inbound.map(n => this.modelLayersMap.get(n))
        if (!every(inboundLayers.map(layer => layer.hasResult))) {
          return false
        }

        if (layerClass === 'Merge') {
          currentLayer.result = currentLayer.call(inboundLayers.map(layer => {
            return new Tensor(layer.result.tensor.data, layer.result.tensor.shape, { gpu: this.gpu })
          }))
        } else {
          if (inboundLayers.length !== 1) {
            throw new Error(`Layer name ${currentLayer.name} has ${inboundLayers.length} inbound nodes, but is not a Merge layer.`)
          }
          const prevLayerResult = inboundLayers[0].result
          currentLayer.result = currentLayer.call(new Tensor(prevLayerResult.tensor.data, prevLayerResult.tensor.shape, { gpu: this.gpu }))
        }
        currentLayer.hasResult = true
        currentLayer.visited = true
        this.layersWithResults.push(currentLayer.name)
        await Promise.delay(0)
      }
      await this.traverseDAG(outbound)
    } else {
      for (let node of nodes) {
        await this.traverseDAG([node])
      }
    }
  }

  /**
   * Predict
   * @async
   * @param {Object} inputData - object where the keys are the named inputs of the model,
   *                             and values the TypedArray numeric data
   * @returns {Promise.<Object>} - outputData object where the keys are the named outputs
   *                             of the model, and values the TypedArray numeric data
   */
  async predict (inputData) {
    this.isRunning = true

    const inputNames = keys(this.inputTensors)
    if (!isEqual(keys(inputData), inputNames)) {
      this.isRunning = false
      throw new Error(`predict() must take an object where the keys are the named inputs of the model: ${inputNames}.`)
    }
    if (!every(inputNames, inputName => inputData[inputName] instanceof Float32Array)) {
      this.isRunning = false
      throw new Error('predict() must take an object where the values are the flattened data as Float32Array.')
    }

    // reset hasResult and visited flags in all layers
    for (let layer of this.modelLayersMap.values()) {
      layer.hasResult = false
      layer.visited = false
    }
    this.layersWithResults = []

    // load data to input tensors
    inputNames.forEach(inputName => {
      let inputLayer = this.modelLayersMap.get(inputName)
      this.inputTensors[inputName] = new Tensor(inputData[inputName], inputLayer.shape, { gpu: this.gpu })
      inputLayer.result = inputLayer.call(this.inputTensors[inputName])
      inputLayer.hasResult = true
      inputLayer.visited = true
    })

    // start traversing DAG at input
    await this.traverseDAG(inputNames)

    // outputs are layers with no outbound nodes
    const modelClass = this.data.model.class_name
    if (modelClass === 'Sequential') {
      const outputLayer = find(values(this.modelDAG), node => !node.outbound.length)
      const { result } = this.modelLayersMap.get(outputLayer.name)
      const outputData = {
        'output': result.tensor.data
      }
      this.isRunning = false
      return outputData
    } else if (modelClass === 'Model') {
      const outputLayers = values(this.modelDAG).filter(node => !node.outbound.length)
      let outputData = {}
      outputLayers.forEach(layer => {
        const { result } = this.modelLayersMap.get(layer.name)
        outputData[layer.name] = result.tensor.data
      })
      this.isRunning = false
      return outputData
    }
  }
}
<commit_msg>update Model class<commit_after>/* global XMLHttpRequest */
import Promise from 'bluebird'
import toPairs from 'lodash/toPairs'
import mapKeys from 'lodash/mapKeys'
import camelCase from 'lodash/camelCase'
import find from 'lodash/find'
import keys from 'lodash/keys'
import values from 'lodash/values'
import sum from 'lodash/sum'
import isEqual from 'lodash/isEqual'
import every from 'lodash/every'
import * as layers from './layers'
import Tensor from './Tensor'

/**
 * Model class
 */
export default class Model {
  /**
   * create new Model class
   * @param {object} config.filepaths
   * @param {string} config.filepaths.modelFilepath - path to model architecture configuration (json)
   * @param {string} config.filepaths.weightsFilepath - path to weights data (arraybuffer)
   * @param {string} config.filepaths.metadataFilepath - path to weights metadata (json)
   * @param {object} [config.headers] - any additional HTTP headers required for resource fetching
   * @param {boolean} [config.GPU] - enable GPU
   */
  constructor (config = {}) {
    const {
      filepaths = {},
      headers = {},
      gpu = false
    } = config

    if (!filepaths.model || !filepaths.weights || !filepaths.metadata) {
      throw new Error('File paths must be declared for model, weights, and metadata.')
    }
    this.filepaths = filepaths
    this.filetypes = {
      model: 'json',
      weights: 'arraybuffer',
      metadata: 'json'
    }

    // HTTP(S) headers used during data fetching
    this.headers = headers

    // flag to enable GPU where possible
    this.gpu = gpu

    this.data = {
      // object representing the model architecture configuration,
      // directly from the to_json() method in Keras
      model: {},
      // ArrayBuffer of all the weights, sequentially concatenated
      // see encoder.py for construction details - essentially the raw flattened
      // numerical data from the HDF5 file is extracted sequentially and concatenated.
      weights: null,
      // array of weight tensor metadata, used to reconstruct tensors from the raw
      // weights ArrayBuffer above.
      metadata: []
    }

    // keep track of XHR requests
    this.xhrs = {
      model: null,
      weights: null,
      metadata: null
    }

    // XHR progress
    this.xhrProgress = {
      model: 0,
      weights: 0,
      metadata: 0
    }

    // map of model layers
    this.modelLayersMap = new Map()

    // array of model layer names with result
    this.layersWithResults = []

    // directed acyclic graph of model network
    this.modelDAG = {}

    // input tensors
    this.inputTensors = {}

    // Promise for when Model class is initialized
    this._ready = this.initialize()

    // flag while computations are being performed
    this.isRunning = false
  }

  /**
   * Promise for when model data is loaded and layers are initialized.
   * @returns {Promise}
   */
  ready () {
    return this._ready
  }

  /**
   * Cancels any existing XHR requests
   */
  interrupt () {
    const dataTypes = ['model', 'weights', 'metdata']
    dataTypes.forEach(type => {
      if (this.xhrs[type]) {
        this.xhrs[type].abort()
        this.xhrs[type] = null
      }
    })
  }

  /**
   * Model initialization
   * @returns {Promise}
   */
  initialize () {
    const dataTypes = ['model', 'weights', 'metadata']
    return Promise.all(
      dataTypes.map(type => this.dataRequest(type, this.headers))
    )
    .then(() => {
      this.createLayers()
      return Promise.resolve()
    })
    .catch(err => {
      console.log(err)
      this.interrupt()
    })
  }

  /**
   * Makes XHR request
   * @async
   * @param {string} type - type of requested data, one of `model`, `weights`, or `metadata`.
   * @param {Object} [headers] - any XHR headers to be passed along with request
   * @returns {Promise}
   */
  dataRequest (type, headers = {}) {
    return new Promise((resolve, reject) => {
      let xhr = new XMLHttpRequest()
      xhr.open('GET', this.filepaths[type], true)
      xhr.responseType = this.filetypes[type]
      for (const [h, v] of toPairs(headers)) {
        xhr.setRequestHeader(h, v)
      }
      xhr.onload = e => {
        this.data[type] = xhr.response
        this.xhrs[type] = null
        this.xhrProgress[type] = 100
        resolve()
      }
      xhr.onprogress = e => {
        if (e.lengthComputable) {
          const percentComplete = Math.round(100 * e.loaded / e.total)
          this.xhrProgress[type] = percentComplete
        }
      }
      xhr.onerror = e => reject(e)
      xhr.send(null)
      this.xhrs[type] = xhr
    })
  }

  /**
   * Loading progress calculated from all the XHRs combined.
   * @returns {number} progress
   */
  getLoadingProgress () {
    const progressValues = values(this.xhrProgress)
    return Math.round(sum(progressValues) / progressValues.length)
  }

  /**
   * Builds network layer DAG
   *
   * For Keras models of class Sequential, we still convert the list into DAG format
   * for straightforward interoperability with graph models. We must first create an
   * Input layer as the initial layer, however.
   *
   * For class Model, the network DAG is constructed from the configuration inbound
   * and outbound nodes.
   *
   * Layer constructors take an `attrs` object, which contain layer parameters among
   * other information. Note that in the Keras model config object variables are
   * in snake_case. We convert the variable names to camelCase here.
   */
  createLayers () {
    const modelClass = this.data.model.class_name

    let modelConfig = []
    if (modelClass === 'Sequential') {
      modelConfig = this.data.model.config
    } else if (modelClass === 'Model') {
      modelConfig = this.data.model.config.layers
    }

    modelConfig.forEach((layerDef, index) => {
      const layerClass = layerDef.class_name
      const layerConfig = layerDef.config

      if (!(layerClass in layers)) {
        throw new Error(`Layer ${layerClass} specified in model configuration is not implemented!`)
      }

      // create InputLayer node for Sequential class (which is not explicitly defined in config)
      // create input tensor for InputLayer specified in Model class (layer itself created later)
      if (modelClass === 'Sequential' && index === 0) {
        const inputName = 'input'
        const inputShape = layerConfig.batch_input_shape.slice(1)
        const layer = new layers.InputLayer({
          name: inputName,
          shape: inputShape
        })
        this.modelLayersMap.set(inputName, layer)
        this.modelDAG[inputName] = {
          layerClass: 'InputLayer',
          name: inputName,
          inbound: [],
          outbound: []
        }
        this.inputTensors[inputName] = new Tensor([], inputShape, { gpu: this.gpu })
      } else if (modelClass === 'Model' && layerClass === 'InputLayer') {
        const inputShape = layerConfig.batch_input_shape.slice(1)
        this.inputTensors[layerConfig.name] = new Tensor([], inputShape, { gpu: this.gpu })
      }

      const attrs = mapKeys(layerConfig, (v, k) => camelCase(k))
      if ('activation' in attrs) {
        attrs.activation = camelCase(attrs.activation)
      }
      const layer = new layers[layerClass](attrs)

      // layer weights
      if (layer.params && layer.params.length) {
        const weights = layer.params.map(param => {
          const paramMetadata = find(this.data.metadata, meta => {
            const weightRE = new RegExp(`^${layerConfig.name}_${param}`)
            return meta.layer_name === layerConfig.name &&
              weightRE.test(meta.weight_name)
          })
          if (!paramMetadata) {
            throw new Error(`[Model] error loading weights.`)
          }

          const { offset, length, shape } = paramMetadata
          return new Tensor(new Float32Array(this.data.weights, offset, length), shape)
        })
        layer.setWeights(weights)
      }

      this.modelLayersMap.set(layerConfig.name, layer)
      this.modelDAG[layerConfig.name] = {
        layerClass,
        name: layerConfig.name,
        inbound: [],
        outbound: []
      }

      if (modelClass === 'Sequential') {
        if (index === 0) {
          const inputName = 'input'
          this.modelDAG[inputName].outbound.push(layerConfig.name)
          this.modelDAG[layerConfig.name].inbound.push(inputName)
        } else {
          const prevLayerConfig = modelConfig[index - 1].config
          this.modelDAG[layerConfig.name].inbound.push(prevLayerConfig.name)
          this.modelDAG[prevLayerConfig.name].outbound.push(layerConfig.name)
        }
      } else if (modelClass === 'Model') {
        if (layerDef.inbound_nodes && layerDef.inbound_nodes.length) {
          layerDef.inbound_nodes[0].forEach(node => {
            const inboundLayerName = node[0]
            this.modelDAG[layerConfig.name].inbound.push(inboundLayerName)
            this.modelDAG[inboundLayerName].outbound.push(layerConfig.name)
          })
        }
      }
    })
  }

  /**
   * Async function for recursively traversing the DAG
   * Graph object is stored in `this.modelDAG`, keyed by layer name.
   * Layers are retrieved from Map object `this.modelLayersMap`.
   * @async
   * @param {[]string} nodes - array of layer names
   * @returns {Promise.<boolean>}
   */
  async traverseDAG (nodes) {
    if (nodes.length === 0) {
      // Stopping criterion:
      // an output node will have 0 outbound nodes.
      return true
    } else if (nodes.length === 1) {
      // Where computational logic lives for a given layer node
      // - Makes sure results are available from inbound layer nodes
      // - Keeps generator going until results are available from inbound layer nodes
      //   (important for Merge layer nodes where multiple inbound nodes may
      //    complete asynchronously)
      // - Runs computation for current layer node: .call()
      // - Starts new generator function for outbound nodes
      const node = nodes[0]
      const { layerClass, inbound, outbound } = this.modelDAG[node]
      if (layerClass !== 'InputLayer') {
        let currentLayer = this.modelLayersMap.get(node)
        if (currentLayer.visited) {
          return false
        }

        const inboundLayers = inbound.map(n => this.modelLayersMap.get(n))
        if (!every(inboundLayers.map(layer => layer.hasResult))) {
          return false
        }
        // const startTime = performance.now()
        if (layerClass === 'Merge') {
          currentLayer.result = currentLayer.call(inboundLayers.map(layer => {
            return new Tensor(layer.result.tensor.data, layer.result.tensor.shape, { gpu: this.gpu })
          }))
        } else {
          if (inboundLayers.length !== 1) {
            throw new Error(`Layer name ${currentLayer.name} has ${inboundLayers.length} inbound nodes, but is not a Merge layer.`)
          }
          const prevLayerResult = inboundLayers[0].result
          currentLayer.result = currentLayer.call(new Tensor(prevLayerResult.tensor.data, prevLayerResult.tensor.shape, { gpu: this.gpu }))
        }
        // const endTime = performance.now()
        currentLayer.hasResult = true
        currentLayer.visited = true
        this.layersWithResults.push(currentLayer.name)
        // this.layerTimes.push([currentLayer.name, endTime - startTime])

        // temporarily pause 0 ms
        // useful for allowing DOM operations and other simultaneously running functions on the main thread
        await Promise.delay(0)
      }
      await this.traverseDAG(outbound)
    } else {
      for (let node of nodes) {
        await this.traverseDAG([node])
      }
    }
  }

  /**
   * Predict
   * @async
   * @param {Object} inputData - object where the keys are the named inputs of the model,
   *                             and values the TypedArray numeric data
   * @returns {Promise.<Object>} - outputData object where the keys are the named outputs
   *                             of the model, and values the TypedArray numeric data
   */
  async predict (inputData) {
    this.isRunning = true

    const inputNames = keys(this.inputTensors)
    if (!isEqual(keys(inputData), inputNames)) {
      this.isRunning = false
      throw new Error(`predict() must take an object where the keys are the named inputs of the model: ${inputNames}.`)
    }
    if (!every(inputNames, inputName => inputData[inputName] instanceof Float32Array)) {
      this.isRunning = false
      throw new Error('predict() must take an object where the values are the flattened data as Float32Array.')
    }

    // reset hasResult and visited flags in all layers
    this.layersWithResults = []
    for (let layer of this.modelLayersMap.values()) {
      layer.hasResult = false
      layer.visited = false
    }

    // load data to input tensors
    inputNames.forEach(inputName => {
      let inputLayer = this.modelLayersMap.get(inputName)
      this.inputTensors[inputName].replaceTensorData(inputData[inputName])
      inputLayer.result = inputLayer.call(this.inputTensors[inputName])
      inputLayer.hasResult = true
      inputLayer.visited = true
    })

    // start traversing DAG at input
    this.layerTimes = []
    await this.traverseDAG(inputNames)

    // outputs are layers with no outbound nodes
    const modelClass = this.data.model.class_name
    if (modelClass === 'Sequential') {
      const outputLayer = find(values(this.modelDAG), node => !node.outbound.length)
      const { result } = this.modelLayersMap.get(outputLayer.name)
      const outputData = {
        'output': result.tensor.data
      }
      this.isRunning = false
      return outputData
    } else if (modelClass === 'Model') {
      const outputLayers = values(this.modelDAG).filter(node => !node.outbound.length)
      let outputData = {}
      outputLayers.forEach(layer => {
        const { result } = this.modelLayersMap.get(layer.name)
        outputData[layer.name] = result.tensor.data
      })
      this.isRunning = false
      return outputData
    }
  }
}
<|endoftext|>


<commit_before>YUI.add('poc-page-view', function (Y) {

var POC = Y.POC,
    BasePageView,
    Page2colLeftView;

BasePageView = Y.Base.create('basePageView', Y.View, [], {
});

Y.namespace('POC').BasePageView = BasePageView;

Page2colLeftView = Y.Base.create('page2colLeftView', Y.POC.BasePageView, [], {
    initializer: function (config) {
        this.set('header', new Y.POC.HeaderView());
        this.set('sidebar', new Y.POC.SidebarView());
        this.set('main', new Y.POC.MainView());
        this.set('footer', new Y.POC.FooterView());

        // Hack: without these calls, views don't get initialized, weird stuff
        this.get('header').get('container');
        this.get('sidebar').get('container');
        this.get('main').get('container');
        this.get('footer').get('container');
    },

    render: function () {
        var container = this.get('container');

        container.get('children').remove();
        container.append(this.get('header').get('container'));
        container.append(this.get('sidebar').get('container'));
        container.append(this.get('main').get('container'));
        container.append(this.get('footer').get('container'));

        return this;
    }
});

Y.namespace('POC').Page2colLeftView = Page2colLeftView;

}, '0.0.1', {
    requires: [
        'app-base',
        'app-content',
        'app-transitions',
        'poc-header-view',
        'poc-sidebar-view',
        'poc-main-view',
        'poc-footer-view'
    ]
});<commit_msg>Added a TODO<commit_after>YUI.add('poc-page-view', function (Y) {

var POC = Y.POC,
    BasePageView,
    Page2colLeftView;

BasePageView = Y.Base.create('basePageView', Y.View, [], {
});

Y.namespace('POC').BasePageView = BasePageView;

Page2colLeftView = Y.Base.create('page2colLeftView', Y.POC.BasePageView, [], {
    initializer: function (config) {
        this.set('header', new Y.POC.HeaderView());
        this.set('sidebar', new Y.POC.SidebarView());
        this.set('main', new Y.POC.MainView());
        this.set('footer', new Y.POC.FooterView());

        // TODO: maybe find something else than this lovely hack :p
        // without these calls, views don't get initialized, weird stuff
        // Note that views get correctly initialized when calling render() on them, but
        // we can't do that since we don't want to render the views the first time (rendered by the server)
        this.get('header').get('container');
        this.get('sidebar').get('container');
        this.get('main').get('container');
        this.get('footer').get('container');
    },

    render: function () {
        var container = this.get('container');

        container.get('children').remove();
        container.append(this.get('header').get('container'));
        container.append(this.get('sidebar').get('container'));
        container.append(this.get('main').get('container'));
        container.append(this.get('footer').get('container'));

        return this;
    }
});

Y.namespace('POC').Page2colLeftView = Page2colLeftView;

}, '0.0.1', {
    requires: [
        'app-base',
        'app-content',
        'app-transitions',
        'poc-header-view',
        'poc-sidebar-view',
        'poc-main-view',
        'poc-footer-view'
    ]
});<|endoftext|>


<commit_before>var assert = require('assert')
  , jpickle = require('../lib/jpickle');

describe('pickle version 0', function() {
    it('should decode integers', function() {
        assert.equal(jpickle.loads('I1\n.'), 1);
        assert.equal(jpickle.loads('I12\n.'), 12);
    });

    it('should decode floats', function() {
        assert.equal(jpickle.loads('F3.14159\n.'), 3.14159);
    });

    it('should decode strings', function() {
        assert.equal(jpickle.loads("S'foo'\np0\n."), 'foo');
        assert.equal(jpickle.loads("S\"foo\"\np0\n."), 'foo');
    });

    it('should decode unicode', function() {
        assert.equal(jpickle.loads('VTest\xe9\np0\n.'), 'Testé');
    });

    it('should decode tuples', function() {
        assert.deepEqual(jpickle.loads("(t."), []);
        assert.deepEqual(jpickle.loads("(S'foo'\np0\ntp1\n."), ['foo']);
        assert.deepEqual(jpickle.loads("(S'foo'\np0\nS'bar'\np1\ntp2\n."), ['foo', 'bar']);
        assert.deepEqual(jpickle.loads("(S'foo'\np0\nS'bar'\np1\nS'wiz'\np2\ntp3\n."), ['foo', 'bar', 'wiz']);
    });

    it('should decode lists', function() {
        assert.deepEqual(jpickle.loads('(lp0\n.'), []);
        assert.deepEqual(jpickle.loads('(lp0\nI1\na.'), [1]);
        assert.deepEqual(jpickle.loads('(lp1\nI1\naI2\naI3\na.'), [1, 2, 3]);
    });

    it('should decode dicts', function() {
        assert.deepEqual(jpickle.loads('(dp0\n.'), {});
        assert.deepEqual(jpickle.loads("(dp0\nS'foo'\np1\nS'bar'\np2\ns."), {foo: 'bar'});
    });
});

describe('pickle version 1', function() {
    it('should decode BININT', function() {
        assert.equal(jpickle.loads('I1\n.'), 1);
        assert.equal(jpickle.loads('I12\n.'), 12);
    });

    it('should decode BININT1', function() {
        assert.equal(jpickle.loads('K\x01.'), 1);
    });

    it('should decode BININT2', function() {
        assert.equal(jpickle.loads('M\x00\x01.'), 256);
    });

    it('should decode BININT4', function() {
        assert.equal(jpickle.loads('J\x00\x00\x01\x00.'), 65536);
    });

    it('should decode BINFLOAT', function() {
        assert.equal(jpickle.loads('G@\t!\xf9\xf0\x1b\x86n.'), 3.14159);
    });

    it('should decode BINSTRING', function() {
        assert.equal(jpickle.loads('T\x00\x01\x00\x00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxq\x00.'), 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
    });

    it('should decode SHORT_BINSTRING', function() {
        assert.equal(jpickle.loads('U\x03fooq\x00.'), 'foo');
    });

    it('should decode BINUNICODE', function() {
        assert.equal(jpickle.loads('X\x06\x00\x00\x00Test\xc3\xa9q\x00.'), 'Testé');
    });

    it('should decode tuples', function() {
        assert.deepEqual(jpickle.loads(').'), []);
        assert.deepEqual(jpickle.loads('(U\x03fooq\x00tq\x01.'), ['foo']);
        assert.deepEqual(jpickle.loads('(U\x03fooq\x00U\x03barq\x01tq\x02.'), ['foo', 'bar']);
        assert.deepEqual(jpickle.loads('(U\x03fooq\x00U\x03barq\x01U\x03wizq\x02tq\x03.'), ['foo', 'bar', 'wiz']);
    });

    it('should decode EMPTY_LIST', function() {
        assert.deepEqual(jpickle.loads(']q\x00.'), []);
    });

    it('should decode EMPTY_LIST, BINPUT .. APPEND', function() {
        assert.deepEqual(jpickle.loads(']q\x00K\x01a.'), [1]);
    });

    it('should decode EMPTY_LIST, BINPUT, MARK .. APPENDS', function() {
        assert.deepEqual(jpickle.loads(']q\x00(K\x01K\x02K\x03e.'), [1, 2, 3]);
    });

    it('should decode EMPTY_DICT', function() {
        assert.deepEqual(jpickle.loads('}q\x00.'), {});
    });

    it('should decode EMPTY_DICT, BINPUT, MARK .. SETITEM', function() {
        assert.deepEqual(jpickle.loads('}q\x00U\x03fooq\x01U\x03barq\x02s.'), {foo: 'bar'});
    });
});

describe('pickle version 2', function() {
    it('should decode tuples', function() {
        assert.deepEqual(jpickle.loads('\x80\x02).'), []);
        assert.deepEqual(jpickle.loads('\x80\x02U\x03fooq\x00\x85q\x01.'), ['foo']);
        assert.deepEqual(jpickle.loads('\x80\x02U\x03fooq\x00U\x03barq\x01\x86q\x02.'), ['foo', 'bar']);
        assert.deepEqual(jpickle.loads('\x80\x02U\x03fooq\x00U\x03barq\x01U\x03wizq\x02\x87q\x03.'), ['foo', 'bar', 'wiz']);
        assert.deepEqual(jpickle.loads('\x80\x02(U\x03fooq\x00U\x03barq\x01U\x03wizq\x02U\x04bangq\x03tq\x04.'), ['foo', 'bar', 'wiz', 'bang']);
    });
});
<commit_msg>use assert.strictEqual for tests<commit_after>var assert = require('assert')
  , jpickle = require('../lib/jpickle');

describe('pickle version 0', function() {
    it('should decode integers', function() {
        assert.strictEqual(jpickle.loads('I1\n.'), 1);
        assert.strictEqual(jpickle.loads('I12\n.'), 12);
    });

    it('should decode floats', function() {
        assert.strictEqual(jpickle.loads('F3.14159\n.'), 3.14159);
    });

    it('should decode strings', function() {
        assert.strictEqual(jpickle.loads("S'foo'\np0\n."), 'foo');
        assert.strictEqual(jpickle.loads("S\"foo\"\np0\n."), 'foo');
    });

    it('should decode unicode', function() {
        assert.strictEqual(jpickle.loads('VTest\xe9\np0\n.'), 'Testé');
    });

    it('should decode tuples', function() {
        assert.deepEqual(jpickle.loads("(t."), []);
        assert.deepEqual(jpickle.loads("(S'foo'\np0\ntp1\n."), ['foo']);
        assert.deepEqual(jpickle.loads("(S'foo'\np0\nS'bar'\np1\ntp2\n."), ['foo', 'bar']);
        assert.deepEqual(jpickle.loads("(S'foo'\np0\nS'bar'\np1\nS'wiz'\np2\ntp3\n."), ['foo', 'bar', 'wiz']);
    });

    it('should decode lists', function() {
        assert.deepEqual(jpickle.loads('(lp0\n.'), []);
        assert.deepEqual(jpickle.loads('(lp0\nI1\na.'), [1]);
        assert.deepEqual(jpickle.loads('(lp1\nI1\naI2\naI3\na.'), [1, 2, 3]);
    });

    it('should decode dicts', function() {
        assert.deepEqual(jpickle.loads('(dp0\n.'), {});
        assert.deepEqual(jpickle.loads("(dp0\nS'foo'\np1\nS'bar'\np2\ns."), {foo: 'bar'});
    });
});

describe('pickle version 1', function() {
    it('should decode BININT', function() {
        assert.strictEqual(jpickle.loads('I1\n.'), 1);
        assert.strictEqual(jpickle.loads('I12\n.'), 12);
    });

    it('should decode BININT1', function() {
        assert.strictEqual(jpickle.loads('K\x01.'), 1);
    });

    it('should decode BININT2', function() {
        assert.strictEqual(jpickle.loads('M\x00\x01.'), 256);
    });

    it('should decode BININT4', function() {
        assert.strictEqual(jpickle.loads('J\x00\x00\x01\x00.'), 65536);
    });

    it('should decode BINFLOAT', function() {
        assert.strictEqual(jpickle.loads('G@\t!\xf9\xf0\x1b\x86n.'), 3.14159);
    });

    it('should decode BINSTRING', function() {
        assert.strictEqual(jpickle.loads('T\x00\x01\x00\x00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxq\x00.'), 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
    });

    it('should decode SHORT_BINSTRING', function() {
        assert.strictEqual(jpickle.loads('U\x03fooq\x00.'), 'foo');
    });

    it('should decode BINUNICODE', function() {
        assert.strictEqual(jpickle.loads('X\x06\x00\x00\x00Test\xc3\xa9q\x00.'), 'Testé');
    });

    it('should decode tuples', function() {
        assert.deepEqual(jpickle.loads(').'), []);
        assert.deepEqual(jpickle.loads('(U\x03fooq\x00tq\x01.'), ['foo']);
        assert.deepEqual(jpickle.loads('(U\x03fooq\x00U\x03barq\x01tq\x02.'), ['foo', 'bar']);
        assert.deepEqual(jpickle.loads('(U\x03fooq\x00U\x03barq\x01U\x03wizq\x02tq\x03.'), ['foo', 'bar', 'wiz']);
    });

    it('should decode EMPTY_LIST', function() {
        assert.deepEqual(jpickle.loads(']q\x00.'), []);
    });

    it('should decode EMPTY_LIST, BINPUT .. APPEND', function() {
        assert.deepEqual(jpickle.loads(']q\x00K\x01a.'), [1]);
    });

    it('should decode EMPTY_LIST, BINPUT, MARK .. APPENDS', function() {
        assert.deepEqual(jpickle.loads(']q\x00(K\x01K\x02K\x03e.'), [1, 2, 3]);
    });

    it('should decode EMPTY_DICT', function() {
        assert.deepEqual(jpickle.loads('}q\x00.'), {});
    });

    it('should decode EMPTY_DICT, BINPUT, MARK .. SETITEM', function() {
        assert.deepEqual(jpickle.loads('}q\x00U\x03fooq\x01U\x03barq\x02s.'), {foo: 'bar'});
    });
});

describe('pickle version 2', function() {
    it('should decode tuples', function() {
        assert.deepEqual(jpickle.loads('\x80\x02).'), []);
        assert.deepEqual(jpickle.loads('\x80\x02U\x03fooq\x00\x85q\x01.'), ['foo']);
        assert.deepEqual(jpickle.loads('\x80\x02U\x03fooq\x00U\x03barq\x01\x86q\x02.'), ['foo', 'bar']);
        assert.deepEqual(jpickle.loads('\x80\x02U\x03fooq\x00U\x03barq\x01U\x03wizq\x02\x87q\x03.'), ['foo', 'bar', 'wiz']);
        assert.deepEqual(jpickle.loads('\x80\x02(U\x03fooq\x00U\x03barq\x01U\x03wizq\x02U\x04bangq\x03tq\x04.'), ['foo', 'bar', 'wiz', 'bang']);
    });
});
<|endoftext|>