Draft support of filling Mistral workbooks (via Barricade.js)

Implement tree-like inputs structure, next things to come are:
* storing data into the model from inputs;
* data validation;
* converting data model into JSON/YAML;
* enable more complex relations between Mistral entities.

Change-Id: Ibda4b5b4856e9025a9d2fb9f0cdd449bd6a82303
This commit is contained in:
Timur Sufiev 2014-09-11 20:24:39 +04:00
parent 20df447086
commit 7a09aeafa3
6 changed files with 15004 additions and 0 deletions

40
css/merlin.css Normal file
View File

@ -0,0 +1,40 @@
/* Copyright (c) 2014 Mirantis, Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
*/
.left, .right {
width: 50%;
float: left;
padding: 6px;
box-sizing: border-box;
}
.left {
border: 1px solid black;
}
.expandable:before {
content: '+';
}
.expandable.expanded:before {
content: '-';
}
label {
font-weight: bold;
}
.container-action {
padding-left: 5px;
}
.inner-node {
padding-left: 5px;
border-left: 1px solid green;
}

24
index.html Normal file
View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>Merlin Project</title>
<script src="js/lib/jquery-1.11.1.js"></script>
<script src="js/lib/barricade.js"></script>
<script src="js/merlin.js"></script>
<script src="js/lib/js-yaml.js"></script>
<link rel="stylesheet" href="css/merlin.css">
</head>
<body>
<div>
<div class="left">
<div id="toolbar">
<button id="create-workbook">New Workbook</button>
<button id="save-workbook">Save Workbook</button>
</div>
<div id="controls"></div>
</div>
<div class="right"></div>
</div>
</body>
</html>

849
js/lib/barricade.js Normal file
View File

@ -0,0 +1,849 @@
// Copyright 2014 Drago Rosson
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
Barricade = (function () {
"use strict";
var barricade = {};
var blueprint = {
create: function (f) {
var g = function () {
if (this.hasOwnProperty('_parents')) {
this._parents.push(g);
} else {
Object.defineProperty(this, '_parents', {
value: [g]
});
}
f.apply(this, arguments);
};
return g;
}
};
barricade.identifiable = blueprint.create(function (id) {
this.getID = function () {
return id;
};
this.setID = function (newID) {
id = newID;
this.emit('change', 'id');
};
});
barricade.omittable = blueprint.create(function (isUsed) {
this.isUsed = function () {
// If required, it has to be used.
return this.isRequired() || isUsed;
};
this.setIsUsed = function (newUsedValue) {
isUsed = !!newUsedValue;
};
this.on('change', function () {
isUsed = !this.isEmpty();
});
});
barricade.deferrable = blueprint.create(function (schema) {
var self = this,
deferred;
function resolver(neededValue) {
var ref = schema['@ref'].resolver(self, neededValue);
if (ref === undefined) {
logError('Could not resolve "' +
JSON.stringify(self.toJSON()) + '"');
}
return ref;
}
function hasDependency() {
return schema.hasOwnProperty('@ref');
}
this.hasDependency = hasDependency;
if (hasDependency()) {
this.getDeferred = function () {
return deferred;
};
deferred = barricade.deferred.create(schema['@ref'].needs,
resolver);
}
});
barricade.validatable = blueprint.create(function (schema) {
var constraints = schema['@constraints'],
self = this,
error = null;
if (barricade.getType(constraints) !== Array) {
constraints = [];
}
this.hasError = function () { return error !== null; };
this.getError = function () { return error || ''; };
this._validate = function (value) {
function getConstraintMessage(i, lastMessage) {
if (lastMessage !== true) {
return lastMessage;
} else if (i < constraints.length) {
return getConstraintMessage(i + 1, constraints[i](value));
}
return null;
}
error = getConstraintMessage(0, true);
if ( !this.hasError()) {
this.getKeys && this.getKeys().forEach(function(key) {
var obj = self._data[key],
ret = obj._validate();
if (!ret)
error = obj.getError();
});
}
return !this.hasError();
};
});
var eventEmitter = blueprint.create(function () {
var events = {};
function hasEvent(eventName) {
return events.hasOwnProperty(eventName);
}
// Adds listener for event
this.on = function (eventName, callback) {
if (!hasEvent(eventName)) {
events[eventName] = [];
}
events[eventName].push(callback);
};
// Removes listener for event
this.off = function (eventName, callback) {
var index;
if (hasEvent(eventName)) {
index = events[eventName].indexOf(callback);
if (index > -1) {
events[eventName].splice(index, 1);
}
}
};
this.emit = function (eventName) {
var args = arguments; // Must come from correct scope
if (events.hasOwnProperty(eventName)) {
events[eventName].forEach(function (callback) {
// Call with emitter as context and pass all but eventName
callback.apply(this, Array.prototype.slice.call(args, 1));
}, this);
}
};
});
barricade.deferred = {
create: function (classGetter, onResolve) {
var self = Object.create(this),
callbacks = [],
isResolved = false;
self.getClass = function () {
return classGetter();
};
self.resolve = function (obj) {
var ref;
if (isResolved) {
throw new Error('Deferred already resolved');
}
ref = onResolve(obj);
isResolved = true;
if (ref === undefined) {
logError('Could not resolve reference');
} else {
callbacks.forEach(function (callback) {
callback(ref);
});
}
return ref;
};
self.isResolved = function () {
return isResolved;
};
self.addCallback = function (callback) {
callbacks.push(callback);
};
return self;
}
};
barricade.base = (function () {
var base = {};
function forInKeys(obj) {
var key,
keys = [];
for (key in obj) {
keys.push(key);
}
return keys;
}
function isPlainObject(obj) {
return barricade.getType(obj) === Object &&
Object.getPrototypeOf(Object.getPrototypeOf(obj)) === null;
}
function extend(extension) {
function addProperty(object, prop) {
return Object.defineProperty(object, prop, {
enumerable: true,
writable: true,
configurable: true,
value: extension[prop]
});
}
// add properties to extended object
return Object.keys(extension).reduce(addProperty,
Object.create(this));
}
function deepClone(object) {
if (isPlainObject(object)) {
return forInKeys(object).reduce(function (clone, key) {
clone[key] = deepClone(object[key]);
return clone;
}, {});
}
return object;
}
function merge(target, source) {
forInKeys(source).forEach(function (key) {
if (target.hasOwnProperty(key) &&
isPlainObject(target[key]) &&
isPlainObject(source[key])) {
merge(target[key], source[key]);
} else {
target[key] = deepClone(source[key]);
}
});
}
Object.defineProperty(base, 'extend', {
enumerable: false,
writable: false,
value: function (extension, schema) {
if (schema) {
extension._schema = '_schema' in this ?
deepClone(this._schema) : {};
merge(extension._schema, schema);
}
return extend.call(this, extension);
}
});
Object.defineProperty(base, 'instanceof', {
enumerable: false,
value: function (proto) {
var _instanceof = this.instanceof,
subject = this;
function hasMixin(obj, mixin) {
return obj.hasOwnProperty('_parents') &&
obj._parents.some(function (_parent) {
return _instanceof.call(_parent, mixin);
});
}
do {
if (subject === proto ||
hasMixin(subject, proto)) {
return true;
}
subject = Object.getPrototypeOf(subject);
} while (subject);
return false;
}
});
return base.extend({
create: function (json, parameters) {
var self = this.extend({}),
schema = self._schema,
type = schema['@type'];
if (!parameters) {
parameters = {};
}
if (schema.hasOwnProperty('@inputMassager')) {
json = schema['@inputMassager'](json);
}
if (barricade.getType(json) !== type) {
if (json) {
logError("Type mismatch (json, schema)");
logVal(json, schema);
} else {
parameters.isUsed = false;
}
// Replace bad type (does not change original)
json = type();
}
self._data = self._sift(json, parameters);
self._parameters = parameters;
if (schema.hasOwnProperty('@toJSON')) {
self.toJSON = schema['@toJSON'];
}
eventEmitter.call(self);
barricade.omittable.call(self, parameters.isUsed !== false);
barricade.deferrable.call(self, schema);
barricade.validatable.call(self, schema);
if (parameters.hasOwnProperty('id')) {
barricade.identifiable.call(self, parameters.id);
}
return self;
},
_sift: function () {
throw new Error("sift() must be overridden in subclass");
},
_safeInstanceof: function (instance, class_) {
return typeof instance === 'object' &&
('instanceof' in instance) &&
instance.instanceof(class_);
},
getPrimitiveType: function () {
return this._schema['@type'];
},
isRequired: function () {
return this._schema['@required'] !== false;
},
isEmpty: function () {
throw new Error('Subclass should override isEmpty()');
}
});
}());
barricade.container = barricade.base.extend({
create: function (json, parameters) {
var self = barricade.base.create.call(this, json, parameters),
allDeferred = [];
function attachListeners(key) {
self._attachListeners(key);
}
function getOnResolve(key) {
return function (resolvedValue) {
self.set(key, resolvedValue);
if (resolvedValue.hasDependency()) {
allDeferred.push(resolvedValue.getDeferred());
}
if ('getAllDeferred' in resolvedValue) {
allDeferred = allDeferred.concat(
resolvedValue.getAllDeferred());
}
};
}
function attachDeferredCallback(key, value) {
if (value.hasDependency()) {
value.getDeferred().addCallback(getOnResolve(key));
}
}
function deferredClassMatches(deferred) {
return self.instanceof(deferred.getClass());
}
function addDeferredToList(obj) {
if (obj.hasDependency()) {
allDeferred.push(obj.getDeferred());
}
if ('getAllDeferred' in obj) {
allDeferred = allDeferred.concat(
obj.getAllDeferred());
}
}
function resolveDeferreds() {
var curDeferred,
unresolvedDeferreds = [];
// New deferreds can be added to allDeferred as others are
// resolved. Iterating this way is safe regardless of how
// new elements are added.
while (allDeferred.length > 0) {
curDeferred = allDeferred.shift();
if (!curDeferred.isResolved()) {
if (deferredClassMatches(curDeferred)) {
curDeferred.addCallback(addDeferredToList);
curDeferred.resolve(self);
} else {
unresolvedDeferreds.push(curDeferred);
}
}
}
allDeferred = unresolvedDeferreds;
}
self.on('_addedElement', attachListeners);
self.each(attachListeners);
self.each(function (key, value) {
attachDeferredCallback(key, value);
});
if (self.hasDependency()) {
allDeferred.push(self.getDeferred());
}
self.each(function (key, value) {
addDeferredToList(value);
});
resolveDeferreds.call(self);
self.getAllDeferred = function () {
return allDeferred;
};
return self;
},
_attachListeners: function (key) {
var self = this,
element = this.get(key);
function onChildChange(child) {
self.emit('childChange', child);
}
function onDirectChildChange() {
onChildChange(this); // 'this' is set to callee, not typo
}
function onReplace(newValue) {
self.set(key, newValue);
}
element.on('childChange', onChildChange);
element.on('change', onDirectChildChange);
element.on('replace', onReplace);
element.on('removeFrom', function (container) {
if (container === self) {
element.off('childChange', onChildChange);
element.off('change', onDirectChildChange);
element.off('replace', onReplace);
}
});
},
set: function (key, value) {
this.get(key).emit('removeFrom', this);
this._doSet(key, value);
this._attachListeners(key);
},
_getKeyClass: function (key) {
return this._schema[key].hasOwnProperty('@class')
? this._schema[key]['@class']
: barricade.poly(this._schema[key]);
},
_keyClassCreate: function (key, keyClass, json, parameters) {
return this._schema[key].hasOwnProperty('@factory')
? this._schema[key]['@factory'](json, parameters)
: keyClass.create(json, parameters);
},
_isCorrectType: function (instance, class_) {
var self = this;
function isRefTo() {
if (typeof class_._schema['@ref'].to === 'function') {
return self._safeInstanceof(instance,
class_._schema['@ref'].to());
} else if (typeof class_._schema['@ref'].to === 'object') {
return self._safeInstanceof(instance,
class_._schema['@ref'].to);
}
throw new Error('Ref.to was ' + class_._schema['@ref'].to);
}
return this._safeInstanceof(instance, class_) ||
(class_._schema.hasOwnProperty('@ref') && isRefTo());
}
});
barricade.arraylike = barricade.container.extend({
create: function (json, parameters) {
if (!this.hasOwnProperty('_elementClass')) {
Object.defineProperty(this, '_elementClass', {
enumerable: false,
writable: true,
value: this._getKeyClass(this._elSymbol)
});
}
return barricade.container.create.call(this, json, parameters);
},
_elSymbol: '*',
_sift: function (json, parameters) {
return json.map(function (el) {
return this._keyClassCreate(this._elSymbol,
this._elementClass, el);
}, this);
},
get: function (index) {
return this._data[index];
},
each: function (functionIn, comparatorIn) {
var arr = this._data.slice();
if (comparatorIn) {
arr.sort(comparatorIn);
}
arr.forEach(function (value, index) {
functionIn(index, value);
});
},
toArray: function () {
return this._data.slice(); // Shallow copy to prevent mutation
},
_doSet: function (index, newVal, newParameters) {
var oldVal = this._data[index];
if (this._isCorrectType(newVal, this._elementClass)) {
this._data[index] = newVal;
} else {
this._data[index] = this._keyClassCreate(
this._elSymbol, this._elementClass,
newVal, newParameters);
}
this.emit('change', 'set', index, this._data[index], oldVal);
},
length: function () {
return this._data.length;
},
isEmpty: function () {
return this._data.length === 0;
},
toJSON: function (ignoreUnused) {
return this._data.map(function (el) {
return el.toJSON(ignoreUnused);
});
},
push: function (newValue, newParameters) {
if (this._isCorrectType(newValue, this._elementClass)) {
this._data.push(newValue);
} else {
this._data.push(this._keyClassCreate(
this._elSymbol, this._elementClass,
newValue, newParameters));
}
this.emit('_addedElement', this._data.length - 1);
this.emit('change', 'add', this._data.length - 1);
},
remove: function (index) {
this._data[index].emit('removeFrom', this);
this._data.splice(index, 1);
this.emit('change', 'remove', index);
}
});
barricade.array = barricade.arraylike.extend({});
barricade.immutableObject = barricade.container.extend({
create: function (json, parameters) {
var self = this;
if (!this.hasOwnProperty('_keyClasses')) {
Object.defineProperty(this, '_keyClasses', {
enumerable: false,
writable: true,
value: this.getKeys().reduce(function (classes, key) {
classes[key] = self._getKeyClass(key);
return classes;
}, {})
});
}
return barricade.container.create.call(this, json, parameters);
},
_sift: function (json, parameters) {
var self = this;
return this.getKeys().reduce(function (objOut, key) {
objOut[key] = self._keyClassCreate(
key, self._keyClasses[key], json[key]);
return objOut;
}, {});
},
get: function (key) {
return this._data[key];
},
_doSet: function (key, newValue, newParameters) {
var oldVal = this._data[key];
if (this._schema.hasOwnProperty(key)) {
if (this._isCorrectType(newValue,
this._keyClasses[key])) {
this._data[key] = newValue;
} else {
this._data[key] = this._keyClassCreate(
key, this._keyClasses[key],
newValue, newParameters);
}
this.emit('change', 'set', key, this._data[key], oldVal);
} else {
console.error('object does not have key (key, schema)');
console.log(key, this._schema);
}
},
each: function (functionIn, comparatorIn) {
var self = this,
keys = this.getKeys();
if (comparatorIn) {
keys.sort(comparatorIn);
}
keys.forEach(function (key) {
functionIn(key, self._data[key]);
});
},
isEmpty: function () {
return Object.keys(this._data).length === 0;
},
toJSON: function (ignoreUnused) {
var data = this._data;
return this.getKeys().reduce(function (jsonOut, key) {
if (ignoreUnused !== true || data[key].isUsed()) {
jsonOut[key] = data[key].toJSON(ignoreUnused);
}
return jsonOut;
}, {});
},
getKeys: function () {
return Object.keys(this._schema).filter(function (key) {
return key.charAt(0) !== '@';
});
}
});
barricade.mutableObject = barricade.arraylike.extend({
_elSymbol: '?',
_sift: function (json, parameters) {
return Object.keys(json).map(function (key) {
return this._keyClassCreate(
this._elSymbol, this._elementClass,
json[key], {id: key});
}, this);
},
getIDs: function () {
return this.toArray().map(function (value) {
return value.getID();
});
},
getByID: function (id) {
var pos = this.toArray().map(function (value) {
return value.getID();
}).indexOf(id);
return this.get(pos);
},
contains: function (element) {
return this.toArray().some(function (value) {
return element === value;
});
},
toJSON: function (ignoreUnused) {
return this.toArray().reduce(function (jsonOut, element) {
if (jsonOut.hasOwnProperty(element.getID())) {
logError("ID encountered multiple times: " +
element.getID());
} else {
jsonOut[element.getID()] =
element.toJSON(ignoreUnused);
}
return jsonOut;
}, {});
},
push: function (newJson, newParameters) {
if (barricade.getType(newParameters) !== Object ||
!newParameters.hasOwnProperty('id')) {
logError('ID should be passed in ' +
'with parameters object');
} else {
barricade.array.push.call(this, newJson, newParameters);
}
},
});
barricade.primitive = barricade.base.extend({
_sift: function (json, parameters) {
return json;
},
get: function () {
return this._data;
},
set: function (newVal) {
var schema = this._schema;
function typeMatches(newVal) {
return barricade.getType(newVal) === schema['@type'];
}
if (typeMatches(newVal) && this._validate(newVal)) {
this._data = newVal;
this.emit('validation', 'succeeded');
this.emit('change');
} else if (this.hasError()) {
this.emit('validation', 'failed');
} else {
logError("Setter - new value did not match " +
"schema (newVal, schema)");
logVal(newVal, schema);
}
},
isEmpty: function () {
if (this._schema['@type'] === Array) {
return this._data.length === 0;
} else if (this._schema['@type'] === Object) {
return Object.keys(this._data).length === 0;
} else {
return this._data === this._schema['@type']();
}
},
toJSON: function () {
return this._data;
}
});
barricade.getType = (function () {
var toString = Object.prototype.toString,
types = {
'boolean': Boolean,
'number': Number,
'string': String,
'[object Array]': Array,
'[object Date]': Date,
'[object Function]': Function,
'[object RegExp]': RegExp
};
return function (val) {
return types[typeof val] ||
types[toString.call(val)] ||
(val ? Object : null);
};
}());
function logMsg(msg) {
console.log("Barricade: " + msg);
}
function logWarning(msg) {
console.warn("Barricade: " + msg);
}
function logError(msg) {
console.error("Barricade: " + msg);
}
function logVal(val1, val2) {
if (val2) {
console.log(val1, val2);
} else {
console.log(val1);
}
}
function BarricadeMain(schema) {
function schemaIsMutable() {
return schema.hasOwnProperty('?');
}
function schemaIsImmutable() {
return Object.keys(schema).some(function (key) {
return key.charAt(0) !== '@' && key !== '?';
});
}
if (schema['@type'] === Object && schemaIsImmutable()) {
return barricade.immutableObject.extend({_schema: schema});
} else if (schema['@type'] === Object && schemaIsMutable()) {
return barricade.mutableObject.extend({_schema: schema});
} else if (schema['@type'] === Array && schema.hasOwnProperty('*')) {
return barricade.array.extend({_schema: schema});
} else {
return barricade.primitive.extend({_schema: schema});
}
}
barricade.poly = BarricadeMain;
BarricadeMain.getType = barricade.getType; // Very helpful function
BarricadeMain.base = barricade.base;
BarricadeMain.container = barricade.container;
BarricadeMain.array = barricade.array;
BarricadeMain.object = barricade.object;
BarricadeMain.immutableObject = barricade.immutableObject;
BarricadeMain.mutableObject = barricade.mutableObject;
BarricadeMain.primitive = barricade.primitive;
BarricadeMain.blueprint = blueprint;
BarricadeMain.eventEmitter = eventEmitter;
BarricadeMain.deferrable = barricade.deferrable;
BarricadeMain.omittable = barricade.omittable;
BarricadeMain.identifiable = barricade.identifiable;
return BarricadeMain;
}());

10308
js/lib/jquery-1.11.1.js vendored Normal file

File diff suppressed because it is too large Load Diff

3515
js/lib/js-yaml.js Normal file

File diff suppressed because it is too large Load Diff

268
js/merlin.js Normal file
View File

@ -0,0 +1,268 @@
/* Copyright (c) 2014 Mirantis, Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
*/
var types = {
Mistral: {
actions: {}
},
HOT: {}
};
types.Mistral.Action = Barricade({
'@type': Object,
'Version': {'@type': String},
'name': {'@type': String},
'base': {'@type': String},
'base-parameters': {
'@type': String,
'@required': false
},
'parameters': {
'@type': Array,
'@required': false,
'*': {'@type': String}
}
});
types.Mistral.Task = Barricade({
'@type': Object,
'Version': {'@type': String},
'name': {'@type': String},
'action': {
'@type': String,
'@required': false
},
'workflow': { // 'action' and 'workflow' are mutually-exclusive but at least one is required
'@type': String,
'@required': false
},
'workflow-parameters': {
'@type': String,
'@required': false
},
'parameters': {
'@type': String,
'@required': false
},
'publish': {
'@type': String,
'@required': false
},
'policies': {
'@type': String,
'@required': false
},
'requires': {
'@type': String,
'@required': false
},
'on-complete': {
'@type': String,
'@required': false
},
'on-success': {
'@type': String,
'@required': false
},
'on-error': {
'@type': String,
'@required': false
}
});
types.Mistral.Workflow = Barricade({
'@type': Object,
'Version': {'@type': String},
'name': {'@type': String},
'type': {
'@type': String,
'@constraints': [function(type) {
var possibleTypes = ['reverse', 'direct'],
validType = possibleTypes.indexOf(type) > -1;
return validType || ('Expected: ' + possibleTypes + ' while ' + type + ' found');
}]
},
'start-task': {
'@type': String,
'@required': false
},
'policies': {
'@type': String,
'@required': false
},
'parameters': {
'@type': String,
'@required': false
},
'output': {
'@type': String,
'@required': false
},
'tasks': {
'@type': Array,
'*': {'@class': types.Mistral.Task}
}
});
types.Mistral.Workbook = Barricade({
'@type': Object,
'Version': {'@type': Number},
'Description': {
'@type': String,
'@required': false
},
'Actions': {
'@type': Array,
'@required': false,
'*': {
'@class': types.Mistral.Action
}
},
'Workflows': {
'@type': Array,
'*': {
'@class': types.Mistral.Workflow
}
}
});
var workbook,
counter = 0;
$(function() {
function drawTextNode(label, item) {
var $item = $('<div></div>'),
$label = $('<label></label>').text(label),
$input = $('<input>');
$item.append($label);
return $item.append($input.attr('type', 'text'));
}
function drawNumberNode(label, item) {
var $item = $('<div></div>'),
$label = $('<label></label>').text(label),
$input = $('<input>');
$item.append($label);
return $item.append($input.attr('type', 'number'));
}
function drawBooleanNode(label, item) {
var $item = $('<div></div>'),
$label = $('<label></label>').text(label),
$input = $('<input>');
$item.append($label);
return $item.append($input.attr('type', 'checkbox'));
}
function drawArrayNode(label, item) {
var $item = $('<div class="inner-node"></div>'),
$label = $('<label></label>').text(label).toggleClass('expandable'),
$addAction = $('<a href="#" class="container-action">Add</a>'),
$container = $('<div></div>').hide();
$item.append($label);
$label.after($addAction);
drawArray($container, item);
$item.append($container);
$label.click(function() {
$label.toggleClass('expanded');
if ( $label.hasClass('expanded') ) {
$container.show();
} else {
$container.hide();
}
});
$addAction.click(function() {
item.push();
var length = item.length();
drawTypedNode($container, 'Element #'+length, item.get(length-1));
});
return $item;
}
function drawContainerNode(label, item) {
var $item = $('<div class="inner-node"></div>'),
labelId = 'label-' + counter,
containerId = 'container-' + counter,
$label = $('<label></label>').attr('id', labelId).text(label).toggleClass('expandable'),
$container = $('<div></div>').attr('id', containerId).hide();
counter++;
$item.append($label);
drawContainer($container, item);
$item.append($container);
$label.click(function() {
$label.toggleClass('expanded');
if ( $label.hasClass('expanded') ) {
$container.show();
} else {
$container.hide();
}
});
return $item;
}
function isPrimitiveType(item, primitiveType) {
return item.instanceof(Barricade.primitive) && Barricade.getType(item.get()) === primitiveType;
}
function drawArray($canvas, array) {
array.each(function(index, item) {
drawTypedNode($canvas, 'Element #'+index, item);
});
}
function drawTypedNode($canvas, label, item) {
var $node;
if ( isPrimitiveType(item, Number) ) {
$node = drawNumberNode(label, item);
$canvas.append($node);
} else if ( isPrimitiveType(item, String) ) {
$node = drawTextNode(label, item);
$canvas.append($node);
} else if ( isPrimitiveType(item, Boolean) ) {
$node = drawBooleanNode(label, item);
$canvas.append($node);
} else if ( item.instanceof(Barricade.array) ) {
$node = drawArrayNode(label, item);
$canvas.append($node);
} else if ( item.instanceof(Barricade.container) ) {
$node = drawContainerNode(label, item);
$canvas.append($node);
} else {
$node = $('<label></label>').text('Unknown elt');
$canvas.append($node);
}
return $node;
}
function drawContainer($canvas, container) {
container.each(function(key, item) {
drawTypedNode($canvas, key, item);
});
}
$('button#create-workbook').click(function() {
var $controls = $('div#controls');
$controls.empty();
workbook = types.Mistral.Workbook.create();
drawTypedNode($controls, 'Mistral Workbook', workbook).find('label').click();
})
});