/*** * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Svidget.js v0.3.5
* Release Date: 2018-03-02
*
* A framework for creating complex widgets using SVG.
*
* http://www.svidget.com/
*
* Copyright 2018, Joe Agster
* Licensed under the MIT license.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
;
!function(global, factory) {
"function" == typeof define && define.amd ? define([ "svidget" ], factory) : "object" == typeof module && module.exports ? module.exports = factory : // don't defer, instantiate svidget immediately
// global === window
global.svidget = factory(global);
}(this, function(global, createOptions) {
var VERSION = "0.3.5", Svidget = {}, window = global, document = window.document || {};
//var root = null;
Svidget.root = null;
// set in Svidget.Root, this is the singleton instance to the global "svidget" object.
Svidget.global = global;
// In server side environments will === global, in browser === window
//Svidget.window = null; // In server side environments will be mock window or jsdom-like window
//Svidget.document = null; // In server side environments will be window.document or jsdom-like document
Svidget.version = VERSION;
Svidget.declaredHandlerName = "_declared";
Svidget.emptyArray = [];
Svidget.defaultType = "object";
/** @function Svidget.array
Builds an array from any collection, be it another array, HTMLCollection, etc. */
Svidget.array = function(anyCollection) {
// oops, we need a collection with a length property
if (!anyCollection || !anyCollection.length) return null;
try {
// this may blow up for IE8 and below and other less than modern browsers
return Svidget.emptyArray.slice.call(anyCollection, 0);
} catch (e) {
for (var res = [], i = 0; i < anyCollection.length; i++) res.push(anyCollection[i]);
return res;
}
};
/** @function Svidget.isArray
Determines if passed in object is actually an array.
This is a more relaxed check and handles the case when array is a Svidget.Collection and/or across frame boundaries */
Svidget.isArray = function(array) {
return null != array && (Array.isArray(array) || Array.prototype.isPrototypeOf(array) || array.length && array.push);
};
Svidget.isFunction = function(func) {
return "function" == typeof func;
};
Svidget.isString = function(str) {
return "string" == typeof str || str.length && str.trim && str.charAt;
};
Svidget.isColor = function(color) {
// todo
return false;
};
Svidget.convert = function(val, type, subtype, typedata) {
return Svidget.Conversion.to(val, type, subtype, typedata);
};
Svidget.getType = function(val) {
if (null == val) return Svidget.defaultType;
// object is the default type (as of 0.3.0, formerly "string")
if (Svidget.isArray(val)) return "array";
var type = typeof val;
// if (type === "boolean") return "bool"; // as of 0.3.0 using "boolean" not "bool"
// if (type === "boolean") return "bool"; // as of 0.3.0 using "boolean" not "bool"
return "string" === type || "number" === type || "boolean" === type ? type : "object";
};
// Given a type, determines if it is valid and if not returns the default type.
Svidget.resolveType = function(type) {
type = Svidget.Conversion.toString(type);
return void 0 === Svidget.ParamTypes[type] ? Svidget.defaultType : "bool" === type ? "boolean" : type;
};
// Given a subtype, determines if it is valid and if not returns null.
Svidget.resolveSubtype = function(type, subtype) {
type = Svidget.resolveType(type);
//subtype is not valid, so return null
return void 0 === Svidget.ParamSubTypes[subtype] ? null : "string" != type || "color" != subtype && "regex" != subtype && "color" != subtype ? "number" == type && "integer" == subtype ? subtype : null : subtype;
};
Svidget.extend = function(objtype, prototype, overwrite) {
for (var methodName in prototype) // do we check for hasOwnProperty here?
(overwrite || void 0 === objtype.prototype[methodName]) && (objtype.prototype[methodName] = prototype[methodName]);
};
Svidget.wrap = function(func, context) {
// todo: use function.bind() if available
// ensure func is function, return undefined if not
if (null != func && "function" == typeof func) {
return function() {
return func.apply(context, arguments);
};
}
};
// Find the function by name in the specified scope, or just return it if is already a function
// By default scope == global scope
Svidget.findFunction = function(funcNameOrInstance, scope) {
if ("function" == typeof funcNameOrInstance) return funcNameOrInstance;
null == scope && (scope = window);
// global scope
if (null != funcNameOrInstance) {
var bind = funcNameOrInstance + "", func = scope[bind];
null == func && scope !== global && (func = global[bind]);
// bind is an expression, so just wrap it in a function
return null == func ? null : "function" == typeof func ? func : "return " != bind.substr(0, 7) ? new Function("return " + bind) : new Function(bind);
}
return null;
};
Svidget.log = function(msg) {
Svidget.Settings.enableLogging && console.log(msg);
};
Svidget.readOnlyProperty = function(value) {
return {
enumerable: true,
configurable: false,
writable: false,
value: value
};
};
Svidget.fixedProperty = function(value) {
return {
enumerable: true,
configurable: false,
writable: true,
value: value
};
};
Svidget.getPrivateAccessor = function(privates) {
return function(p) {
return privates[p];
};
};
Svidget.setPrivateAccessor = function(privates) {
return function(p, val) {
if (!privates.writable.contains(p)) return false;
privates[p] = val;
return true;
};
};
Svidget.returnFalse = function() {
return false;
};
Svidget.returnTrue = function() {
return true;
};
Svidget.Settings = {};
Svidget.Settings.showPrivates = true;
//show private members of objects, useful for debugging
Svidget.Settings.enableLogging = false;
// whether console.logging is enabled, turn on for troubleshooting
Array.prototype.contains || (Array.prototype.contains = function(obj) {
for (var i = this.length; i--; ) if (this[i] === obj) return true;
return false;
});
// a note about "bla instanceof Array" checks, they don't work across frame boundaries
// so we use isArray
Array.isArray || (Array.isArray = function(arg) {
return "[object Array]" === Object.prototype.toString.call(arg);
});
/**
* Encapsulates common functionality for all object types in the framework.
* @class
* @static
* @memberof Svidget
*/
Svidget.ObjectPrototype = {
// protected
setup: function(privates) {
this.getPrivate = Svidget.getPrivateAccessor(privates);
this.setPrivate = Svidget.setPrivateAccessor(privates);
Svidget.Settings.showPrivates && (this.privates = privates);
},
// protected
// as of 0.3.0: type argument converts val to type
getset: function(prop, val, type, validator) {
// ** get mode
var curProp = this.getPrivate(prop);
if (void 0 === val) return curProp;
// ** set mode
// first, convert if needed
null != type && (val = Svidget.convert(val, type));
// if value is current value then do nothing
// if value is current value then do nothing
// then, validate
return val !== curProp && (!(validator && !validator.call(this, val)) && this.setPrivate(prop, val));
},
// protected
// should always return a collection
select: function(col, selector) {
if ("number" == typeof selector) {
selector = parseInt(selector);
// coerce to integer
return col.wrap(col.getByIndex(selector));
}
return "function" == typeof selector ? col.where(selector) : void 0 !== selector ? col.wrap(col.getByName(selector + "")) : col;
},
// protected
// should always return a single item
selectFirst: function(col, selector) {
if ("number" == typeof selector) {
selector = parseInt(selector);
// coerce to integer
return col.getByIndex(selector);
}
return "function" == typeof selector ? col.first(selector) : void 0 !== selector ? col.getByName(selector + "") : col.first();
},
// protected
wireCollectionAddRemoveHandlers: function(col, addFunc, removeFunc) {
if (null != col) {
col.onAdded(Svidget.wrap(addFunc, this));
col.onRemoved(Svidget.wrap(removeFunc, this));
}
}
};
/**
* Encapsulates a set of prototype methods for managing events.
* @class
* @memberof Svidget
* @param {array} typelist - A list of event types.
*/
Svidget.EventPrototype = function(typelist) {
// todo: validate typelist
// these are the event types that the base object supports, like "invoke" for action.
this.eventTypes = new Svidget.Collection(typelist);
};
Svidget.EventPrototype.prototype = {
on: function(type, data, name, handler) {
// resolve handler from whether name, data passed
var argsCount = Svidget.isFunction(handler) ? 4 : Svidget.isFunction(name) ? 3 : Svidget.isFunction(data) ? 2 : 1;
//handler = handler || (Svidget.isFunction(name) ? name : (Svidget.isFunction(data) ? data : null));
handler = 4 == argsCount ? handler : 3 == argsCount ? name : 2 == argsCount ? data : null;
data = argsCount > 2 ? data : null;
name = argsCount > 3 ? name : null;
return !!handler && this.addHandler(type, handler, name, data);
},
off: function(type, handlerOrName) {
// separate handlerOrName into handler, name
var handler = Svidget.isFunction(handlerOrName) ? handlerOrName : null, name = null != handler ? null : handlerOrName;
return this.removeHandler(type, handler, name);
},
trigger: function(type, value, originalTarget) {
if (null != type) {
// nothing to do
// get event object from handlers
var e = this.triggerHandlers(type, value, originalTarget);
Svidget.log("trigger: " + type);
// if not stopPropagation call bubble
e.isPropagationStopped() || this.bubble(type, e);
}
},
triggerHandlers: function(type, value, originalTarget) {
// generate event object
var e = new Svidget.Event(null, type, null, this.getTarget(), originalTarget, value);
if (null == type || null == this.handlers || null == this.handlers[type]) return e;
// loop through each handler (make sure it is a function)
// h == { handler: handler, name: name, data: data }
this.handlers[type].each(function(h) {
if (e.isImmediatePropagationStopped()) return false;
// if stopImmediatePropagation, then exit loop by returning false
if (null != h && null != h.handler && "function" == typeof h.handler) {
// handler is not a function
// set name/data
e.name = h.name;
e.data = h.data;
// invoke handler
h.handler.call(null, e);
}
});
return e;
},
bubble: function(type, sourceEvent) {
// invoked from child
this.ensureBubbleParents();
sourceEvent.name = null;
sourceEvent.data = null;
this.bubbleParents[type] && this.bubbleParents[type](type, sourceEvent, this.getTarget());
},
addHandler: function(type, handler, name, data) {
this.ensureHandlersByType(type);
// todo: get handler function name, we will use to off() handlers by name
//if (this.handlers[type].contains(handler)) return false;
if (this.handlerExists(type, handler, name)) return false;
var obj = this.toHandlerObject(handler, name, data);
this.handlers[type].push(obj);
return true;
},
removeHandler: function(type, handler, name) {
this.ensureHandlers();
if (!this.handlers[type]) return false;
//return this.handlers[type].removeAll(handler);
var that = this;
return this.handlers[type].removeWhere(function(item) {
return that.handlerMatch(item, handler, name);
});
},
handlerExists: function(type, handler, name) {
var that = this;
return this.handlers[type].any(function(item) {
return that.handlerMatch(item, handler, name);
});
},
handlerMatch: function(handlerObj, handler, name) {
return null != name && handlerObj.name === name || handler === handlerObj.handler;
},
setBubbleParent: function(type, callback) {
this.ensureBubbleParents();
this.bubbleParents[type] = callback;
},
// private, called by the object to register a single callback for all its event types
// bubbleTarget: usually a parent object
registerBubbleCallback: function(types, bubbleTarget, callback) {
if (bubbleTarget && callback) for (var i = 0; i < types.length; i++) this.setBubbleParent(types[i], Svidget.wrap(callback, bubbleTarget));
},
toHandlerObject: function(handler, name, data) {
return {
handler: handler,
name: name,
data: data
};
},
bubbleFuncs: function(objectType) {},
ensureHandlers: function() {
this.handlers || (this.handlers = {});
},
ensureHandlersByType: function(type) {
this.ensureHandlers();
this.handlers[type] || (this.handlers[type] = new Svidget.Collection());
},
ensureBubbleParents: function() {
this.bubbleParents || (this.bubbleParents = {});
},
// internal
// returns the target object to use for the event object
// override in eventContainer
getTarget: function() {
return this;
}
};
/**
* Encapsulates common functionality for a Param and ActionParam.
* @class
* @abstract
* @memberof Svidget
*/
Svidget.ParamPrototype = {
/**
* Gets the param name.
* @method
* @returns {string}
*/
name: function(val) {
return void 0 === val && this.getPrivate("name");
},
/**
* Gets or sets the description.
* @method
* @param {string} [val] - Sets the value when specified.
* @returns {string} - The value for a get, or true/false if succeeded or failed for a set.
*/
description: function(val) {
var res = this.getset("description", val, "string");
// if undefined its a get so return value, if res is false then set failed
if (void 0 === val || !!!res) return res;
// fire "changed" event
val = this.getPrivate("description");
// get converted value
this.trigger && this.trigger("change", {
property: "description",
value: val
});
// set was successful
return true;
},
/**
* Gets or sets the param type. This can be "string", "number", etc. See the Svidget.ParamTypes enum.
* @method
* @param {string} [val] - Sets the value when specified.
* @returns {string} - The value for a get, or true/false if succeeded or failed for a set.
*/
type: function(val) {
null === val && (val = Svidget.resolveType(null));
// rturns default type
"bool" == val && (val = "boolean");
var res = this.getset("type", val, "string", this.validateType);
// if undefined its a get so return value, if res is false then set failed
if (void 0 === val || !!!res) return res;
// fire "changed" event
val = this.getPrivate("type");
// get converted value
this.trigger && this.trigger("change", {
property: "type",
value: val
});
// set was successful
return true;
},
/**
* Gets or sets the param subtype. This can be on of the values from the Svidget.ParamSubTypes enum.
* @method
* @param {string} [val] - Sets the value when specified.
* @returns {string} - The value for a get, or true/false if succeeded or failed for a set.
*/
subtype: function(val) {
var res = this.getset("subtype", val, "string", this.validateSubtype);
// if undefined its a get so return value, if res is false then set failed
if (void 0 === val || !!!res) return res;
// fire "changed" event
val = this.getPrivate("subtype");
// get converted value
this.trigger && this.trigger("change", {
property: "subtype",
value: val
});
// set was successful
return true;
},
/**
* Gets or sets the type data associated with the subtype. This can be a regex pattern or choice string, among other things.
* @method
* @param {string} [val] - Sets the typedata when specified.
* @returns {string} - The typedata when nothing is passed, or true/false if succeeded or failed when setting.
*/
typedata: function(val) {
var res = this.getset("typedata", val, "string");
// if undefined its a get so return value, if res is false then set failed
if (void 0 === val || !!!res) return res;
// fire "changed" event
val = this.getPrivate("typedata");
// get converted value
this.trigger && this.trigger("change", {
property: "typedata",
value: val
});
// set was successful
return true;
},
/**
* Gets or sets the param defvalue. This is the default value if nothing (or undefined) is set.
* @method
* @param {string} [val] - Sets the value when specified.
* @returns {string} - The value for a get, or true/false if succeeded or failed for a set.
*/
defvalue: function(val) {
var res = this.getset("defvalue", val);
// if undefined its a get so return value, if res is false then set failed
if (void 0 === val || !!!res) return res;
// fire "change" event
this.trigger && this.trigger("change", {
property: "defvalue",
value: val
});
return true;
},
validateType: function(t) {
return "string" !== !typeof t && void 0 !== Svidget.ParamTypes[t];
},
validateSubtype: function(t) {
return true;
},
_resolveType: function(type, value, defvalue) {
// infer the type from the value or defvalue
value = null != value ? value : defvalue;
type = null == type ? Svidget.getType(value) : Svidget.resolveType(type);
// normalize type to a valid type
return type;
}
};
/**
* Represents a structured collection. Extends Array by providing additional methods to select, tranverse, and modify an array.
* @constructor
* @augments Array
* @param {Array} array - The initial elements of the collection.
*/
Svidget.Collection = function(array) {
this.__type = "Svidget.Collection";
this.source = array;
//this.baseType = Svidget.Collection;
// this.items = [];
// append items from source to this instance
array && (Svidget.isArray(array) || Array.prototype.isPrototypeOf(array)) && this.push.apply(this, array);
};
Svidget.Collection.prototype = new Array();
Svidget.extend(Svidget.Collection, {
/**
* Returns true if any of the items satisfies the specified predicate function.
* @method
* @param {Function} predicate - A function that accepts an item as input and returns true/false.
* @returns {boolean}
*/
any: function(predicate) {
if (null == predicate) return this.length > 0;
for (var i = 0; i < this.length; i++) if (predicate(this[i])) return true;
return false;
},
/**
* Returns true if all of the items satisfies the specified predicate function.
* @method
* @param {Function} predicate - A function that accepts an item as input and returns true/false.
* @returns {boolean}
*/
all: function(predicate) {
if (null == predicate) return false;
for (var i = 0; i < this.length; i++) if (!predicate(this[i])) return false;
return true;
},
/**
* Returns true if the item is contained in the collection.
* @method
* @param {object} obj - The object to look for.
* @returns {boolean}
*/
contains: function(obj) {
return this.indexOf(obj) >= 0;
},
/**
* Iterates on each item in the collection and performs the operation.
* @method
* @param {Function} operation - A function that accepts an item as input.
*/
each: function(operation) {
for (var i = 0; i < this.length; i++) {
if (false === operation(this[i])) break;
}
},
/**
* Returns the first item in the collection that satisfies the condition in the specified predicate function.
* @method
* @param {Function} predicate - A function that accepts an item as input and returns true/false.
* @returns {object} - The item in the collection.
*/
first: function(predicate) {
if (0 == this.length) return null;
if (null == predicate || "function" === !typeof predicate) return this[0];
for (var i = 0; i < this.length; i++) if (predicate(this[i])) return this[i];
return null;
},
/**
* Returns the last item in the collection that satisfies the condition in the specified predicate function.
* @method
* @param {Function} predicate - A function that accepts an item as input and returns true/false.
* @returns {object} - The item in the collection.
*/
last: function(predicate) {
if (0 == this.length) return null;
if (null == predicate || "function" === !typeof predicate) return this[0];
for (var i = this.length - 1; i >= 0; i--) if (predicate(this[i])) return this[i];
return null;
},
/**
* Iterates on the collection calling the specified selector function and returns a new collection. Chainable.
* @method
* @param {Function} selector - A function that accepts an item as input and returns a value based on it.
* @returns {Svidget.Collection} - The result collection.
*/
select: function(selector) {
for (var result = [], i = 0; i < this.length; i++) result.push(selector(this[i]));
return new Svidget.Collection(result);
},
/**
* Iterates on the collection calling the specified predicate filter function and returns a new collection. Chainable.
* @method
* @param {Function} predicate - A function that accepts an item as input and returns true/false.
* @returns {Svidget.Collection} - The result collection.
*/
where: function(predicate) {
for (var result = [], i = 0; i < this.length; i++) predicate(this[i]) && result.push(this[i]);
return new Svidget.Collection(result);
},
// modifiers
/**
* Adds an item to the collection.
* @method
* @param {object} obj - The item to add.
* @returns {boolean} - True if add succeeds.
*/
add: function(obj) {
if (this.indexOf(obj) >= 0) return false;
this.push(obj);
return true;
},
/**
* Adds an array of items to the collection.
* @method
* @param {Array} array - The items to add.
* @returns {boolean} - True if add succeeds.
*/
addRange: function(array) {
if (!Svidget.isArray(array)) return false;
this.push.apply(this, array);
return true;
},
/**
* Inserts an item to the collection at the specified index.
* @method
* @param {object} obj - The item to add.
* @param {number} index - The items to add.
* @returns {boolean} - True if add succeeds.
*/
insert: function(obj, index) {
index = parseInt(index);
if (!isNaN(index) && (index < 0 || index > this.length)) return false;
this.splice(index, 0, obj);
return true;
},
/**
* Removes an item from the collection. Only removes the first instance of the item found.
* @method
* @param {object} obj - The item to remove.
* @returns {boolean} - True if remove succeeds.
*/
remove: function(obj) {
var pos = this.indexOf(obj);
if (pos < 0) return false;
this.splice(pos, 1);
return true;
},
/**
* Removes an item from the collection. Removes all instances of the item.
* @method
* @param {object} obj - The item to remove.
* @returns {boolean} - True if remove succeeds.
*/
removeAll: function(obj) {
for (var removed = false; this.remove(obj); ) removed = true;
return removed;
},
/**
* Clears all items in the collection.
* @method
* @returns {boolean} - True after collection cleared.
*/
clear: function() {
this.splice(0, this.length);
return true;
},
/**
* Removes an item from the collection based on the specified predicate function.
* @method
* @param {Function} predicate - A function that accepts an item as input and returns true/false.
* @returns {boolean} - True if remove succeeds.
*/
removeWhere: function(predicate) {
for (var result = [], removed = false, i = 0; i < this.length; i++) predicate(this[i]) && result.push(this[i]);
for (var i = 0; i < result.length; i++) removed = this.remove(result[i]) || removed;
return removed;
},
// misc
/**
* Returns a new array based on items in the collection.
* @method
* @returns {Array}
*/
toArray: function() {
for (var arr = [], i = 0; i < this.length; i++) arr.push(this[i]);
//var check = arr instanceof Array;
//Svidget.log('is it array: ' + check);
return arr;
}
});
/**
* Represents a specialized collection for framework objects.
* @constructor
* @mixes Svidget.ObjectPrototype
* @augments Svidget.Collection
* @param {Array} array - The initial elements of the collection.
* @param {Function} type - The type of objects that the collection should hold.
*/
Svidget.ObjectCollection = function(array, type) {
// todo: filter input array by type specified
Svidget.Collection.apply(this, [ array ]);
this.__type = "Svidget.ObjectCollection";
this._ctor = Svidget.ObjectCollection;
// private fields
var privates = new function() {
this.writable = [ "addedFunc", "removedFunc" ];
this.type = type;
this.addedFunc = null;
this.removedFunc = null;
}();
// private accessors
this.setup(privates);
};
var base = new Svidget.Collection();
Svidget.ObjectCollection.prototype = base;
Svidget.ObjectCollection.prototype.base_add = base.add;
Svidget.ObjectCollection.prototype.base_remove = base.remove;
Svidget.extend(Svidget.ObjectCollection, {
/**
* Gets an item in the collection by its index (if number passed) or id/name (if string passed).
* @method
* @param {(string|number)} selector - The selector.
* @returns {object} - The object in the collection.
*/
get: function(selector) {
return "number" == typeof selector ? col.getByIndex(selector) : col.getByName(selector);
},
/**
* Gets an item in the collection by its index.
* @method
* @param {number} selector - The index.
* @returns {object} - The object in the collection.
*/
getByIndex: function(index) {
if (null == index || isNaN(index)) return null;
index = parseInt(index);
var res = this[index];
return null == res ? null : res;
},
/**
* Gets an item in the collection by its name.
* @method
* @param {string} selector - The name.
* @returns {object} - The object in the collection.
*/
getByName: function(name) {
return this.first(function(p) {
return p.name() == name;
});
},
/**
* Gets the object type that this collection holds.
* @method
* @returns {Function} - The type constructor that represents the type.
*/
type: function() {
return this.getset("type");
},
/**
* Adds an item to the collection.
* @method
* @param {object} obj - The item to add.
* @returns {boolean} - The item that was added.
*/
add: function() {
if (0 == arguments.length) return null;
// call add overload
var item, arg0, success;
arguments.length >= 1 && (arg0 = arguments[0]);
item = "string" == typeof arg0 ? this.create.apply(this, arguments) : arg0;
if (null == item) return null;
success = this.addObject(item);
if (!success) return null;
// if succeeded, notify widget
this.triggerAdded(item);
return item;
},
addObject: function(obj) {
// ensure obj is a valid Param
if (null == obj || !obj instanceof this.type()) return false;
// ensure no other parameter exists in the collection by that name
if (void 0 !== obj.name && null != this.getByName(obj.name())) return false;
// add to collection
this.push(obj);
return obj;
},
/**
* Creates an returns the item.
* @abstract
*/
create: function() {
// override me
return null;
},
/**
* Removes an item from the collection. Only removes the first instance of the item found.
* @method
* @param {object} obj - The item to remove.
* @returns {boolean} - True if remove succeeds.
*/
remove: function(name) {
var item = this.getByName(name);
if (null == item) return false;
if (!this.base_remove(item)) return false;
// if succeeded, notify widget
this.triggerRemoved(item);
return true;
},
/**
* Wraps the specified item in a new ObjectCollection.
* @method
* @param {object} item - The item to make a collection from.
* @returns {Svidget.ObjectCollection} - The collection.
*/
wrap: function(item) {
var items = [ item ];
(null == item || !item instanceof this.type()) && (items = []);
//, this.parent);
return new Svidget.Collection(items);
},
// internal
// wires up an internal handler when an item is added to the collection
onAdded: function(func) {
this.getset("addedFunc", func);
},
// internal
// wires up an internal handler when an item is removed from the collection
onRemoved: function(func) {
this.getset("removedFunc", func);
},
// private
triggerAdded: function(item) {
var func = this.getset("addedFunc");
func && func(item);
},
// private
triggerRemoved: function(item) {
var func = this.getset("removedFunc");
func && func(item);
}
}, true);
// overwrite base methods, copied above
Svidget.extend(Svidget.Collection, Svidget.ObjectPrototype);
/**
* Provides communications functionality between a page and a widget. Internal class only.
* @class
*/
Svidget.Communicator = function() {
this.__type = "Svidget.Communicator";
this.sameDomain = null;
this._init();
};
Svidget.Communicator.prototype = {
_init: function() {
this.addMessageEvent();
},
// REGION: Events
addMessageEvent: function() {
window.addEventListener && window.addEventListener("message", Svidget.wrap(this.receiveXSM, this), false);
},
// REGION: Receive
receiveFromParent: function(name, payload) {
svidget.receiveFromParent(name, payload);
},
receiveFromWidget: function(name, payload, widgetID) {
svidget.receiveFromWidget(name, payload, widgetID);
},
receiveXSM: function(message) {
if (null != message) {
var msgData = message.data;
null != msgData && (// it's possible in future we'll have a nested widget scenario, so a widget can have both a parent and widgets.
void 0 !== msgData.widget ? this.receiveFromWidget(msgData.name, msgData.payload, msgData.widget) : this.receiveFromParent(msgData.name, msgData.payload));
}
},
// REGION: Signal Parent
signalParent: function(name, payload, widgetID) {
// todo: cache result of isParentSameDomain
this.isParentSameDomain() ? this.signalParentDirect(name, payload, widgetID) : this.signalParentXSM(name, payload, widgetID);
},
signalParentDirect: function(name, payload, widgetID) {
//var check = window === window.parent;
// note: if window.parent.svidget is null it means the widget DOM was ready before the page DOM, although unlikely it is possible
// so we need to handle somehow
if (null != window.parent && window !== window.parent && null != window.parent.svidget && window.parent.svidget) {
var root = window.parent.svidget;
setTimeout(function() {
root.routeFromWidget(name, payload, widgetID);
}, 0);
}
},
signalParentXSM: function(name, payload, widgetID) {
if (null != window.parent && null != window.parent.postMessage) {
//alert('postMessage');
var msg = this.buildSignalParentMessage(name, payload, widgetID);
window.parent.postMessage(msg, "*");
}
},
buildSignalParentMessage: function(name, payload, widgetID) {
return {
name: name,
payload: payload,
widget: widgetID
};
},
// todo: move to widget
// note: this returns true when widget is forced cross domain
isParentSameDomain: function() {
null == this.sameParentDomain && (this.sameParentDomain = this.checkParentSameDomain());
return this.sameParentDomain;
},
// todo: move to Svidget.DOM
checkParentSameDomain: function() {
if (null == window.parent) return false;
try {
window.parent.document;
return true;
} catch (ex) {
return false;
}
},
// REGION: Widgets
signalWidget: function(widgetRef, name, payload) {
Svidget.log("communicator: signalWidget {name: " + name + "}");
widgetRef.isCrossDomain() ? this.signalWidgetXSM(widgetRef, name, payload) : //this.isWidgetSameDomain(widgetProxy))
this.signalWidgetDirect(widgetRef, name, payload);
},
signalWidgetDirect: function(widgetRef, name, payload) {
if (null != widgetRef) {
var root = widgetRef.root();
null != root && setTimeout(function() {
root.receiveFromParent(name, payload);
}, 0);
}
},
signalWidgetXSM: function(widgetRef, name, payload) {
if (null != widgetRef && null != widgetRef.window()) {
var msg = this.buildSignalWidgetMessage(name, payload);
//widgetRef.window().postMessage(msg, '*');
setTimeout(function() {
Svidget.log("communicator: postMessage");
widgetRef.window().postMessage(msg, "*");
}, 0);
}
},
buildSignalWidgetMessage: function(name, payload) {
return {
name: name,
payload: payload
};
}
};
/**
* Handles conversion to/from different types.
* @static
* @memberof Svidget
*/
Svidget.Conversion = {};
// type == string type i.e. "string", "number"
Svidget.Conversion.to = function(val, type, subtype, typedata) {
var t = Svidget.ParamTypes[type] || Svidget.ParamTypes.object, st = Svidget.ParamSubTypes[subtype] || Svidget.ParamSubTypes.none;
switch (t) {
case Svidget.ParamTypes.string:
return Svidget.Conversion.toString(val, st, typedata);
// todo: coerce when subtype choice, default to first one
case Svidget.ParamTypes.number:
return Svidget.Conversion.toNumber(val, st == Svidget.ParamSubTypes.integer);
case Svidget.ParamTypes.bool:
return Svidget.Conversion.toBool(val);
case Svidget.ParamTypes.array:
return Svidget.Conversion.toArray(val);
default:
return Svidget.Conversion.toObject(val);
}
};
/**
* Converts any value to a string.
* @static
*/
Svidget.Conversion.toString = function(val, subtype, typedata) {
return null == val ? null : subtype == Svidget.ParamSubTypes.choice ? Svidget.Conversion.toChoiceString(val, typedata) : Svidget.isArray(val) || "object" == typeof val ? JSON.stringify(val) : val + "";
};
/**
* Converts any value to a string among a choice list.
* @static
*/
Svidget.Conversion.toChoiceString = function(val, typedata) {
val += "";
if (null == typedata) return val;
var choices = typedata.split("|");
return null == choices || 0 == choices.length ? null : choices.indexOf(val) >= 0 ? val : choices[0];
};
/**
* Converts any value to a number.
* @static
*/
Svidget.Conversion.toNumber = function(val, isInt) {
if (!val) return 0;
// null, undefined, false all convert to 0
if (true === val) return 1;
// true converts to 1
if (isInt) return parseInt(val + "");
val = parseFloat(val);
isNaN(val) && (val = 0);
return val;
};
/**
* Converts any value to a bool.
* @static
*/
Svidget.Conversion.toBool = function(val) {
return "false" != (val + "").toLowerCase() && 0 != +val && !!val;
};
/**
* Converts any value to a array.
* @static
*/
Svidget.Conversion.toArray = function(val) {
if (null == val) return val;
// if val is array just return it
if (Svidget.isArray(val)) return val;
// if val is stringified array, JSON parse and return array
if (Svidget.Conversion.isArrayString(val)) {
var a = Svidget.Conversion.parseArray(val);
if (null != a) return a;
}
// else wrap as an array
return [ val ];
};
/**
* Converts any value to a object.
* @static
*/
Svidget.Conversion.toObject = function(val) {
if (null == val) return val;
// if val is valid JSON string, JSON parse and return array
if (Svidget.Conversion.isJSONString(val)) {
var newval = Svidget.Conversion.jsonifyString(val);
// convert single to double chars or else parser chokes
try {
return JSON.parse(newval);
} catch (ex) {}
}
// else return original val
return val;
};
Svidget.Conversion.isJSONString = function(val) {
// note: this doesn't check if its strictly JSON, just does a sanity check before attempting to convert
if (!val) return false;
val = (val + "").trim();
return val.length > 0 && Svidget.isString(val) && "{" == val.charAt(0) && "}" == val.charAt(val.length - 1);
};
Svidget.Conversion.isArrayString = function(val) {
if (null == val) return false;
val = (val + "").trim();
return val.length > 0 && "[" == val.charAt(0) && "]" == val.charAt(val.length - 1);
};
Svidget.Conversion.isQuotedString = function(val) {
if (!val) return false;
val = (val + "").trim();
return val.length > 0 && Svidget.isString(val) && ("'" == val.charAt(0) && "'" == val.charAt(val.length - 1) || '"' == val.charAt(0) && '"' == val.charAt(val.length - 1));
};
Svidget.Conversion.parseArray = function(val) {
val = Svidget.Conversion.jsonifyString(val);
// convert single to double chars or else parser chokes
var wrap = '{"d":' + val + "}";
// note, this fails when the array uses single quotes
try {
var result = JSON.parse(wrap);
return result && result.d ? result.d : null;
} catch (ex) {
// not an array after all
return null;
}
};
// ['hello', 'world']
// ['hello', 'world"it"']
// ['hello', ['world"it"']]
// ['he\'ll\'o', ['world"it"']]
// try this if below doesn't work: https://gist.github.com/thejsj/aad9c0392a59a7d87d9c
Svidget.Conversion.jsonifyString = function(val) {
if (null == val || val.indexOf("'") < 0) return val;
val = (val + "").trim();
for (var SQ = "'", DQ = '"', BS = "\\", result = "", inQuotes = false, quoteChar = null, escaped = false, i = 0; i < val.length; i++) {
var char = val[i], newchar = char;
// 'he\'ll\'o' => "he'll'o"
// 'world"it"' => "world\"it\""
if (char == SQ || char == DQ) {
if (escaped) {
quoteChar == SQ && char == SQ ? result = result.substr(0, result.length - 1) : quoteChar == SQ && char == DQ && (// add a backslash because one was there before for double quote i.e. 'hello\"there' => "hello\\\"there"
newchar = BS + BS + DQ);
escaped = false;
} else if (inQuotes && char == quoteChar) {
inQuotes = false;
quoteChar = null;
newchar = DQ;
} else if (inQuotes && char == DQ) // handle case of 'world"it"' => "world\"it\"" need to add backslashes
newchar = BS + DQ; else if (!inQuotes) {
quoteChar = char;
inQuotes = true;
newchar = DQ;
}
} else char == BS && (escaped = true);
result += newchar;
}
return result;
};
/**
* A collection of methods for working with DOM elements.
* @static
* @memberof Svidget
*/
Svidget.DOM = {
// REMARKS
// Wrapper for getElementById
get: function(sel) {
return document.getElementById ? document.getElementById(sel) : null;
},
getByName: function(tagName, asCollection) {
return this.getChildrenByName(document, tagName, asCollection);
},
getByNameNS: function(namespace, tagName, asCollection) {
if (!document.getElementsByTagNameNS) return null;
var tags = document.getElementsByTagNameNS(namespace, tagName);
return asCollection ? new Svidget.Collection(Svidget.array(tags)) : tags;
},
// Gets elements by tag name belonging to the svidget namespace
getByNameSvidget: function(tagName, asCollection) {
return this.getByNameNS(Svidget.Namespaces.svidget, tagName, asCollection);
},
getChildrenByName: function(source, tagName, asCollection) {
var tags = source.getElementsByTagName(tagName);
return asCollection ? new Svidget.Collection(Svidget.array(tags)) : tags;
},
// if sel is a string calls get()
getElement: function(sel) {
return "string" == typeof sel ? this.get(sel) : sel;
},
// gets an element by ID and returns a DomItem
getItem: function(sel) {
return this.wrap(this.get(sel));
},
// returns a DomQuery object
select: function(sel) {
// given a selector string free from any commas, looks up the elements/attributes for it
// and returns a collection.
function selectSingle(s) {
if (null == s) return null;
var attrName, attrRXEnd = /@([^=#\s]+)$/g, sel = s, match = attrRXEnd.exec(s);
if (match) {
attrName = match[1];
// match the subgroup
sel = sel.replace(match[0], "");
}
// query
var eles;
try {
eles = document.querySelectorAll(sel);
} catch (ex) {
// seelctor was invalid, so just return null
return null;
}
for (var col = [], i = 0; i < eles.length; i++) {
var item, ele = eles[i];
if (attrName) {
var attr = Svidget.DOM.attr(ele, attrName);
null != attr && (item = new Svidget.DOMItem(attr));
} else item = new Svidget.DOMItem(ele);
item && col.push(item);
}
return new Svidget.Collection(col);
}
// todo: support jQuery selector if querySelectorAll fails, it might be loaded on the page so we might as well use it if we can
if (!document.querySelectorAll) return null;
if (null == sel) return null;
// new Svidget.DomQuery();
// use querySelectorAll
var res, attrRX = /@[^=#\s]+/g, hasAttr = attrRX.test(sel), col = new Svidget.Collection();
// if selector has an attribute in it, split it first by comma
if (hasAttr) for (var parts = sel.split(","), i = 0; i < parts.length; i++) {
res = selectSingle(parts[i]);
res && col.addRange(res);
} else {
res = selectSingle(sel);
// its possible selector was bad, in which case we get null, so check
res && col.addRange(res);
}
return new Svidget.DOMQuery(col, sel);
},
// Returns the first DOM element in a query
selectElement: function(sel) {
if ("string" == typeof sel) {
var q = this.select(sel);
return null == q || 0 == q.length ? null : q.item(0).source();
}
return this.isElement(sel) ? sel : null;
},
// returns a DomItem if ele is a element or attribute object
// else returns the original item
wrap: function(ele) {
return new Svidget.DOMItem(ele);
},
// converts an HTML or SVG DOM node into a pure object literal used to transport
transportize: function(ele) {
return null == ele ? null : {
name: ele.localName,
namespace: ele.namespaceURI,
value: ele.value,
type: 1 == ele.nodeType ? Svidget.NodeType.element : 2 == ele.nodeType ? Svidget.NodeType.attribute : null
};
},
// Elements
root: function() {
return document.documentElement;
},
rootItem: function() {
// should we cache?
return this.wrap(this.root());
},
attr: function(ele, attrName) {
return ele.attributes[attrName];
},
attrValue: function(ele, attrName) {
var a = this.attr(ele, attrName);
return a ? a.value : null;
},
// determines if attribute is specified but empty/no value (i.e.
isAttrEmpty: function(ele, attrName) {
var av = this.attrValue(ele, attrName);
return null != av && 0 == av.length;
},
clone: function(item) {},
cloneDetached: function(item) {},
isDOMNode: function(node) {
return null == node ? null : !(null == node.namespaceURI || null == node.localName || null == node.nodeType || null == node.value && null == node.textContent || 1 != node.nodeType && 2 != node.nodeType);
},
fromNodeType: function(type) {
return 1 == type ? "element" : 2 == type ? "attribute" : 3 == type ? "text" : null;
},
// TEXT
text: function(sel, text) {
var obj = this.select(sel);
if (void 0 === text) return this.getText(obj);
this.setText(obj, text);
},
getText: function(obj) {
return obj.textContent ? obj.textContent : obj.innerHTML ? obj.innerHTML : null;
},
setText: function(obj, text) {
obj.textContent ? obj.textContent = text + "" : obj.innerHTML && (obj.innerHTML = text + "");
},
// returns null - it means document hasn't loaded yet
// returns undefined - it means document is loaded but not accessible due to security (cross domain) constraints
getDocument: function(objOrWinEle) {
try {
var doc = objOrWinEle.contentDocument;
null != objOrWinEle.contentWindow && objOrWinEle.contentWindow.document;
// certain browsers (Chrome) returns a blank document instead of null
// certain browsers (Chrome) returns a blank document instead of null
return null != doc && "about:blank" == doc.URL ? null : doc;
} catch (ex) {
return;
}
},
// determines if document for