build/wireit-app/wireit-app.js
YUI.add('wireit-app', function (Y, NAME) {
// -- LocalStorageSync ---------------------------------------------------------------------
// Saves WiringModel
function LocalStorageSync(key) {
var localStorage;
if (!key) {
Y.error('No storage key specified.');
}
if (Y.config.win.localStorage) {
localStorage = Y.config.win.localStorage;
}
// Try to retrieve existing data from localStorage, if there is any.
// Otherwise, initialize `data` to an empty object.
var data = Y.JSON.parse((localStorage && localStorage.getItem(key)) || '{}');
// Delete a model with the specified id.
function destroy(id) {
var modelHash;
if ((modelHash = data[id])) {
delete data[id];
save();
}
return modelHash;
}
// Generate a unique id to assign to a newly-created model.
function generateId() {
var id = '',
i = 4;
while (i--) {
id += (((1 + Math.random()) * 0x10000) | 0)
.toString(16).substring(1);
}
return id;
}
// Loads a model with the specified id. This method is a little tricky,
// since it handles loading for both individual models and for an entire
// model list.
//
// If an id is specified, then it loads a single model. If no id is
// specified then it loads an array of all models. This allows the same sync
// layer to be used for both the TodoModel and TodoList classes.
function get(id) {
return id ? data[id] : Y.Object.values(data);
}
// Saves the entire `data` object to localStorage.
function save() {
localStorage && localStorage.setItem(key, Y.JSON.stringify(data));
}
// Sets the id attribute of the specified model (generating a new id if
// necessary), then saves it to localStorage.
function set(model) {
var hash = model.toJSON(),
idAttribute = model.idAttribute;
if (!Y.Lang.isValue(hash[idAttribute])) {
hash[idAttribute] = generateId();
}
data[hash[idAttribute]] = hash;
save();
return hash;
}
// Returns a `sync()` function that can be used with either a Model or a
// ModelList instance.
return function (action, options, callback) {
// `this` refers to the Model or ModelList instance to which this sync
// method is attached.
var isModel = Y.Model && this instanceof Y.Model;
switch (action) {
case 'create': // intentional fallthru
case 'update':
callback(null, set(this));
return;
case 'read':
callback(null, get(isModel && this.get('id')));
return;
case 'delete':
callback(null, destroy(isModel && this.get('id')));
return;
}
};
}
// -- WiringModel ---------------------------------------------------------------------
Y.WiringModel = Y.Base.create('wiringModel', Y.Model, [], {
sync: LocalStorageSync('wireit-app')
}, {
ATTRS: {
id: {value: null},
name : {value: ''},
containers : {value: []},
description: {value: ''},
wires : {value: []}
}
});
// -- WiringModelList ---------------------------------------------------------------------
Y.WiringModelList = Y.Base.create('wiringModelList', Y.ModelList, [], {
sync: LocalStorageSync('wireit-app'),
model : Y.WiringModel
});
Y.WiringListView = Y.Base.create('wiringListView', Y.View, [], {
template: Y.Handlebars.compile(Y.one('#t-wiring-list').getContent()),
/*initializer: function () {
},*/
render: function () {
var content = this.template({wirings: this.get('modelList').toJSON() });
this.get('container').setContent(content);
return this;
}
});
// -- ContainerType ---------------------------------------------------------------------
Y.ContainerType = Y.Base.create('containerModel', Y.Model, [], {
// The `id` attribute for this Model will be an alias for `name`.
idAttribute: 'name'
}, {
ATTRS: {
name : {value: null},
description: {value: null},
config : {value: null}
}
});
// -- ContainerTypeList -----------------------------------------------------------------
Y.ContainerTypeList = Y.Base.create('containerTypeList', Y.ModelList, [], {
model: Y.ContainerType
});
// -- Editor View ------------------------------------------------------------
Y.EditorView = Y.Base.create('editorView', Y.View, [], {
template: Y.Handlebars.compile(Y.one('#t-editor').getContent()),
events: {
'#wiring-save-btn': {click: 'saveWiring'}
},
render: function () {
var content = this.template({
containerTypes: this.get('containerTypes').toJSON()
});
this.get('container').setContent(content);
// Make items draggable to the layer
var that = this;
this.get('container').all('.containerType-name').each(function (node) {
var drag = new Y.DD.Drag({
node: node,
groups: ['containerType']
}).plug(Y.Plugin.DDProxy, {
cloneNode: true,
moveOnEnd: false
});
drag._containerTypeName = node._node.attributes["app-container-name"].value; //node._node.innerHTML;
// On drom, add it to the layer
drag.on('drag:drophit', function (ev) {
var p = that.layer.get('boundingBox').getXY();
that._addContainerFromName(ev.drag._containerTypeName, {
x: ev.drag.lastXY[0] - p[0],
y: ev.drag.lastXY[1] - p[1]
});
}, this);
});
this._renderLayer();
return this;
},
_renderLayer: function () {
this.layer = new Y.Layer({
height: 500
});
// Create the Drop object
var drop = new Y.DD.Drop({
node: this.layer.get('contentBox'),
groups: ['containerType']
});
this.layer.render( this.get('container').one('#layer-container') );
var wiring = this.get('model');
if(wiring) {
this.setWiring( wiring );
}
},
saveWiring: function (e) {
var o = {
name: Y.one('#wiring-name').get('value') || 'Unnamed'
};
// Children are containers
o.containers = [];
Y.Array.each(this.layer._items, function (item) {
o.containers.push({
containerType: item.containerTypeName,
config: item.toJSON()
});
});
// Wires:
o.wires = [];
var layer = this.layer;
Y.Array.each(this.layer._wires, function (wire) {
var src = wire.get('src');
var tgt = wire.get('tgt');
o.wires.push( {
src: { container: layer._items.indexOf( src.get('parent') ), terminal: src.get('name') },
tgt: { container: layer._items.indexOf( tgt.get('parent') ), terminal: tgt.get('name') },
config: wire.toJSON()
});
});
if( this.get('model') ) {
this.get('model').setAttrs(o);
}
else {
this.set('model', new Y.WiringModel(o) );
}
this.get('model').save();
// TODO: add only one message
var s = Y.Node.create('<div class="alert-message bg-warning" style="width: 300px; z-index: 10001;"><p>Saved !</p></div>').appendTo(document.body);
var anim = new Y.Anim({
node: s,
duration: 0.5,
easing: Y.Easing.easeOut,
from: { xy: [400, -50] },
to: { xy: [400, 2] }
});
anim.on('end', function () {
Y.later(1000, this, function () {
(new Y.Anim({
node: s,
duration: 0.5,
easing: Y.Easing.easeOut,
to: { xy: [400, -50] }
})).run();
});
});
anim.run();
},
setWiring: function (wiring) {
var that = this,
layer = this.layer;
Y.Array.each( wiring.get('containers'), function (container) {
that._addContainerFromName(container.containerType, container.config);
Y.on('available', function (el) {
Y.one('#wiring-name').set('value', wiring.get('name') );
}, '#wiring-name');
});
Y.Array.each( wiring.get('wires'), function (wire) {
// prevent bad configs...
if(!wire.src || !wire.tgt) return;
var srcContainer = layer.item(wire.src.container),
srcTerminal = srcContainer.getTerminal(wire.src.terminal),
tgtContainer = layer.item(wire.tgt.container),
tgtTerminal = tgtContainer.getTerminal(wire.tgt.terminal);
// TODO: wire.config;
var w = layer.graphic.addShape({
type: Y.BezierWire,
stroke: {
weight: 4,
color: "rgb(173,216,230)"
},
src: srcTerminal,
tgt: tgtTerminal
});
});
// TODO: this is awful ! But we need to wait for everything to render & position
Y.later(200, this, function () {
layer.redrawAllWires();
});
},
_addContainerFromName: function (containerTypeName, containerConfig) {
var containerType = this.get('containerTypes').getById(containerTypeName);
var containerConf = Y.mix({}, containerType.get('config'));
containerConf = Y.mix(containerConf, containerConfig);
this.layer.add(containerConf);
var container = this.layer.item(this.layer.size()-1);
container.containerTypeName = containerTypeName;
}
}, {
ATTRS: {
containerTypes: {
value: null
}
}
});
/**
* @module wireit-app
*/
// -- WireIt App ---------------------------------------------------------
Y.WireItApp = new Y.Base.create('contributorsApp', Y.App, [], {
views: {
editorPage: {
type: Y.EditorView
},
wiringListPage: {
type: Y.WiringListView
}
},
initializer: function () {
// show indication that the app is busy loading data.
this.on('navigate', this.indicateLoading);
this.once('ready', function (e) {
if (this.hasRoute(this.getPath())) {
this.dispatch();
} else {
this.showWiringListPage();
}
});
},
// -- Event Handlers -------------------------------------------------------
indicateLoading: function (e) {
this.get('activeView').get('container').addClass('loading');
},
// -- Route Handlers -------------------------------------------------------
handleWiring: function (req, res, next) {
var wiringId = req.params.wiring,
wirings = this.get('modelList'),
wiring = wirings.getById(wiringId);
this.set('wiring', wiring);
next();
},
showEditorPage: function () {
this.showView('editorPage', {
containerTypes: this.get('containerTypes'),
wirings: this.get('modelList'),
model: this.get('wiring')
});
},
blankEditorPage: function () {
this.showView('editorPage', {
containerTypes: this.get('containerTypes'),
wirings: this.get('modelList'),
model: null
});
},
showWiringListPage: function () {
//this.get('modelList').load();
var wirings = new Y.WiringModelList();
wirings.load();
this.set('modelList', wirings);
this.showView('wiringListPage', {
modelList: this.get('modelList')
});
}
}, {
ATTRS: {
containerTypes: {
value: new Y.ContainerTypeList()
},
modelList: {
value: new Y.WiringModelList()
},
wiring: {
value: null
},
routes: {
value: [
{path: '/', callback: 'showWiringListPage'},
{path: '/wirings/:wiring/*', callback: 'handleWiring'},
{path: '/wirings/:wiring/edit', callback: 'showEditorPage'},
{path: '/wirings/new', callback: 'blankEditorPage'}
]
}
}
});
}, '@VERSION@', {"requires": ["app", "handlebars", "model", "model-list", "json", "view", "layer", "bezier-wire", "anim"]});