This repository has been archived on 2018-10-12. You can view files and clone it, but cannot push or open issues or pull requests.
node-task/public/js/ink.common.js

1065 lines
38 KiB
JavaScript
Raw Normal View History

2014-09-24 17:57:25 -04:00
/**
* Auxiliar utilities for UI Modules
* @module Ink.UI.Common_1
* @version 1
*/
Ink.createModule('Ink.UI.Common', '1', ['Ink.Dom.Element_1', 'Ink.Net.Ajax_1','Ink.Dom.Css_1','Ink.Dom.Selector_1','Ink.Util.Url_1'], function(InkElement, Ajax,Css,Selector,Url) {
'use strict';
var nothing = {} /* a marker, for reference comparison. */;
var keys = Object.keys || function (obj) {
var ret = [];
for (var k in obj) if (obj.hasOwnProperty(k)) {
ret.push(k);
}
return ret;
};
var es6WeakMapSupport = 'WeakMap' in window;
var instances = es6WeakMapSupport ? new WeakMap() : null;
var domRegistry = {
get: function get(el) {
return es6WeakMapSupport ?
instances.get(el) :
el.__InkInstances;
},
set: function set(el, thing) {
if (es6WeakMapSupport) {
instances.set(el, thing);
} else {
el.__InkInstances = thing;
}
}
};
/**
* @namespace Ink.UI.Common_1
*/
var Common = {
/**
* Supported Ink Layouts
*
* @property Layouts
* @type Object
* @readOnly
*/
Layouts: {
TINY: 'tiny',
SMALL: 'small',
MEDIUM: 'medium',
LARGE: 'large',
XLARGE: 'xlarge'
},
/**
* Checks if an item is a valid DOM Element.
*
* @method isDOMElement
* @static
* @param {Mixed} o The object to be checked.
* @return {Boolean} True if it's a valid DOM Element.
* @example
* var el = Ink.s('#element');
* if( Ink.UI.Common.isDOMElement( el ) === true ){
* // It is a DOM Element.
* } else {
* // It is NOT a DOM Element.
* }
*/
isDOMElement: InkElement.isDOMElement,
/**
* Checks if an item is a valid integer.
*
* @method isInteger
* @static
* @param {Mixed} n The value to be checked.
* @return {Boolean} True if it's a valid integer.
* @example
* var value = 1;
* if( Ink.UI.Common.isInteger( value ) === true ){
* // It is an integer.
* } else {
* // It is NOT an integer.
* }
*/
isInteger: function(n) {
return (typeof n === 'number' && n % 1 === 0);
},
/**
* Gets a DOM Element.
*
* @method elOrSelector
* @static
* @param {DOMElement|String} elOrSelector DOM Element or CSS Selector
* @param {String} fieldName The name of the field. Commonly used for debugging.
* @return {DOMElement} Returns the DOMElement passed or the first result of the CSS Selector. Otherwise it throws an exception.
* @example
* // In case there are several .myInput, it will retrieve the first found
* var el = Ink.UI.Common.elOrSelector('.myInput','My Input');
*/
elOrSelector: function(elOrSelector, fieldName) {
if (!this.isDOMElement(elOrSelector)) {
var t = Selector.select(elOrSelector);
if (t.length === 0) {
Ink.warn(fieldName + ' must either be a DOM Element or a selector expression!\nThe script element must also be after the DOM Element itself.');
return null;
}
return t[0];
}
return elOrSelector;
},
/**
* Alias for `elOrSelector` but returns an array of elements.
*
* @method elsOrSelector
*
* @static
* @param {DOMElement|String} elOrSelector DOM Element or CSS Selector
* @param {String} fieldName The name of the field. Commonly used for debugging.
* @return {DOMElement} Returns the DOMElement passed or the first result of the CSS Selector. Otherwise it throws an exception.
* @param {Boolean} required Flag to accept an empty array as output.
* @return {Array} The selected DOM Elements.
* @example
* var elements = Ink.UI.Common.elsOrSelector('input.my-inputs', 'My Input');
*/
elsOrSelector: function(elsOrSelector, fieldName, required) {
var ret;
if (typeof elsOrSelector === 'string') {
ret = Selector.select(elsOrSelector);
} else if (Common.isDOMElement(elsOrSelector)) {
ret = [elsOrSelector];
} else if (elsOrSelector && typeof elsOrSelector === 'object' && typeof elsOrSelector.length === 'number') {
ret = elsOrSelector;
}
if (ret && ret.length) {
return ret;
} else {
if (required) {
throw new TypeError(fieldName + ' must either be a DOM Element, an Array of elements, or a selector expression!\nThe script element must also be after the DOM Element itself.');
} else {
return [];
}
}
},
/**
* Gets options an object and element's metadata.
*
* The element's data attributes take precedence. Values from the element's data-atrributes are coerced into the required type.
*
* @method options
*
* @param {Object} [fieldId] Name to be used in debugging features.
* @param {Object} defaults Object with the options' types and defaults.
* @param {Object} overrides Options to override the defaults. Usually passed when instantiating an UI module.
* @param {DOMElement} [element] Element with data-attributes
*
* @example
*
* this._options = Ink.UI.Common.options('MyComponent', {
* 'anobject': ['Object', null], // Defaults to null
* 'target': ['Element', null],
* 'stuff': ['Number', 0.1],
* 'stuff2': ['Integer', 0],
* 'doKickFlip': ['Boolean', false],
* 'targets': ['Elements'], // Required option since no default was given
* 'onClick': ['Function', null]
* }, options || {}, elm)
*
* @example
*
* ### Note about booleans
*
* Here is how options are read from the markup
* data-attributes, for several values`data-a-boolean`.
*
* Options considered true:
*
* - `data-a-boolean="true"`
* - (Every other value which is not on the list below.)
*
* Options considered false:
*
* - `data-a-boolean="false"`
* - `data-a-boolean=""`
* - `data-a-boolean`
*
* Options which go to default:
*
* - (no attribute). When `data-a-boolean` is ommitted, the
* option is not considered true nor false, and as such
* defaults to what is in the `defaults` argument.
*
**/
options: function (fieldId, defaults, overrides, element) {
if (typeof fieldId !== 'string') {
element = overrides;
overrides = defaults;
defaults = fieldId;
fieldId = '';
}
overrides = overrides || {};
var out = {};
var dataAttrs = element ? InkElement.data(element) : {};
var fromDataAttrs;
var type;
var lType;
var defaultVal;
var invalidStr = function (str) {
if (fieldId) { str = fieldId + ': "' + ('' + str).replace(/"/, '\\"') + '"'; }
return str;
};
var quote = function (str) {
return '"' + ('' + str).replace(/"/, '\\"') + '"';
};
var invalidThrow = function (str) {
throw new Error(invalidStr(str));
};
var invalid = function (str) {
Ink.error(invalidStr(str) + '. Ignoring option.');
};
function optionValue(key) {
type = defaults[key][0];
lType = type.toLowerCase();
defaultVal = defaults[key].length === 2 ? defaults[key][1] : nothing;
if (!type) {
invalidThrow('Ink.UI.Common.options: Always specify a type!');
}
if (!(lType in Common._coerce_funcs)) {
invalidThrow('Ink.UI.Common.options: ' + defaults[key][0] + ' is not a valid type. Use one of ' + keys(Common._coerce_funcs).join(', '));
}
if (!defaults[key].length || defaults[key].length > 2) {
invalidThrow('the "defaults" argument must be an object mapping option names to [typestring, optional] arrays.');
}
if (key in dataAttrs) {
fromDataAttrs = Common._coerce_from_string(lType, dataAttrs[key], key, fieldId);
// (above can return `nothing`)
} else {
fromDataAttrs = nothing;
}
if (fromDataAttrs !== nothing) {
if (!Common._options_validate(fromDataAttrs, lType)) {
invalid('(' + key + ' option) Invalid ' + lType + ' ' + quote(fromDataAttrs));
return defaultVal;
} else {
return fromDataAttrs;
}
} else if (key in overrides) {
return overrides[key];
} else if (defaultVal !== nothing) {
return defaultVal;
} else {
invalidThrow('Option ' + key + ' is required!');
}
}
for (var key in defaults) {
if (defaults.hasOwnProperty(key)) {
out[key] = optionValue(key);
}
}
return out;
},
_coerce_from_string: function (type, val, paramName, fieldId) {
if (type in Common._coerce_funcs) {
return Common._coerce_funcs[type](val, paramName, fieldId);
} else {
return val;
}
},
_options_validate: function (val, type) {
if (type in Common._options_validate_types) {
return Common._options_validate_types[type].call(Common, val);
} else {
// 'object' options cannot be passed through data-attributes.
// Json you say? Not any good to embed in HTML.
return false;
}
},
_coerce_funcs: (function () {
var ret = {
element: function (val) {
return Common.elOrSelector(val, '');
},
elements: function (val) {
return Common.elsOrSelector(val, '', false /*not required, so don't throw an exception now*/);
},
object: function (val) { return val; },
number: function (val) { return parseFloat(val); },
'boolean': function (val) {
return !(val === 'false' || val === '' || val === null);
},
string: function (val) { return val; },
'function': function (val, paramName, fieldId) {
Ink.error(fieldId + ': You cannot specify the option "' + paramName + '" through data-attributes because it\'s a function');
return nothing;
}
};
ret['float'] = ret.integer = ret.number;
return ret;
}()),
_options_validate_types: (function () {
var types = {
string: function (val) {
return typeof val === 'string';
},
number: function (val) {
return typeof val === 'number' && !isNaN(val) && isFinite(val);
},
integer: function (val) {
return val === Math.round(val);
},
element: function (val) {
return Common.isDOMElement(val);
},
elements: function (val) {
return val && typeof val === 'object' && typeof val.length === 'number' && val.length;
},
'boolean': function (val) {
return typeof val === 'boolean';
},
object: function () { return true; }
};
types['float'] = types.number;
return types;
}()),
/**
* Deep copy (clone) an object.
* Note: The object cannot have referece loops.
*
* @method clone
* @static
* @param {Object} o The object to be cloned/copied.
* @return {Object} Returns the result of the clone/copy.
* @example
* var originalObj = {
* key1: 'value1',
* key2: 'value2',
* key3: 'value3'
* };
* var cloneObj = Ink.UI.Common.clone( originalObj );
*/
clone: function(o) {
try {
return JSON.parse( JSON.stringify(o) );
} catch (ex) {
throw new Error('Given object cannot have loops!');
}
},
/**
* Gets an element's one-base index relative to its parent.
*
* @method childIndex
* @static
* @param {DOMElement} childEl Valid DOM Element.
* @return {Number} Numerical position of an element relatively to its parent.
* @example
* <!-- Imagine the following HTML: -->
* <ul>
* <li>One</li>
* <li>Two</li>
* <li id="test">Three</li>
* <li>Four</li>
* </ul>
*
* <script>
* var testLi = Ink.s('#test');
* Ink.UI.Common.childIndex( testLi ); // Returned value: 3
* </script>
*/
childIndex: function(childEl) {
if( Common.isDOMElement(childEl) ){
var els = Selector.select('> *', childEl.parentNode);
for (var i = 0, f = els.length; i < f; ++i) {
if (els[i] === childEl) {
return i;
}
}
}
throw 'not found!';
},
/**
* AJAX JSON request shortcut method
* It provides a more convenient way to do an AJAX request and expect a JSON response.It also offers a callback option, as third parameter, for better async handling.
*
* @method ajaxJSON
* @static
* @async
* @param {String} endpoint Valid URL to be used as target by the request.
* @param {Object} params This field is used in the thrown Exception to identify the parameter.
* @param {Function} cb Callback for the request.
* @example
* // In case there are several .myInput, it will retrieve the first found
* var el = Ink.UI.Common.elOrSelector('.myInput','My Input');
*/
ajaxJSON: function(endpoint, params, cb) {
new Ajax(
endpoint,
{
evalJS: 'force',
method: 'POST',
parameters: params,
onSuccess: function( r) {
try {
r = r.responseJSON;
if (r.status !== 'ok') {
throw 'server error: ' + r.message;
}
cb(null, r);
} catch (ex) {
cb(ex);
}
},
onFailure: function() {
cb('communication failure');
}
}
);
},
/**
* Gets the current Ink layout.
*
* @method currentLayout
* @static
* @return {String} A string representation of the current layout name.
* @example
* var inkLayout = Ink.UI.Common.currentLayout();
* if (inkLayout === 'small') {
* // ...
* }
*/
currentLayout: function() {
var i, f, k, v, el, detectorEl = Selector.select('#ink-layout-detector')[0];
if (!detectorEl) {
detectorEl = document.createElement('div');
detectorEl.id = 'ink-layout-detector';
for (k in this.Layouts) {
if (this.Layouts.hasOwnProperty(k)) {
v = this.Layouts[k];
el = document.createElement('div');
el.className = 'show-' + v + ' hide-all';
el.setAttribute('data-ink-layout', v);
detectorEl.appendChild(el);
}
}
document.body.appendChild(detectorEl);
}
for (i = 0, f = detectorEl.children.length; i < f; ++i) {
el = detectorEl.children[i];
if (Css.getStyle(el, 'display') === 'block') {
return el.getAttribute('data-ink-layout');
}
}
return 'large';
},
/**
* Sets the location's hash (window.location.hash).
*
* @method hashSet
* @static
* @param {Object} o Object with the info to be placed in the location's hash.
* @example
* // It will set the location's hash like: <url>#key1=value1&key2=value2&key3=value3
* Ink.UI.Common.hashSet({
* key1: 'value1',
* key2: 'value2',
* key3: 'value3'
* });
*/
hashSet: function(o) {
if (typeof o !== 'object') { throw new TypeError('o should be an object!'); }
var hashParams = Url.getAnchorString();
hashParams = Ink.extendObj(hashParams, o);
window.location.hash = Url.genQueryString('', hashParams).substring(1);
},
/**
* Removes children nodes from a given object.
* This method was initially created to help solve a problem in Internet Explorer(s) that occurred when trying to set the innerHTML of some specific elements like 'table'.
*
* @method cleanChildren
* @static
* @param {DOMElement} parentEl Valid DOM Element
* @example
* <!-- Imagine the following HTML: -->
* <ul id="myUl">
* <li>One</li>
* <li>Two</li>
* <li>Three</li>
* <li>Four</li>
* </ul>
*
* <script>
* Ink.UI.Common.cleanChildren( Ink.s( '#myUl' ) );
* </script>
*
* <!-- After running it, the HTML changes to: -->
* <ul id="myUl"></ul>
*/
cleanChildren: function(parentEl) {
if( !Common.isDOMElement(parentEl) ){
throw 'Please provide a valid DOMElement';
}
var prevEl, el = parentEl.lastChild;
while (el) {
prevEl = el.previousSibling;
parentEl.removeChild(el);
el = prevEl;
}
},
/**
* Stores the id and/or classes of an element in an object.
*
* @method storeIdAndClasses
* @static
* @param {DOMElement} fromEl Valid DOM Element to get the id and classes from.
* @param {Object} inObj Object where the id and classes will be saved.
* @example
* <div id="myDiv" class="aClass"></div>
*
* <script>
* var storageObj = {};
* Ink.UI.Common.storeIdAndClasses( Ink.s('#myDiv'), storageObj );
* // storageObj changes to:
* {
* _id: 'myDiv',
* _classes: 'aClass'
* }
* </script>
*/
storeIdAndClasses: function(fromEl, inObj) {
if( !Common.isDOMElement(fromEl) ){
throw 'Please provide a valid DOMElement as first parameter';
}
var id = fromEl.id;
if (id) {
inObj._id = id;
}
var classes = fromEl.className;
if (classes) {
inObj._classes = classes;
}
},
/**
* Sets the id and className properties of an element based
*
* @method restoreIdAndClasses
* @static
* @param {DOMElement} toEl Valid DOM Element to set the id and classes on.
* @param {Object} inObj Object where the id and classes to be set are. This method uses the same format as the one given in `storeIdAndClasses`
* @example
* <div></div>
*
* <script>
* var storageObj = {
* _id: 'myDiv',
* _classes: 'aClass'
* };
*
* Ink.UI.Common.storeIdAndClasses( Ink.s('div'), storageObj );
* </script>
*
* <!-- After the code runs the div element changes to: -->
* <div id="myDiv" class="aClass"></div>
*/
restoreIdAndClasses: function(toEl, inObj) {
if( !Common.isDOMElement(toEl) ){
throw 'Please provide a valid DOMElement as first parameter';
}
if (inObj._id && toEl.id !== inObj._id) {
toEl.id = inObj._id;
}
if (inObj._classes && toEl.className.indexOf(inObj._classes) === -1) {
if (toEl.className) { toEl.className += ' ' + inObj._classes; }
else { toEl.className = inObj._classes; }
}
if (inObj._instanceId && !toEl.getAttribute('data-instance')) {
toEl.setAttribute('data-instance', inObj._instanceId);
}
},
_warnDoubleInstantiation: function (elm, newInstance) {
var instances = Common.getInstance(elm);
if (getName(newInstance) === '') { return; }
if (!instances) { return; }
var nameWithoutVersion = getName(newInstance);
if (!nameWithoutVersion) { return; }
for (var i = 0, len = instances.length; i < len; i++) {
if (nameWithoutVersion === getName(instances[i])) {
// Yes, I am using + to concatenate and , to split
// arguments.
//
// Elements can't be concatenated with strings, but if
// they are passed in an argument, modern debuggers will
// pretty-print them and make it easy to find them in the
// element inspector.
//
// On the other hand, if strings are passed as different
// arguments, they get pretty printed. And the pretty
// print of a string has quotes around it.
//
// If some day people find out that strings are not
// just text and they start preserving contextual
// information, then by all means change this to a
// regular concatenation.
//
// But they won't. So don't change this.
Ink.warn('Creating more than one ' + nameWithoutVersion + '.',
'(Was creating a ' + nameWithoutVersion + ' on:', elm, ').');
return false;
}
}
function getName(thing) {
return ((thing.constructor && (thing.constructor._name)) ||
thing._name ||
'').replace(/_.*?$/, '');
}
return true;
},
/**
* Saves a component's instance reference for later retrieval.
*
* @method registerInstance
* @static
* @param {Object} inst Object that holds the instance.
* @param {DOMElement} el DOM Element to associate with the object.
*/
registerInstance: function(inst, el) {
if (!inst) { return; }
if (!Common.isDOMElement(el)) { throw new TypeError('Ink.UI.Common.registerInstance: The element passed in is not a DOM element!'); }
// [todo] this belongs in the BaseUIComponent's initialization
if (Common._warnDoubleInstantiation(el, inst) === false) {
return false;
}
var instances = domRegistry.get(el);
if (!instances) {
instances = [];
domRegistry.set(el, instances);
}
instances.push(inst);
return true;
},
/**
* Deletes an instance with a given id.
*
* @method unregisterInstance
* @static
* @param {String} id Id of the instance to be destroyed.
*/
unregisterInstance: function(inst) {
if (!inst || !inst._element) { return; }
var instances = domRegistry.get(inst._element);
for (var i = 0, len = instances.length; i < len; i++) {
if (instances[i] === inst) {
instances.splice(i, 1);
}
}
},
/**
* Gets an UI instance from an element or instance id.
*
* @method getInstance
* @static
* @param {String|DOMElement} el DOM Element from which we want the instances.
* @return {Object|Array} Returns an instance or a collection of instances.
*/
getInstance: function(el, UIComponent) {
el = Common.elOrSelector(el);
var instances = domRegistry.get(el);
if (!instances) {
instances = [];
}
if (typeof UIComponent !== 'function') {
return instances;
}
for (var i = 0, len = instances.length; i < len; i++) {
if (instances[i] instanceof UIComponent) {
return instances[i];
}
}
return null;
},
/**
* Gets an instance based on a selector.
*
* @method getInstanceFromSelector
* @static
* @param {String} selector CSS selector to get the instances from.
* @return {Object|Array} Returns an instance or a collection of instances.
*/
getInstanceFromSelector: function(selector) {
return Common.getInstance(Common.elOrSelector(selector));
},
/**
* Gets all the instance ids
*
* @method getInstanceIds
* @static
* @return {Array} Collection of instance ids
*/
getInstanceIds: function() {
var res = [];
for (var id in instances) {
if (instances.hasOwnProperty(id)) {
res.push( id );
}
}
return res;
},
/**
* Gets all the instances
*
* @method getInstances
* @static
* @return {Array} Collection of existing instances.
*/
getInstances: function() {
var res = [];
for (var id in instances) {
if (instances.hasOwnProperty(id)) {
res.push( instances[id] );
}
}
return res;
},
/**
* Boilerplate method to destroy a component.
* Components should copy this method as its destroy method and modify it.
*
* @method destroyComponent
* @static
*/
destroyComponent: function() {
Common.unregisterInstance(this);
this._element.parentNode.removeChild(this._element);
}
};
/**
* Ink UI Base Class
**/
function warnStub() {
/* jshint validthis: true */
if (!this || this === window || typeof this.constructor !== 'function') { return; }
Ink.warn('You called a method on an incorrectly instantiated ' + this.constructor._name + ' component. Check the warnings above to see what went wrong.');
}
function stub(prototype, obj) {
for (var k in prototype) if (prototype.hasOwnProperty(k)) {
if (k === 'constructor') { continue; }
if (typeof obj[k] === 'function') {
obj[k] = warnStub;
}
}
}
/**
* Ink UI Base Class
*
* You don't use this class directly, or inherit from it directly.
*
* See createUIComponent() (in this module) for how to create a UI component and inherit from this. It's not plain old JS inheritance, for several reasons.
*
* @class Ink.UI.Common.BaseUIComponent
* @constructor
*
* @param element
* @param options
**/
function BaseUIComponent(element, options) {
var constructor = this.constructor;
var _name = constructor._name;
if (!this || this === window) {
throw new Error('Use "new InkComponent()" instead of "InkComponent()"');
}
if (this && !(this instanceof BaseUIComponent)) {
throw new Error('You forgot to call Ink.UI.Common.createUIComponent() on this module!');
}
if (!element && !constructor._componentOptions.elementIsOptional) {
Ink.error(new Error(_name + ': You need to pass an element or a selector as the first argument to "new ' + _name + '()"'));
return;
} else {
this._element = Common.elsOrSelector(element,
_name + ': An element with the selector "' + element + '" was not found!')[0];
}
if (!this._element && !constructor._componentOptions.elementIsOptional) {
isValidInstance = false;
Ink.error(new Error(element + ' does not match an element on the page. You need to pass a valid selector to "new ' + _name + '".'));
}
// TODO Change Common.options's signature? the below looks better, more manageable
// var options = Common.options({
// element: this._element,
// modName: constructor._name,
// options: constructor._optionDefinition,
// defaults: constructor._globalDefaults
// });
this._options = Common.options(_name, constructor._optionDefinition, options, this._element);
var isValidInstance = BaseUIComponent._validateInstance(this) === true;
if (isValidInstance && typeof this._init === 'function') {
try {
this._init.apply(this, arguments);
} catch(e) {
isValidInstance = false;
Ink.error(e);
}
}
if (!isValidInstance) {
BaseUIComponent._stubInstance(this, constructor, _name);
} else if (this._element) {
Common.registerInstance(this, this._element);
}
}
/**
* Calls the `instance`'s _validate() method so it can validate itself.
*
* Returns false if the method exists, was called, but no Error was returned or thrown.
*
* @method _validateInstance
* @private
*/
BaseUIComponent._validateInstance = function (instance) {
var err;
if (typeof instance._validate !== 'function') { return true; }
try {
err = instance._validate();
} catch (e) {
err = e;
}
if (err instanceof Error) {
instance._validationError = err;
return false;
}
return true;
};
/**
* Replaces every method in the instance with stub functions which just call Ink.warn().
*
* This avoids breaking the page when there are errors.
*
* @method _stubInstance
* @param instance
* @param constructor
* @param name
* @private
*/
BaseUIComponent._stubInstance = function (instance, constructor, name) {
stub(constructor.prototype, instance);
stub(BaseUIComponent.prototype, instance);
Ink.warn(name + ' was not correctly created. ' + (instance._validationError || ''));
};
// TODO BaseUIComponent.setGlobalOptions = function () {}
// TODO BaseUIComponent.createMany = function (selector) {}
BaseUIComponent.getInstance = function (elOrSelector) {
elOrSelector = Common.elOrSelector(elOrSelector);
return Common.getInstance(elOrSelector, this /* get instance by constructor */);
};
Ink.extendObj(BaseUIComponent.prototype, {
/**
* Get an UI component's option's value.
*
* @method getOption
* @param name
*
* @return The option value, or undefined if nothing is found.
*
* @example
*
* var myUIComponent = new Modal('#element', { trigger: '#trigger' }); // or anything else inheriting BaseUIComponent
* myUIComponent.getOption('trigger'); // -> The trigger element (not the selector string, mind you)
*
**/
getOption: function (name) {
if (this.constructor && !(name in this.constructor._optionDefinition)) {
Ink.error('"' + name + '" is not an option for ' + this.constructor._name);
return undefined;
}
return this._options[name];
},
/**
* Sets an option's value
*
* @method getOption
* @param name
* @param value
*
* @example
*
* var myUIComponent = new Modal(...);
* myUIComponent.setOption('trigger', '#some-element');
**/
setOption: function (name, value) {
if (this.constructor && !(name in this.constructor._optionDefinition)) {
Ink.error('"' + name + ' is not an option for ' + this.constructor._name);
return;
}
this._options[name] = value;
},
/**
* Get the element associated with an UI component (IE the one you used in the constructor)
*
* @method getElement
* @return {Element} The component's element.
*
* @example
* var myUIComponent = new Modal('#element'); // or anything else inheriting BaseUIComponent
* myUIComponent.getElement(); // -> The '#element' (not the selector string, mind you).
*
**/
getElement: function () {
return this._element;
}
});
Common.BaseUIComponent = BaseUIComponent;
/**
* @method createUIComponent
* @param theConstructor UI component constructor. It should have an _init function in its prototype, an _optionDefinition object, and a _name property indicating its name.
* @param options
* @param [options.elementIsOptional=false] Whether the element argument is optional (For example, when the component might work on existing markup or create its own).
**/
Common.createUIComponent = function createUIComponent(theConstructor, options) {
theConstructor._componentOptions = options || {};
function assert(test, msg) {
if (!test) {
throw new Error('Ink.UI_1.createUIComponent: ' + msg);
}
}
function assertProp(prop, propType, message) {
var propVal = theConstructor[prop];
// Check that the property was passed
assert(typeof propVal !== 'undefined',
theConstructor + ' doesn\'t have a "' + prop + '" property. ' + message);
// Check that its type is correct
assert(propType && typeof propVal === propType,
'typeof ' + theConstructor + '.' + prop + ' is not "' + propType + '". ' + message);
}
assert(typeof theConstructor === 'function',
'constructor argument is not a function!');
assertProp('_name', 'string', 'This property is used for error ' +
'messages. Set it to the full module path and version (Ink.My.Module_1).');
assertProp('_optionDefinition', 'object', 'This property contains the ' +
'option names, types and defaults. See Ink.UI.Common.options() for reference.');
// Extend the instance methods and props
var _oldProto = theConstructor.prototype;
if (typeof Object.create === 'function') {
theConstructor.prototype = Object.create(BaseUIComponent.prototype);
} else {
theConstructor.prototype = (function hideF() {
function F() {}
F.prototype = BaseUIComponent.prototype;
return new F();
}());
}
Ink.extendObj(theConstructor.prototype, _oldProto);
theConstructor.prototype.constructor = theConstructor;
// Extend static methods
Ink.extendObj(theConstructor, BaseUIComponent);
};
return Common;
});