and \n *\n * @method _createRowsFromJSON\n * @param {Object} rows Array of objects with the data to be showed\n * @private\n */\n _createRowsFromJSON: function( rows ){\n var tbody = Selector.select('tbody',this._rootElement)[0];\n\n if( !tbody ){\n tbody = document.createElement('tbody');\n this._rootElement.appendChild( tbody );\n } else {\n Element.setHTML(tbody, '');\n }\n\n this._data = [];\n var row;\n\n for (var trIndex in rows) {\n if (rows.hasOwnProperty(trIndex)) {\n row = this._options.processJSONRow(rows[trIndex]);\n this._createSingleRowFromJson(tbody, row, trIndex);\n }\n }\n\n this._originalData = this._data.slice(0);\n },\n\n _createSingleRowFromJson: function (tbody, row, rowIndex) {\n var tr = document.createElement('tr');\n tbody.appendChild( tr );\n for( var field in row ){\n if (row.hasOwnProperty(field)) {\n this._createFieldFromJson(tr, row[field], field, rowIndex);\n }\n }\n this._data.push(tr);\n },\n\n _createFieldFromJson: function (tr, fieldData, fieldName, rowIndex) {\n if (!this._fieldIsVisible(fieldName)) { return; }\n\n var processor =\n this._options.processJSONField[fieldName] || // per-field callback\n this._options.processJSONField; // generic callback\n\n var result;\n if (typeof processor === 'function') {\n result = processor(fieldData, fieldName, rowIndex);\n } else {\n result = fieldData;\n }\n var elm = this._elOrFieldData(result);\n\n var className = this._options.tdClassNames[fieldName];\n if (className) {\n Css.addClassName(elm, className);\n }\n\n tr.appendChild(elm);\n },\n\n _elOrFieldData: function (processed) {\n if (Common.isDOMElement(processed)) {\n return processed;\n }\n\n var isString = typeof processed === 'string';\n var isNumber = typeof processed === 'number';\n var elm = Element.create('td');\n\n if (isString && /^\\s*?\n *\n * By applying this UI class to the above input, you get a tag field with the tags \"initial\" and \"value\". The class preserves the original input element. It remains hidden and is updated with new tag information dynamically, so regular HTML form logic still applies.\n *\n * Below \"input\" refers to the current value of the input tag (updated as the user enters text, of course), and \"output\" refers to the value which this class writes back to said input tag.\n *\n * @class Ink.UI.TagField\n * @version 1\n * @constructor\n * @param {String|DOMElement} element Selector or DOM Input Element.\n * @param {Object} [options] Options object\n * @param {String|Array} [options.tags] Initial tags in the input\n * @param {Boolean} [options.allowRepeated] Flag to allow user to input several tags. Defaults to true.\n * @param {RegExp} [options.separator] Split the input by this RegExp. Defaults to /[,;(space)]+/g (spaces, commas and semicolons)\n * @param {String} [options.outSeparator] Use this string to separate each tag from the next in the output. Defaults to ','.\n * @param {Boolean} [options.autoSplit] Flag to activate tag creation when the user types a separator. Defaults to true.\n * @param {Integer} [options.maxTags] Maximum number of tags allowed. Set to -1 for no limit. Defaults to -1.\n * @example\n */\n function TagField(element, options) {\n this.init(element, options);\n }\n\n TagField.prototype = {\n /**\n * Init function called by the constructor\n * \n * @method _init\n * @private\n */\n init: function(element, options) {\n element = this._element = Common.elOrSelector(element, 'Ink.UI.TagField');\n var o = this._options = Common.options('Ink.UI.TagField', {\n tags: ['String', []],\n tagQuery: ['Object', null],\n tagQueryAsync: ['Object', null],\n allowRepeated: ['Boolean', false],\n maxTags: ['Integer', -1],\n outSeparator: ['String', ','],\n separator: ['String', /[,; ]+/g],\n autoSplit: ['Boolean', true]\n }, options || {}, this._element);\n\n if (typeof o.separator === 'string') {\n o.separator = new RegExp(o.separator, 'g');\n }\n\n if (typeof o.tags === 'string') {\n // coerce to array using the separator\n o.tags = this._readInput(o.tags);\n }\n\n Css.addClassName(this._element, 'hide-all');\n\n this._viewElm = InkElement.create('div', {\n className: 'ink-tagfield',\n insertAfter: this._element\n });\n\n this._input = InkElement.create('input', {\n type: 'text',\n className: 'new-tag-input',\n insertBottom: this._viewElm\n });\n\n var tags = [].concat(o.tags, this._tagsFromMarkup(this._element));\n\n this._tags = [];\n\n InkArray.each(tags, Ink.bindMethod(this, '_addTag'));\n\n InkEvent.observe(this._input, 'keyup', Ink.bindEvent(this._onKeyUp, this));\n InkEvent.observe(this._input, 'change', Ink.bindEvent(this._onKeyUp, this));\n InkEvent.observe(this._input, 'keydown', Ink.bindEvent(this._onKeyDown, this));\n InkEvent.observe(this._input, 'blur', Ink.bindEvent(this._onBlur, this));\n InkEvent.observe(this._viewElm, 'click', Ink.bindEvent(this._refocus, this));\n\n Common.registerInstance(this, this._element);\n },\n\n destroy: function () {\n InkElement.remove(this._viewElm);\n Css.removeClassName(this._element, 'hide-all');\n },\n\n _tagsFromMarkup: function (element) {\n var tagname = element.tagName.toLowerCase();\n if (tagname === 'input') {\n return this._readInput(element.value);\n } else if (tagname === 'select') {\n return InkArray.map(element.getElementsByTagName('option'), function (option) {\n return InkElement.textContent(option);\n });\n } else {\n throw new Error('Cannot read tags from a ' + tagname + ' tag. Unknown tag');\n }\n },\n\n _tagsToMarkup: function (tags, element) {\n var tagname = element.tagName.toLowerCase();\n if (tagname === 'input') {\n if (this._options.separator) {\n element.value = tags.join(this._options.outSeparator);\n }\n } else if (tagname === 'select') {\n element.innerHTML = '';\n InkArray.each(tags, function (tag) {\n var opt = InkElement.create('option', {selected: 'selected'});\n InkElement.setTextContent(opt, tag);\n element.appendChild(opt);\n });\n } else {\n throw new Error('TagField: Cannot read tags from a ' + tagname + ' tag. Unknown tag');\n }\n },\n\n _addTag: function (tag) {\n if (this._options.maxTags !== -1 &&\n this._tags.length >= this._options.maxTags) {\n return;\n }\n if ((!this._options.allowRepeated &&\n InkArray.inArray(tag, this._tags, tag)) || !tag) {\n return false;\n }\n var elm = InkElement.create('span', {\n className: 'ink-tag',\n setTextContent: tag + ' '\n });\n\n var remove = InkElement.create('span', {\n className: 'remove fa fa-times',\n insertBottom: elm\n });\n InkEvent.observe(remove, 'click', Ink.bindEvent(this._removeTag, this, null));\n\n var spc = document.createTextNode(' ');\n\n this._tags.push(tag);\n this._viewElm.insertBefore(elm, this._input);\n this._viewElm.insertBefore(spc, this._input);\n this._tagsToMarkup(this._tags, this._element);\n },\n\n _readInput: function (text) {\n if (this._options.separator) {\n return InkArray.filter(text.split(this._options.separator), isTruthy);\n } else {\n return [text];\n }\n },\n\n _onKeyUp: function () { // TODO control input box size\n if (!this._options.autoSplit) {\n return;\n }\n var split = this._input.value.split(this._options.separator);\n if (split.length <= 1) {\n return;\n }\n var last = split[split.length - 1];\n split = split.splice(0, split.length - 1);\n split = InkArray.filter(split, isTruthy);\n \n InkArray.each(split, Ink.bind(this._addTag, this));\n this._input.value = last;\n },\n\n _onKeyDown: function (event) {\n if (event.which === enterKey) {\n return this._onEnterKeyDown(event);\n } else if (event.which === backspaceKey) {\n return this._onBackspaceKeyDown();\n } else if (this._removeConfirm) {\n // user pressed another key, cancel removal from a backspace key\n this._unsetRemovingVisual(this._tags.length - 1);\n }\n },\n\n /**\n * When the user presses backspace twice on the empty input, we delete the last tag on the field.\n * @method onBackspaceKeyDown\n * @private\n */\n _onBackspaceKeyDown: function () {\n if (this._input.value) { return; }\n\n if (this._removeConfirm) {\n this._unsetRemovingVisual(this._tags.length - 1);\n this._removeTag(this._tags.length - 1);\n this._removeConfirm = null;\n } else {\n this._setRemovingVisual(this._tags.length - 1);\n }\n },\n\n _onEnterKeyDown: function (event) {\n var tag = this._input.value;\n if (tag) {\n this._addTag(tag);\n this._input.value = '';\n }\n InkEvent.stopDefault(event);\n },\n\n _onBlur: function () {\n this._addTag(this._input.value);\n this._input.value = '';\n },\n\n /* For when the user presses backspace.\n * Set the style of the tag so that it seems like it's going to be removed\n * if they press backspace again. */\n _setRemovingVisual: function (tagIndex) {\n var elm = this._viewElm.children[tagIndex];\n Css.addClassName(elm, 'tag-deleting');\n\n this._removeRemovingVisualTimeout = setTimeout(Ink.bindMethod(this, '_unsetRemovingVisual', tagIndex), 4000);\n InkEvent.observe(this._input, 'blur', Ink.bindMethod(this, '_unsetRemovingVisual', tagIndex));\n this._removeConfirm = true;\n },\n _unsetRemovingVisual: function (tagIndex) {\n var elm = this._viewElm.children[tagIndex];\n if (elm) {\n Css.removeClassName(elm, 'tag-deleting');\n clearTimeout(this._removeRemovingVisualTimeout);\n }\n this._removeConfirm = null;\n },\n\n _removeTag: function (event) {\n var index;\n if (typeof event === 'object') { // click event on close button\n var elm = InkEvent.element(event).parentNode;\n index = InkElement.parentIndexOf(this._viewElm, elm);\n } else if (typeof event === 'number') { // manual removal\n index = event;\n }\n this._tags = InkArray.remove(this._tags, index, 1);\n InkElement.remove(this._viewElm.children[index]);\n this._tagsToMarkup(this._tags, this._element);\n },\n\n _refocus: function (event) {\n this._input.focus();\n InkEvent.stop(event);\n return false;\n }\n };\n return TagField;\n});\n","/**\n * Toggle the visibility of elements.\n * @module Ink.UI.Toggle_1\n * @version 1\n */\n\n Ink.createModule('Ink.UI.Toggle', '1', ['Ink.UI.Common_1','Ink.Dom.Event_1','Ink.Dom.Css_1','Ink.Dom.Element_1','Ink.Dom.Selector_1','Ink.Util.Array_1'], function(Common, InkEvent, Css, InkElement, Selector, InkArray ) {\n 'use strict';\n\n /**\n *\n * You need two elements to use Toggle: the `trigger` element, and the `target` element (or elements). The default behaviour is to toggle the `target`(s) when you click the `trigger`.\n *\n * The toggle has a state. It is either \"on\" or \"off\". It works by switching between the CSS classes in `classNameOn` and `classNameOff` according to the current state.\n *\n * When you initialize the Toggle, it will check if the targets are visible to figure out what the initial state is. You can force the toggle to consider itself turned \"on\" or \"off\" by setting the `initialState` option to `true` or `false`, respectively.\n *\n * You can get the current state of the Toggle by calling `getState`, or by checking if your `trigger` element has the \"active\" class.\n * The state can be changed through JavaScript. Just call `setState(true)` \n * to turn the Toggle on (or `setState(false)` to turn it off).\n *\n * @class Ink.UI.Toggle\n * @constructor\n * @version 1\n * @param {String|DOMElement} selector Trigger element. By clicking this, the target (or targets) are triggered.\n * @param {Object} [options] Options object, containing:\n *\n * @param {String} options.target CSS Selector that specifies the elements that this component will toggle\n * @param {String} [options.classNameOn] CSS class to toggle when on. Defaults to 'show-all'.\n * @param {String} [options.classNameOff] CSS class to toggle when off. Defaults to 'hide-all'.\n * @param {String} [options.triggerEvent] Event that will trigger the toggling. Defaults to 'click'.\n * @param {Boolean} [options.closeOnClick] Flag to toggle the targe off when clicking outside the toggled content. Defaults to true.\n * @param {String} [options.closeOnInsideClick] Toggle off when an element matching this selector is clicked. Set to null to deactivate the check. Defaults to 'a[href]'.\n * @param {Boolean} [options.initialState] Flag to define initial state. false: off, true: on, null: markup. Defaults to null.\n * @param {Function} [options.onChangeState] Callback when the toggle state changes. Return `false` to cancel the event.\n *\n * @sample Ink_UI_Toggle_1_constructor.html\n */\n var Toggle = function( selector, options ){\n this._rootElement = Common.elOrSelector(selector, '[Ink.UI.Toggle root element]:');\n\n this._options = Ink.extendObj({\n target : undefined,\n triggerEvent: 'click',\n closeOnClick: true,\n isAccordion: false,\n initialState: null,\n classNameOn: 'show-all',\n classNameOff: 'hide-all',\n togglesDisplay: null,\n closeOnInsideClick: 'a[href]', // closes the toggle when a target is clicked and it is a link\n onChangeState: null\n }, options || {}, InkElement.data(this._rootElement));\n\n this._targets = Common.elsOrSelector(this._options.target, 'Ink.UI.Toggle target option', true);\n\n // Boolean option handling\n this._options.closeOnClick = this._options.closeOnClick.toString() === 'true';\n // Actually a throolean\n if (this._options.initialState !== null){\n this._options.initialState = this._options.initialState.toString() === 'true';\n } else {\n this._options.initialState = Css.getStyle(this._targets[0], 'display') !== 'none';\n }\n\n if (this._options.classNameOn !== 'show-all' || this._options.classNameOff !== 'hide-all') {\n for (var i = 0, len = this._targets.length; i < len; i++) {\n Css.removeClassName(this._targets[i], 'show-all');\n Css.removeClassName(this._targets[i], 'hide-all');\n }\n }\n\n this._init();\n\n Common.registerInstance(this, this._rootElement);\n };\n\n Toggle.prototype = {\n\n /**\n * Init function called by the constructor\n * \n * @method _init\n * @private\n */\n _init: function(){\n this._accordion = ( Css.hasClassName(this._rootElement.parentNode,'accordion') || Css.hasClassName(this._targets[0].parentNode,'accordion') );\n\n this._firstTime = true;\n\n this._bindEvents();\n\n if (this._options.initialState !== null) {\n this.setState(this._options.initialState, true);\n } else {\n // Add initial classes matching the current \"display\" of the object.\n var state = Css.getStyle(this._targets[0], 'display') !== 'none';\n this.setState(state, true);\n }\n // Aditionally, remove any inline \"display\" style.\n for (var i = 0, len = this._targets.length; i < len; i++) {\n if (this._targets[i].style.display) {\n this._targets[i].style.display = ''; // becomes default\n }\n }\n\n this._rootElement.setAttribute('data-is-toggle-trigger', 'true');\n },\n\n /**\n * @method _bindEvents\n * @private\n */\n _bindEvents: function () {\n if ( this._options.triggerEvent ) {\n InkEvent.observe(\n this._rootElement,\n this._options.triggerEvent,\n Ink.bind(this._onTriggerEvent, this));\n }\n if( this._options.closeOnClick ){\n InkEvent.observe( document, 'click', Ink.bind(this._onOutsideClick, this));\n }\n if( this._options.closeOnInsideClick && this._options.closeOnInsideClick !== 'false') {\n var sel = this._options.closeOnInsideClick;\n if (sel.toString() === 'true') {\n sel = '*';\n }\n InkEvent.observeMulti(this._targets, 'click', Ink.bind(function (e) {\n if ( InkElement.findUpwardsBySelector(InkEvent.element(e), sel) ) {\n this.setState(false, true);\n }\n }, this));\n }\n },\n\n /**\n * Event handler. It's responsible for handling the `triggerEvent` as defined in the options.\n *\n * This will trigger the toggle.\n * \n * @method _onTriggerEvent\n * @param {Event} event\n * @private\n */\n _onTriggerEvent: function( event ){\n // When the togglee is a child of the toggler, we get the togglee's events here. We have to check that this event is for us.\n var target = InkEvent.element(event);\n\n var isAncestorOfClickedElement = InkArray.some(this._targets, function (thisOne) {\n return thisOne === target || InkElement.isAncestorOf(thisOne, target);\n });\n\n if (isAncestorOfClickedElement) {\n return;\n }\n\n if (this._accordion) {\n this._updateAccordion();\n }\n\n var has = this.getState();\n this.setState(!has, true);\n if (!has && this._firstTime) {\n this._firstTime = false;\n }\n\n InkEvent.stopDefault(event);\n },\n\n /**\n * Be compatible with accordions\n *\n * @method _updateAccordion\n **/\n _updateAccordion: function () {\n var elms, accordionElement;\n if( Css.hasClassName(this._targets[0].parentNode,'accordion') ){\n accordionElement = this._targets[0].parentNode;\n } else {\n accordionElement = this._targets[0].parentNode.parentNode;\n }\n elms = Selector.select('.toggle, .ink-toggle',accordionElement);\n for(var i=0; i 0) && (targetElm[0] !== this._targets[0]) ){\n targetElm[0].style.display = 'none';\n }\n }\n },\n\n /**\n * Click handler. Will handle clicks outside the toggle component.\n * \n * @method _onOutsideClick\n * @param {Event} event\n * @private\n */\n _onOutsideClick: function( event ){\n var tgtEl = InkEvent.element(event),\n shades;\n\n if (InkElement.findUpwardsBySelector(tgtEl, '[data-is-toggle-trigger=\"true\"]')) return;\n\n var ancestorOfTargets = InkArray.some(this._targets, function (target) {\n return InkElement.isAncestorOf(target, tgtEl) || target === tgtEl;\n });\n\n if( (this._rootElement === tgtEl) || InkElement.isAncestorOf(this._rootElement, tgtEl) || ancestorOfTargets) {\n return;\n } else if( (shades = Ink.ss('.ink-shade')).length ) {\n var shadesLength = shades.length;\n\n for( var i = 0; i < shadesLength; i++ ){\n if( InkElement.isAncestorOf(shades[i],tgtEl) && InkElement.isAncestorOf(shades[i],this._rootElement) ){\n return;\n }\n }\n }\n\n this.setState(false, true); // dismiss\n },\n\n /**\n * Sets the state of the toggle. (on/off)\n *\n * @method setState\n * @param newState {Boolean} New state (on/off)\n */\n setState: function (on, callHandler) {\n if (on === this.getState()) { return; }\n if (callHandler && typeof this._options.onChangeState === 'function') {\n var ret = this._options.onChangeState(on);\n if (ret === false) { return false; } // Canceled by the event handler\n }\n for (var i = 0, len = this._targets.length; i < len; i++) {\n Css.addRemoveClassName(this._targets[i], this._options.classNameOn, on);\n Css.addRemoveClassName(this._targets[i], this._options.classNameOff, !on);\n }\n Css.addRemoveClassName(this._rootElement, 'active', on);\n },\n\n /**\n * Gets the state of the toggle. (on/off)\n *\n * @method getState\n *\n * @return {Boolean} whether the toggle is toggled on.\n */\n getState: function () {\n return Css.hasClassName(this._rootElement, 'active');\n }\n };\n\n return Toggle;\n});\n","/**\n * Content Tooltips\n * @module Ink.UI.Tooltip_1\n * @version 1\n */\nInk.createModule('Ink.UI.Tooltip', '1', ['Ink.UI.Common_1', 'Ink.Dom.Event_1', 'Ink.Dom.Element_1', 'Ink.Dom.Selector_1', 'Ink.Util.Array_1', 'Ink.Dom.Css_1', 'Ink.Dom.Browser_1'], function (Common, InkEvent, InkElement, Selector, InkArray, Css) {\n 'use strict';\n\n /**\n * Tooltips are useful as a means to display information about functionality while avoiding clutter.\n *\n * Tooltips show up when you hover elements which \"have\" tooltips.\n *\n * This class will \"give\" a tooltip to many elements, selected by its first argument (`target`). This is contrary to the other UI modules in Ink, which are created once per element.\n *\n * You can define options either through the second argument of the Tooltip constructor, or as data-attributes in each `target` element. Options set through data-attributes all start with \"data-tip\", and override options passed into the Tooltip constructor.\n *\n * @class Ink.UI.Tooltip\n * @constructor\n *\n * @param {DOMElement|String} target Target element or selector of elements, to display the tooltips on.\n * @param {Object} [options] Options object\n * @param {String} [options.text] Text content for the tooltip.\n * @param {String} [options.html] HTML for the tooltip. Same as above, but won't escape HTML.\n * @param {String} [options.where] Positioning for the tooltip. Options are 'up', 'down', 'left', 'right', 'mousemove' (follows the cursor), and 'mousefix' (stays fixed). Defaults to 'up'.\n * \n * @param {String} [options.color] Color of the tooltip. Options are red, orange, blue, green and black. Default is white.\n * @param {Number} [options.fade] Number of seconds to fade in/out. Defaults to 0.3.\n * @param {Boolean} [options.forever] Flag to prevent the tooltip from being erased when the mouse hovers away from the target.\n * @param {Number} [options.timeout] Number of seconds the tooltip will stay open. Useful together with options.forever. Defaults to 0.\n * @param {Number} [options.delay] Time the tooltip waits until it is displayed. Useful to avoid getting the attention of the user unnecessarily\n * @param {DOMElement|Selector} [options.template] Element or selector containing HTML to be cloned into the tooltips. Can be a hidden element, because CSS `display` is set to `block`.\n * @param {String} [options.templatefield] Selector within the template element to choose where the text is inserted into the tooltip. Useful when a wrapper DIV is required.\n * @param {Number} [options.left] Spacing from the target to the tooltip, when `where` is `mousemove` or `mousefix`. Defaults to 10.\n * @param {Number} [options.top] Spacing from the target to the tooltip, when `where` is `mousemove` or `mousefix`. Defaults to 10.\n * @param {Number} [options.spacing] Spacing between the tooltip and the target element, when `where` is not `mousemove` or `mousefix`. Defaults to 8.\n * \n * @sample Ink_UI_Tooltip_1.html\n */\n function Tooltip(element, options) {\n this._init(element, options || {});\n }\n\n function EachTooltip(root, elm) {\n this._init(root, elm);\n }\n\n var transitionDurationName,\n transitionPropertyName,\n transitionTimingFunctionName;\n (function () { // Feature detection\n var test = document.createElement('DIV');\n var names = ['transition', 'oTransition', 'msTransition', 'mozTransition',\n 'webkitTransition'];\n for (var i = 0; i < names.length; i++) {\n if (typeof test.style[names[i] + 'Duration'] !== 'undefined') {\n transitionDurationName = names[i] + 'Duration';\n transitionPropertyName = names[i] + 'Property';\n transitionTimingFunctionName = names[i] + 'TimingFunction';\n break;\n }\n }\n }());\n\n // Body or documentElement\n var bodies = document.getElementsByTagName('body');\n var body = bodies.length ? bodies[0] : document.documentElement;\n\n Tooltip.prototype = {\n _init: function(element, options) {\n var elements;\n\n this.options = Ink.extendObj({\n where: 'up',\n zIndex: 10000,\n left: 10,\n top: 10,\n spacing: 8,\n forever: 0,\n color: '',\n timeout: 0,\n delay: 0,\n template: null,\n templatefield: null,\n fade: 0.3,\n text: ''\n }, options || {});\n\n if (typeof element === 'string') {\n elements = Selector.select(element);\n } else if (typeof element === 'object') {\n elements = [element];\n } else {\n throw 'Element expected';\n }\n\n this.tooltips = [];\n\n for (var i = 0, len = elements.length; i < len; i++) {\n this.tooltips[i] = new EachTooltip(this, elements[i]);\n }\n },\n /**\n * Destroys the tooltips created by this instance\n *\n * @method destroy\n */\n destroy: function () {\n InkArray.each(this.tooltips, function (tooltip) {\n tooltip._destroy();\n });\n this.tooltips = null;\n this.options = null;\n }\n };\n\n EachTooltip.prototype = {\n _oppositeDirections: {\n left: 'right',\n right: 'left',\n up: 'down',\n down: 'up'\n },\n _init: function(root, elm) {\n InkEvent.observe(elm, 'mouseover', Ink.bindEvent(this._onMouseOver, this));\n InkEvent.observe(elm, 'mouseout', Ink.bindEvent(this._onMouseOut, this));\n InkEvent.observe(elm, 'mousemove', Ink.bindEvent(this._onMouseMove, this));\n\n this.root = root;\n this.element = elm;\n this._delayTimeout = null;\n this.tooltip = null;\n\n Common.registerInstance(this, this.element);\n },\n _makeTooltip: function (mousePosition) {\n if (!this._getOpt('text') &&\n !this._getOpt('html') &&\n !InkElement.hasAttribute(this.element, 'title')) {\n return false;\n }\n\n var tooltip = this._createTooltipElement();\n\n if (this.tooltip) {\n this._removeTooltip();\n }\n\n this.tooltip = tooltip;\n\n this._fadeInTooltipElement(tooltip);\n this._placeTooltipElement(tooltip, mousePosition);\n\n InkEvent.observe(tooltip, 'mouseover', Ink.bindEvent(this._onTooltipMouseOver, this));\n\n var timeout = this._getFloatOpt('timeout');\n if (timeout) {\n setTimeout(Ink.bind(function () {\n if (this.tooltip === tooltip) {\n this._removeTooltip();\n }\n }, this), timeout * 1000);\n }\n },\n _createTooltipElement: function () {\n var template = this._getOpt('template'), // User template instead of our HTML\n templatefield = this._getOpt('templatefield'),\n \n tooltip, // The element we float\n field; // Element where we write our message. Child or same as the above\n\n if (template) { // The user told us of a template to use. We copy it.\n var temp = document.createElement('DIV');\n temp.innerHTML = Common.elOrSelector(template, 'options.template').outerHTML;\n tooltip = temp.firstChild;\n \n if (templatefield) {\n field = Selector.select(templatefield, tooltip);\n if (field) {\n field = field[0];\n } else {\n throw 'options.templatefield must be a valid selector within options.template';\n }\n } else {\n field = tooltip; // Assume same element if user did not specify a field\n }\n } else { // We create the default structure\n tooltip = document.createElement('DIV');\n Css.addClassName(tooltip, 'ink-tooltip');\n Css.addClassName(tooltip, this._getOpt('color'));\n\n field = document.createElement('DIV');\n Css.addClassName(field, 'content');\n\n tooltip.appendChild(field);\n }\n \n if (this._getOpt('html')) {\n field.innerHTML = this._getOpt('html');\n } else if (this._getOpt('text')) {\n InkElement.setTextContent(field, this._getOpt('text'));\n } else {\n InkElement.setTextContent(field, this.element.getAttribute('title'));\n }\n tooltip.style.display = 'block';\n tooltip.style.position = 'absolute';\n tooltip.style.zIndex = this._getIntOpt('zIndex');\n\n return tooltip;\n },\n _fadeInTooltipElement: function (tooltip) {\n var fadeTime = this._getFloatOpt('fade');\n if (transitionDurationName && fadeTime) {\n tooltip.style.opacity = '0';\n tooltip.style[transitionDurationName] = fadeTime + 's';\n tooltip.style[transitionPropertyName] = 'opacity';\n tooltip.style[transitionTimingFunctionName] = 'ease-in-out';\n setTimeout(function () {\n tooltip.style.opacity = '1';\n }, 0); // Wait a tick\n }\n },\n _placeTooltipElement: function (tooltip, mousePosition) {\n var where = this._getOpt('where');\n\n if (where === 'mousemove' || where === 'mousefix') {\n var mPos = mousePosition;\n this._setPos(mPos[0], mPos[1]);\n body.appendChild(tooltip);\n } else if (where.match(/(up|down|left|right)/)) {\n body.appendChild(tooltip);\n var targetElementPos = InkElement.offset(this.element);\n var tleft = targetElementPos[0],\n ttop = targetElementPos[1];\n\n var centerh = (InkElement.elementWidth(this.element) / 2) - (InkElement.elementWidth(tooltip) / 2),\n centerv = (InkElement.elementHeight(this.element) / 2) - (InkElement.elementHeight(tooltip) / 2);\n var spacing = this._getIntOpt('spacing');\n\n var tooltipDims = InkElement.elementDimensions(tooltip);\n var elementDims = InkElement.elementDimensions(this.element);\n\n var maxX = InkElement.scrollWidth() + InkElement.viewportWidth();\n var maxY = InkElement.scrollHeight() + InkElement.viewportHeight();\n \n where = this._getWhereValueInsideViewport(where, {\n left: tleft - tooltipDims[0],\n right: tleft + tooltipDims[0],\n top: ttop + tooltipDims[1],\n bottom: ttop + tooltipDims[1]\n }, {\n right: maxX,\n bottom: maxY\n });\n \n if (where === 'up') {\n ttop -= tooltipDims[1];\n ttop -= spacing;\n tleft += centerh;\n } else if (where === 'down') {\n ttop += elementDims[1];\n ttop += spacing;\n tleft += centerh;\n } else if (where === 'left') {\n tleft -= tooltipDims[0];\n tleft -= spacing;\n ttop += centerv;\n } else if (where === 'right') {\n tleft += elementDims[0];\n tleft += spacing;\n ttop += centerv;\n }\n \n var arrow = null;\n if (where.match(/(up|down|left|right)/)) {\n arrow = document.createElement('SPAN');\n Css.addClassName(arrow, 'arrow');\n Css.addClassName(arrow, this._oppositeDirections[where]);\n tooltip.appendChild(arrow);\n }\n\n var tooltipLeft = tleft;\n var tooltipTop = ttop;\n\n var toBottom = (tooltipTop + tooltipDims[1]) - maxY;\n var toRight = (tooltipLeft + tooltipDims[0]) - maxX;\n var toLeft = 0 - tooltipLeft;\n var toTop = 0 - tooltipTop;\n\n if (toBottom > 0) {\n if (arrow) { arrow.style.top = (tooltipDims[1] / 2) + toBottom + 'px'; }\n tooltipTop -= toBottom;\n } else if (toTop > 0) {\n if (arrow) { arrow.style.top = (tooltipDims[1] / 2) - toTop + 'px'; }\n tooltipTop += toTop;\n } else if (toRight > 0) {\n if (arrow) { arrow.style.left = (tooltipDims[0] / 2) + toRight + 'px'; }\n tooltipLeft -= toRight;\n } else if (toLeft > 0) {\n if (arrow) { arrow.style.left = (tooltipDims[0] / 2) - toLeft + 'px'; }\n tooltipLeft += toLeft;\n }\n\n tooltip.style.left = tooltipLeft + 'px';\n tooltip.style.top = tooltipTop + 'px';\n }\n },\n\n /**\n * Get a value for \"where\" (left/right/up/down) which doesn't put the\n * tooltip off the screen\n *\n * @method _getWhereValueInsideViewport\n * @param where {String} \"where\" value which was given by the user and we might change\n * @param bbox {BoundingBox} A bounding box like what you get from getBoundingClientRect ({top, bottom, left, right}) with pixel positions from the top left corner of the viewport.\n * @param viewport {BoundingBox} Bounding box for the viewport. \"top\" and \"left\" are omitted because these coordinates are relative to the top-left corner of the viewport so they are zero.\n *\n * @TODO: we can't use getBoundingClientRect in this case because it returns {0,0,0,0} on our uncreated tooltip.\n */\n _getWhereValueInsideViewport: function (where, bbox, viewport) {\n if (where === 'left' && bbox.left < 0) {\n return 'right';\n } else if (where === 'right' && bbox.right > viewport.right) {\n return 'left';\n } else if (where === 'up' && bbox.top < 0) {\n return 'down';\n } else if (where === 'down' && bbox.bottom > viewport.bottom) {\n return 'up';\n }\n\n return where;\n },\n _removeTooltip: function() {\n var tooltip = this.tooltip;\n if (!tooltip) {return;}\n\n var remove = Ink.bind(InkElement.remove, {}, tooltip);\n\n if (this._getOpt('where') !== 'mousemove' && transitionDurationName) {\n tooltip.style.opacity = 0;\n // remove() will operate on correct tooltip, although this.tooltip === null then\n setTimeout(remove, this._getFloatOpt('fade') * 1000);\n } else {\n remove();\n }\n this.tooltip = null;\n },\n _getOpt: function (option) {\n var dataAttrVal = InkElement.data(this.element)[InkElement._camelCase('tip-' + option)];\n if (dataAttrVal /* either null or \"\" may signify the absense of this attribute*/) {\n return dataAttrVal;\n }\n var instanceOption = this.root.options[option];\n if (typeof instanceOption !== 'undefined') {\n return instanceOption;\n }\n },\n _getIntOpt: function (option) {\n return parseInt(this._getOpt(option), 10);\n },\n _getFloatOpt: function (option) {\n return parseFloat(this._getOpt(option), 10);\n },\n _destroy: function () {\n if (this.tooltip) {\n InkElement.remove(this.tooltip);\n }\n this.root = null; // Cyclic reference = memory leaks\n this.element = null;\n this.tooltip = null;\n },\n _onMouseOver: function(e) {\n // on IE < 10 you can't access the mouse event not even a tick after it fired\n var mousePosition = this._getMousePosition(e);\n var delay = this._getFloatOpt('delay');\n if (delay) {\n this._delayTimeout = setTimeout(Ink.bind(function () {\n if (!this.tooltip) {\n this._makeTooltip(mousePosition);\n }\n this._delayTimeout = null;\n }, this), delay * 1000);\n } else {\n this._makeTooltip(mousePosition);\n }\n },\n _onMouseMove: function(e) {\n if (this._getOpt('where') === 'mousemove' && this.tooltip) {\n var mPos = this._getMousePosition(e);\n this._setPos(mPos[0], mPos[1]);\n }\n },\n _onMouseOut: function () {\n if (!this._getIntOpt('forever')) {\n this._removeTooltip();\n }\n if (this._delayTimeout) {\n clearTimeout(this._delayTimeout);\n this._delayTimeout = null;\n }\n },\n _onTooltipMouseOver: function () {\n if (this.tooltip) { // If tooltip is already being removed, this has no effect\n this._removeTooltip();\n }\n },\n _setPos: function(left, top) {\n left += this._getIntOpt('left');\n top += this._getIntOpt('top');\n var pageDims = this._getPageXY();\n if (this.tooltip) {\n var elmDims = [InkElement.elementWidth(this.tooltip), InkElement.elementHeight(this.tooltip)];\n var scrollDim = this._getScroll();\n\n if((elmDims[0] + left - scrollDim[0]) >= (pageDims[0] - 20)) {\n left = (left - elmDims[0] - this._getIntOpt('left') - 10);\n }\n if((elmDims[1] + top - scrollDim[1]) >= (pageDims[1] - 20)) {\n top = (top - elmDims[1] - this._getIntOpt('top') - 10);\n }\n\n this.tooltip.style.left = left + 'px';\n this.tooltip.style.top = top + 'px';\n }\n },\n _getPageXY: function() {\n var cWidth = 0;\n var cHeight = 0;\n if( typeof( window.innerWidth ) === 'number' ) {\n cWidth = window.innerWidth;\n cHeight = window.innerHeight;\n } else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {\n cWidth = document.documentElement.clientWidth;\n cHeight = document.documentElement.clientHeight;\n } else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {\n cWidth = document.body.clientWidth;\n cHeight = document.body.clientHeight;\n }\n return [parseInt(cWidth, 10), parseInt(cHeight, 10)];\n },\n _getScroll: function() {\n var dd = document.documentElement, db = document.body;\n if (dd && (dd.scrollLeft || dd.scrollTop)) {\n return [dd.scrollLeft, dd.scrollTop];\n } else if (db) {\n return [db.scrollLeft, db.scrollTop];\n } else {\n return [0, 0];\n }\n },\n _getMousePosition: function(e) {\n return [parseInt(InkEvent.pointerX(e), 10), parseInt(InkEvent.pointerY(e), 10)];\n }\n };\n\n return Tooltip;\n});\n","/**\n * Elements in a tree structure\n * @module Ink.UI.TreeView_1\n * @version 1\n */\nInk.createModule('Ink.UI.TreeView', '1', ['Ink.UI.Common_1','Ink.Dom.Event_1','Ink.Dom.Css_1','Ink.Dom.Element_1','Ink.Dom.Selector_1','Ink.Util.Array_1'], function(Common, Event, Css, Element, Selector, InkArray ) {\n 'use strict';\n\n\n /**\n * Shows elements in a tree structure which can be expanded and contracted.\n * A TreeView is built with \"node\"s and \"children\". \"node\"s are `li` tags, and \"children\" are `ul` tags.\n * You can build your TreeView out of a regular UL and LI element structure which you already use to display lists with several levels.\n * If you want a node to be open when the TreeView is built, just add the data-open=\"true\" attribute to it.\n * \n * @class Ink.UI.TreeView\n * @constructor\n * @version 1\n * @param {String|DOMElement} selector Element or selector.\n * @param {String} [options] Options object, containing:\n * @param {String} [options.node] Selector for the nodes. Defaults to 'li'.\n * @param {String} [options.children] Selector for the children. Defaults to 'ul'.\n * @param {String} [options.parentClass] CSS classes to be added to parent nodes. Defaults to 'parent'.\n * @param {String} [options.openClass] CSS classes to be added to the icon when a parent is open. Defaults to 'fa fa-minus-circle'.\n * @param {String} [options.closedClass] CSS classes to be added to the icon when a parent is closed. Defaults to 'fa fa-plus-circle'.\n * @param {String} [options.hideClass] CSS Class to toggle visibility of the children. Defaults to 'hide-all'.\n * @param {String} [options.iconTag] The name of icon tag. The component tries to find a tag with that name as a direct child of the node. If it doesn't find it, it creates it. Defaults to 'i'.\n * @param {Boolean} [options.stopDefault] Flag to stops the default behavior of the click handler. Defaults to true.\n * @example\n * \n * \n * \n * @sample Ink_UI_TreeView_1.html\n */\n var TreeView = function(selector, options){\n this._element = Common.elOrSelector(selector, '[Ink.UI.TreeView_1]');\n\n this._options = Common.options('Treeview', {\n 'node': ['String', 'li'],\n // [3.0.1] Deprecate this terrible, terrible name\n 'child': ['String',null],\n 'children': ['String','ul'],\n 'parentClass': ['String','parent'],\n 'openNodeClass': ['String', 'open'],\n 'openClass': ['String','fa fa-minus-circle'],\n 'closedClass': ['String','fa fa-plus-circle'],\n 'hideClass': ['String','hide-all'],\n 'iconTag': ['String', 'i'],\n 'stopDefault' : ['Boolean', true]\n }, options || {}, this._element);\n\n if (this._options.child) {\n Ink.warn('Ink.UI.TreeView: options.child is being renamed to options.children.');\n this._options.children = this._options.child;\n }\n\n this._init();\n };\n\n TreeView.prototype = {\n\n /**\n * Init function called by the constructor. Sets the necessary event handlers.\n * \n * @method _init\n * @private\n */\n _init: function(){\n this._handlers = {\n click: Ink.bindEvent(this._onClick,this)\n };\n\n Event.on(this._element, 'click', this._options.node, this._handlers.click);\n\n InkArray.each(Ink.ss(this._options.node, this._element), Ink.bind(function(item){\n if( this.isParent(item) ) {\n Css.addClassName(item, this._options.parentClass);\n\n var isOpen = this.isOpen(item);\n if( !this._getIcon(item) ){\n Element.create(this._options.iconTag, { insertTop: item });\n }\n\n this._setNodeOpen(item, isOpen);\n }\n },this));\n\n Common.registerInstance(this, this._element);\n },\n\n _getIcon: function (node) {\n return Ink.s('> ' + this._options.iconTag, node);\n },\n\n /**\n * Checks if a node is open.\n *\n * @method isOpen\n * @param {DOMElement} node The tree node to check\n **/\n isOpen: function (node) {\n if (!this._getChild(node)) {\n throw new Error('not a node!');\n }\n\n return Element.data(node).open === 'true' ||\n Css.hasClassName(node, this._options.openNodeClass);\n },\n\n /**\n * Checks if a node is a parent.\n *\n * @method isParent\n * @param {DOMElement} node Node to check\n **/\n isParent: function (node) {\n return Css.hasClassName(node, this._options.parentClass) ||\n this._getChild(node) != null;\n },\n\n _setNodeOpen: function (node, beOpen) {\n var child = this._getChild(node);\n if (child) {\n Css.setClassName(child, this._options.hideClass, !beOpen);\n var icon = this._getIcon(node);\n\n node.setAttribute('data-open', beOpen);\n\n /*\n * Don't refactor this to\n *\n * setClassName(el, className, status); setClassName(el, className, !status);\n *\n * because it won't work with multiple classes.\n *\n * Doing:\n * setClassName(el, 'fa fa-whatever', true);setClassName(el, 'fa fa-whatever-else', false);\n *\n * will remove 'fa' although it is a class we want.\n */\n\n var toAdd = beOpen ? this._options.openClass : this._options.closedClass;\n var toRemove = beOpen ? this._options.closedClass : this._options.openClass;\n Css.removeClassName(icon, toRemove);\n Css.addClassName(icon, toAdd);\n\n Css.setClassName(node, this._options.openNodeClass, beOpen);\n } else {\n Ink.error('Ink.UI.TreeView: node', node, 'is not a node!');\n }\n },\n\n /**\n * Opens one of the tree nodes\n *\n * Make sure you pass the node's DOMElement\n * @method open\n * @param {DOMElement} node The node you wish to open.\n **/\n open: function (node) {\n this._setNodeOpen(node, true);\n },\n\n /**\n * Closes one of the tree nodes\n *\n * Make sure you pass the node's DOMElement\n * @method close\n * @param {DOMElement} node The node you wish to close.\n **/\n close: function (node) {\n this._setNodeOpen(node, false);\n },\n\n /**\n * Toggles a node state\n *\n * @method toggle\n * @param {DOMElement} node The node to toggle.\n **/\n toggle: function (node) {\n if (this.isOpen(node)) {\n this.close(node);\n } else {\n this.open(node);\n }\n },\n\n _getChild: function (node) {\n return Selector.select(this._options.children, node)[0] || null;\n },\n\n /**\n * Handles the click event (as specified in the _init function).\n * \n * @method _onClick\n * @param {Event} event\n * @private\n */\n _onClick: function(ev){\n /**\n * Summary:\n * If the clicked element is a \"node\" as defined in the options, will check if it has any \"child\".\n * If so, will toggle its state and stop the event's default behavior if the stopDefault option is true.\n **/\n\n if (!this.isParent(ev.currentTarget) ||\n Selector.matchesSelector(ev.target, this._options.node) ||\n Selector.matchesSelector(ev.target, this._options.children)) {\n return;\n }\n\n if (this._options.stopDefault){\n ev.preventDefault();\n }\n\n this.toggle(ev.currentTarget);\n }\n };\n\n return TreeView;\n});\n","Ink.createModule('Ink.UI.Upload', '1', [\n 'Ink.Dom.Event_1',\n 'Ink.Dom.Element_1',\n 'Ink.Dom.Browser_1',\n 'Ink.UI.Common_1'\n], function(Event, Element, Browser, Common) {\n 'use strict';\n\n var DirectoryReader = function(options) {\n this.init(options);\n };\n\n DirectoryReader.prototype = {\n init: function(options) {\n this._options = Ink.extendObj({\n entry: undefined,\n maxDepth: 10\n }, options || {});\n\n try {\n this._read();\n } catch(e) {\n Ink.error(e);\n }\n },\n\n\n _read: function() {\n if(!this._options.entry) {\n Ink.error('You must specify the entry!');\n return;\n }\n\n try {\n this._readDirectories();\n } catch(e) {\n Ink.error(e);\n }\n },\n\n\n _readDirectories: function() {\n var entries = [],\n running = false,\n maxDepth = 0;\n\n /* TODO return as tree because much better well */\n var _readEntries = Ink.bind(function(currentEntry) {\n var dir = currentEntry.createReader();\n running = true;\n\n dir.readEntries(Ink.bind(function(res) {\n if(res.length > 0) {\n for(var i = 0, len = res.length; i=0; i--) {\n if(typeof(arr[i]) === 'undefined' || arr[i] === null || arr[i] === '') {\n arr.splice(i, 1);\n }\n }\n return arr;\n }\n };\n\n var Queue = {\n lists: [],\n items: [],\n\n\n /**\n * Create new queue list\n * @function create\n * @public\n * @param {String} list name\n * @param {Function} function to iterate on items\n * @return {Object} list id\n */\n create: function(name) {\n var id;\n name = String(name);\n this.lists.push({name: name});\n id = this.lists.length - 1;\n return id;\n },\n\n\n getItems: function(parentId) {\n if(!parentId) {\n return this.items;\n }\n var items = [];\n for(var i = 0, len = this.items.length; i=0; i--) {\n if(this.items[i] && id === this.items[i].parentId) {\n this.remove(this.items[i].parentId, this.items[i].pid);\n }\n }\n if(!keepList) {\n this.lists.splice(id, 1);\n }\n return true;\n } catch(e) {\n Ink.error('Purge: invalid id');\n return false;\n }\n },\n\n\n /**\n * add an item to a list\n * @function add\n * @public\n * @param {String} name\n * @param {Object} item\n * @return {Number} pid\n */\n add: function(parentId, item, priority) {\n if(!this.lists[parentId]) {\n return false;\n }\n if(typeof(item) !== 'object') {\n item = String(item);\n }\n\n var pid = parseInt(Math.round(Math.random() * 100000) + \"\" + Math.round(Math.random() * 100000), 10);\n priority = priority || 0;\n\n this.items.push({parentId: parentId, item: item, priority: priority || 0, pid: pid});\n return pid;\n },\n\n\n /**\n * View list\n * @function view\n * @public\n * @param {Number} list id\n * @param {Number} process id\n * @return {Object} item\n */\n view: function(parentId, pid) {\n var id = this._searchByPid(parentId, pid);\n if(id === false) {\n return false;\n }\n return this.items[id];\n },\n\n\n /**\n * Remove an item\n * @function remove\n * @public\n * @param {Object} item\n * @return {Object|Boolean} removed item or false if not found\n */\n remove: function(parentId, pid) {\n try {\n var id = this._searchByPid(parentId, pid);\n if(id === false) {\n return false;\n }\n this.items.splice(id, 1);\n return true;\n } catch(e) {\n Ink.error('Remove: invalid id');\n return false;\n }\n },\n\n _searchByPid: function(parentId, pid) {\n if(!parentId && typeof(parentId) === 'boolean' || !pid) {\n return false;\n }\n\n parentId = parseInt(parentId, 10);\n pid = parseInt(pid, 10);\n\n if(isNaN(parentId) || isNaN(pid)) {\n return false;\n }\n\n for(var i = 0, len = this.items.length; i this._options.minSizeToUseChunks;\n },\n\n\n _dropEventHandler: function(ev) {\n Event.stop(ev);\n\n this.publish('DropComplete', ev.dataTransfer);\n\n var data = ev.dataTransfer;\n\n if(!data || !data.files || !data.files.length) {\n return false;\n }\n\n this._files = data.files;\n this._files = Array.prototype.slice.call(this._files || [], 0);\n\n // check if webkitGetAsEntry exists on first item\n if(data.items && data.items[0] && data.items[0].webkitGetAsEntry) {\n if(!this._options.foldersEnabled) {\n return setTimeout(Ink.bind(this._addFilesToQueue, this, this._files), 0);\n }\n var entry, folders = [];\n for(var i = ev.dataTransfer.items.length-1; i>=0; i--) {\n entry = ev.dataTransfer.items[i].webkitGetAsEntry();\n if(entry && entry.isDirectory) {\n folders.push(entry);\n this._files[i].isDirectory = true;\n this._files.splice(i, 1);\n }\n }\n // starting callback hell\n this._addFolderToQueue(folders, Ink.bind(function() {\n setTimeout(Ink.bind(this._addFilesToQueue, this, this._files), 0);\n }, this));\n } else {\n setTimeout(Ink.bind(this._addFilesToQueue, this, this._files), 0);\n }\n\n return true;\n },\n\n\n _addFolderToQueue: function(folders, cb) {\n var files = [], invalidFolders = {};\n\n if(!folders || !folders.length) {\n cb();\n return files;\n }\n\n var getFiles = function(entries) {\n var files = [];\n for(var i = 0, len = entries.length; i this._options.maxFilesize) {\n this.publish('MaxSizeFailure', file, this._options.maxFilesize);\n continue;\n }\n\n fileID = parseInt(Math.round(Math.random() * 100000) + \"\" + Math.round(Math.random() * 100000), 10);\n o = { id: i, data: file, fileID: fileID, directory: file.isDirectory };\n Queue.add(this._queueId, o);\n\n this.publish('FileAddedToQueue', o);\n }\n this._processQueue(true);\n this._files = [];\n },\n\n\n _processQueue: function(internalUpload) {\n if(this._queueRunning) {\n return false;\n }\n\n this.running = 0;\n var max = 1, i = 0, items,\n queueLen = Queue.items.length;\n this._queueRunning = true;\n\n this.interval = setInterval(Ink.bind(function() {\n if(Queue.items.length === i && this.running === 0) {\n Queue.purge(this._queueId, true);\n this._queueRunning = false;\n clearInterval(this.interval);\n this.publish('QueueEnd', this._queueId, queueLen);\n }\n\n items = Queue.getItems(this._queueId);\n\n if(this.running < max && items[i]) {\n if(!items[i].canceled) {\n _doRequest.call(this, items[i].pid, items[i].item.data, items[i].item.fileID, items[i].item.directory, internalUpload);\n this.running++;\n i++;\n } else {\n var j = i;\n while(items[j] && items[j].canceled) {\n i++;\n j++;\n }\n }\n return true;\n }\n return false;\n }, this), 100);\n\n\n var _doRequest = function(pid, data, fileID, directory, internalUpload) {\n var o = {\n file: data,\n fileID: fileID,\n cb: Ink.bind(function() {\n this.running--;\n }, this)\n };\n if(internalUpload) {\n if(directory) {\n // do magic\n o.cb();\n } else {\n this._upload(o);\n }\n }\n };\n\n return true;\n },\n\n\n _upload: function(o) {\n var file = o.file,\n xhr = new XMLHttpRequest(),\n fileID = o.fileID;\n\n this.publish('BeforeUpload', file, this._options.extraData, fileID, xhr, this._supportChunks(file.size));\n\n var forceAbort = function(showError) {\n if(o.cb && typeof(o.cb === 'function')) {\n o.cb();\n }\n\n this.publish('OnProgress', {\n length: file.size,\n lengthComputable: true,\n loaded: file.size,\n total: file.size\n }, file, fileID);\n this.publish('EndUpload', file, fileID, (showError ? { error: true } : true));\n this.publish('InvalidFile', file, 'name');\n xhr.abort();\n };\n\n if(this._options.INVALID_FILE_NAME && this._options.INVALID_FILE_NAME instanceof RegExp) {\n if(this._options.INVALID_FILE_NAME.test(o.file.name)) {\n forceAbort.call(this);\n return;\n }\n }\n\n // If file was renamed, abort it\n // FU OPERA: Opera always return lastModified date as null\n if(!file.lastModifiedDate && !Ink.Dom.Browser.OPERA) {\n forceAbort.call(this, true);\n return;\n }\n\n xhr.upload.onprogress = Ink.bind(this.publish, this, 'OnProgress', file, fileID);\n\n var endpoint, method;\n if(this._supportChunks(file.size)) {\n if(file.size <= file.chunk_offset) {\n endpoint = this._options.endpointChunkCommit;\n method = 'POST';\n } else {\n endpoint = this._options.endpointChunk;\n if(file.chunk_upload_id) {\n endpoint += '?upload_id=' + file.chunk_upload_id;\n }\n if(file.chunk_offset) {\n endpoint += '&offset=' + file.chunk_offset;\n }\n method = 'PUT';\n }\n } else {\n endpoint = this._options.endpoint;\n method = 'POST';\n }\n\n xhr.open(method, endpoint, true);\n xhr.withCredentials = true;\n xhr.setRequestHeader(\"x-requested-with\", \"XMLHttpRequest\");\n if(this._supportChunks(file.size)) {\n xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');\n }\n\n var fd = new FormData(),\n blob;\n\n if(\"Blob\" in window && typeof Blob === 'function') {\n blob = new Blob([file], { type: file.type });\n if(this._supportChunks(file.size)) {\n file.chunk_offset = file.chunk_offset || 0;\n blob = blob.slice(file.chunk_offset, file.chunk_offset + this._options.chunkSize);\n } else {\n fd.append(this._options.fileFormName, blob, file.name);\n }\n } else {\n fd.append(this._options.fileFormName, file);\n }\n\n if(!this._supportChunks(file.size)) {\n for(var k in this._options.extraData) {\n if(this._options.extraData.hasOwnProperty(k)) {\n fd.append(k, this._options.extraData[k]);\n }\n }\n } else {\n fd.append('upload_id', file.chunk_upload_id);\n fd.append('path', file.upload_path);\n }\n\n if(!file.hasParent) {\n if(!this._supportChunks(file.size)) {\n xhr.send(fd);\n } else {\n if(file.size <= file.chunk_offset) {\n xhr.send('upload_id=' + file.chunk_upload_id + '&path=' + file.upload_path + '/' + file.name);\n } else {\n xhr.send(blob);\n }\n }\n } else {\n this.publish('cbCreateFolder', file.parentID, file.fullPath, this._options.extraData, this._folders, file.rootPath, Ink.bind(function() {\n if(!this._supportChunks(file.size)) {\n xhr.send(fd);\n } else {\n if(file.size <= file.chunk_offset) {\n xhr.send('upload_id=' + file.chunk_upload_id + '&path=' + file.upload_path + '/' + file.name);\n } else {\n xhr.send(blob);\n }\n }\n }, this));\n }\n\n\n xhr.onload = Ink.bindEvent(function() {\n /* jshint boss:true */\n if(this._supportChunks(file.size) && file.size > file.chunk_offset) {\n if(xhr.response) {\n var response = JSON.parse(xhr.response);\n\n // check expected offset\n var invalidOffset = file.chunk_offset && response.offset !== (file.chunk_offset + this._options.chunkSize) && file.size !== response.offset;\n if(invalidOffset) {\n if(o.cb) {\n o.cb();\n }\n this.publish('ErrorUpload', file, fileID);\n } else {\n file.chunk_upload_id = response.upload_id;\n file.chunk_offset = response.offset;\n file.chunk_expires = response.expires;\n this._upload(o);\n }\n } else {\n if(o.cb) {\n o.cb();\n }\n this.publish('ErrorUpload', file, fileID);\n }\n return (xhr = null);\n }\n\n if(o.cb) {\n o.cb();\n }\n\n if(xhr.responseText && xhr['status'] < 400) {\n this.publish('EndUpload', file, fileID, xhr.responseText);\n } else {\n this.publish('ErrorUpload', file, fileID);\n }\n return (xhr = null);\n }, this);\n\n\n xhr.onerror = Ink.bindEvent(function() {\n if(o.cb) {\n o.cb();\n }\n this.publish('ErrorUpload', file, fileID);\n }, this);\n\n xhr.onabort = Ink.bindEvent(function() {\n if(o.cb) {\n o.cb();\n }\n this.publish('AbortUpload', file, fileID, {\n abortAll: Ink.bind(this.abortAll, this),\n abortOne: Ink.bind(this.abortOne, this)\n });\n }, this);\n },\n\n\n abortAll: function() {\n if(!this._queueRunning) {\n return false;\n }\n clearInterval(this.interval);\n this._queueRunning = false;\n Queue.purge(this._queueId, true);\n return true;\n },\n\n abortOne: function(id, cb) {\n var items = Queue.getItems(0),\n o;\n for(var i = 0, len = items.length; i [ [1, 1], [2, 2], [3], [1] ]\n * InkArray.groupBy([1.1, 1.2, 2.1], { key: Math.floor }) // -> [ [1.1, 1.2], [2.1] ]\n * InkArray.groupBy([1.1, 1.2, 2.1], { key: Math.floor, pairs: true }) // -> [ [1, [1.1, 1.2]], [2, [2.1]] ]\n *\n **/\n groupBy: function (arr, options) {\n options = options || {};\n var ret = [];\n var latestGroup;\n function eq(a, b) {\n return outKey(a) === outKey(b);\n }\n function outKey(item) {\n if (typeof options.key === 'function') {\n return options.key(item);\n } else {\n return item;\n }\n }\n\n for (var i = 0, len = arr.length; i < len; i++) {\n latestGroup = [arr[i]];\n\n // Chunkin'\n while ((i + 1 < len) && eq(arr[i], arr[i + 1])) {\n latestGroup.push(arr[i + 1]);\n i++;\n }\n\n if (options.pairs) {\n ret.push([outKey(arr[i]), latestGroup]);\n } else {\n ret.push(latestGroup);\n }\n }\n return ret;\n },\n\n /**\n * Replacement for Array.prototype.reduce.\n *\n * Produces a single result from a list of values by calling an \"aggregator\" function.\n *\n * Falls back to Array.prototype.reduce if available.\n *\n * @method reduce\n * @param array {Array} Input array to be reduced.\n * @param callback {Function} `function (previousValue, currentValue, index, all) { return {Mixed} }` to execute for each value.\n * @param initial {Mixed} Object used as the first argument to the first call of `callback`\n *\n * @example\n * var sum = InkArray.reduce([1, 2, 3], function (a, b) { return a + b; }); // -> 6\n */\n reduce: function (array, callback, initial) {\n if (arrayProto.reduce) {\n return arrayProto.reduce.apply(array, [].slice.call(arguments, 1));\n }\n\n // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#Polyfill\n var t = Object( array ), len = t.length >>> 0, k = 0, value;\n if ( arguments.length >= 3 ) {\n value = initial;\n } else {\n while ( k < len && !(k in t) ) k++; \n if ( k >= len )\n throw new TypeError('Reduce of empty array with no initial value');\n value = t[ k++ ];\n }\n for ( ; k < len ; k++ ) {\n if ( k in t ) {\n value = callback( value, t[k], k, t );\n }\n }\n return value;\n },\n\n /**\n * Checks if a value exists in array\n *\n * @method inArray\n * @public\n * @static\n * @param {Mixed} value Value to check\n * @param {Array} arr Array to search in\n * @return {Boolean} True if value exists in the array\n * @sample Ink_Util_Array_inArray.html \n */\n inArray: function(value, arr) {\n if (typeof arr === 'object') {\n for (var i = 0, f = arr.length; i < f; ++i) {\n if (arr[i] === value) {\n return true;\n }\n }\n }\n return false;\n },\n\n /**\n * Sorts an array of objects by an object property\n *\n * @method sortMulti\n * @param {Array} arr Array of objects to sort\n * @param {String} key Property to sort by\n * @return {Array|Boolean} False if it's not an array, returns a sorted array if it's an array.\n * @public\n * @static\n * @sample Ink_Util_Array_sortMulti.html \n */\n sortMulti: function(arr, key) {\n if (typeof arr === 'undefined' || arr.constructor !== Array) { return false; }\n if (typeof key !== 'string') { return arr.sort(); }\n if (arr.length > 0) {\n if (typeof(arr[0][key]) === 'undefined') { return false; }\n arr.sort(function(a, b){\n var x = a[key];\n var y = b[key];\n return ((x < y) ? -1 : ((x > y) ? 1 : 0));\n });\n }\n return arr;\n },\n\n /**\n * Gets the indexes of a value in an array\n *\n * @method keyValue\n * @param {String} value Value to search for.\n * @param {Array} arr Array to run the search in.\n * @param {Boolean} [first] Flag to stop the search at the first match. It also returns an index number instead of an array of indexes.\n * @return {Boolean|Number|Array} False for no matches. Array of matches or first match index.\n * @public\n * @static\n * @sample Ink_Util_Array_keyValue.html \n */\n keyValue: function(value, arr, first) {\n if (typeof value !== 'undefined' && typeof arr === 'object' && this.inArray(value, arr)) {\n var aKeys = [];\n for (var i = 0, f = arr.length; i < f; ++i) {\n if (arr[i] === value) {\n if (typeof first !== 'undefined' && first === true) {\n return i;\n } else {\n aKeys.push(i);\n }\n }\n }\n return aKeys;\n }\n return false;\n },\n\n /**\n * Shuffles an array.\n *\n * @method shuffle\n * @param {Array} arr Array to shuffle\n * @return {Array|Boolean} Shuffled Array or false if not an array.\n * @public\n * @static\n * @sample Ink_Util_Array_shuffle.html \n */\n shuffle: function(arr) {\n if (typeof(arr) !== 'undefined' && arr.constructor !== Array) { return false; }\n var total = arr.length,\n tmp1 = false,\n rnd = false;\n\n while (total--) {\n rnd = Math.floor(Math.random() * (total + 1));\n tmp1 = arr[total];\n arr[total] = arr[rnd];\n arr[rnd] = tmp1;\n }\n return arr;\n },\n\n /**\n * Runs a function through each of the elements of an array\n *\n * @method forEach\n * @param {Array} arr The array to be cycled/iterated\n * @param {Function} cb The function receives as arguments the value, index and array.\n * @return {Array} Iterated array.\n * @public\n * @static\n * @sample Ink_Util_Array_forEach.html \n */\n forEach: function(array, callback, context) {\n if (arrayProto.forEach) {\n return arrayProto.forEach.call(array, callback, context);\n }\n for (var i = 0, len = array.length >>> 0; i < len; i++) {\n callback.call(context, array[i], i, array);\n }\n },\n\n /**\n * Alias for backwards compatibility. See forEach\n *\n * @method each\n */\n each: function () {\n InkArray.forEach.apply(InkArray, [].slice.call(arguments));\n },\n\n /**\n * Runs a function for each item in the array. \n * That function will receive each item as an argument and its return value will change the corresponding array item.\n * @method map\n * @param {Array} array The array to map over\n * @param {Function} map The map function. Will take `(item, index, array)` as arguments and `this` will be the `context` argument.\n * @param {Object} [context] Object to be `this` in the map function. \n *\n * @sample Ink_Util_Array_map.html \n */\n map: function (array, callback, context) {\n if (arrayProto.map) {\n return arrayProto.map.call(array, callback, context);\n }\n var mapped = new Array(len);\n for (var i = 0, len = array.length >>> 0; i < len; i++) {\n mapped[i] = callback.call(context, array[i], i, array);\n }\n return mapped;\n },\n\n /**\n * Filters an array based on a truth test.\n * This method runs a test function on all the array values and returns a new array with all the values that pass the test.\n * @method filter\n * @param {Array} array The array to filter\n * @param {Function} test A test function taking `(item, index, array)`\n * @param {Object} [context] Object to be `this` in the test function.\n * @return {Array} Returns the filtered array\n *\n * @sample Ink_Util_Array_filter.html \n */\n filter: function (array, test, context) {\n if (arrayProto.filter) {\n return arrayProto.filter.call(array, test, context);\n }\n var filtered = [],\n val = null;\n for (var i = 0, len = array.length; i < len; i++) {\n val = array[i]; // it might be mutated\n if (test.call(context, val, i, array)) {\n filtered.push(val);\n }\n }\n return filtered;\n },\n\n /**\n * Checks if some element in the array passes a truth test\n *\n * @method some\n * @param {Array} arr The array to iterate through\n * @param {Function} cb The callback to be called on the array's elements. It receives the value, the index and the array as arguments.\n * @param {Object} context Object of the callback function\n * @return {Boolean} True if the callback returns true at any point, false otherwise\n * @public\n * @static\n * @sample Ink_Util_Array_some.html \n */\n some: function(arr, cb, context){\n\n if (arr === null){\n throw new TypeError('First argument is invalid.');\n }\n\n var t = Object(arr);\n var len = t.length >>> 0;\n if (typeof cb !== \"function\"){ throw new TypeError('Second argument must be a function.'); }\n\n for (var i = 0; i < len; i++) {\n if (i in t && cb.call(context, t[i], i, t)){ return true; }\n }\n\n return false;\n },\n\n /**\n * Compares the values of two arrays and return the matches\n *\n * @method intersect\n * @param {Array} arr1 First array\n * @param {Array} arr2 Second array\n * @return {Array} Empty array if one of the arrays is false (or do not intersect) | Array with the intersected values\n * @public\n * @static\n * @sample Ink_Util_Array_intersect.html \n */\n intersect: function(arr1, arr2) {\n if (!arr1 || !arr2 || arr1 instanceof Array === false || arr2 instanceof Array === false) {\n return [];\n }\n\n var shared = [];\n for (var i = 0, I = arr1.length; i 0) {\n for (x = a; x < b; x += step) {\n r.push(x);\n }\n } else {\n for (x = a; x > b; x += step) {\n r.push(x);\n }\n }\n\n return r;\n },\n\n /**\n * Inserts a value on a specified index\n *\n * @method insert\n * @param {Array} arr Array where the value will be inserted\n * @param {Number} idx Index of the array where the value should be inserted\n * @param {Mixed} value Value to be inserted\n * @public\n * @static\n * @sample Ink_Util_Array_insert.html \n */\n insert: function(arr, idx, value) {\n arr.splice(idx, 0, value);\n },\n\n /**\n * Removes a range of values from the array\n *\n * @method remove\n * @param {Array} arr Array where the value will be removed\n * @param {Number} from Index of the array where the removal will start removing.\n * @param {Number} rLen Number of items to be removed from the index onwards.\n * @return {Array} An array with the remaining values\n * @public\n * @static\n * @sample Ink_Util_Array_remove.html \n */\n remove: function(arr, from, rLen){\n var output = [];\n\n for(var i = 0, iLen = arr.length; i < iLen; i++){\n if(i >= from && i < from + rLen){\n continue;\n }\n\n output.push(arr[i]);\n }\n\n return output;\n }\n };\n\n return InkArray;\n\n});\n","/**\n * Binary Packing algorithm implementation\n * @module Ink.Util.BinPack_1\n * @version 1\n */\n\nInk.createModule('Ink.Util.BinPack', '1', [], function() {\n\n 'use strict';\n\n /*jshint boss:true */\n\n // https://github.com/jakesgordon/bin-packing/\n\n /*\n Copyright (c) 2011, 2012, 2013 Jake Gordon and contributors\n\n Permission is hereby granted, free of charge, to any person obtaining a copy\n of this software and associated documentation files (the \"Software\"), to deal\n in the Software without restriction, including without limitation the rights\n to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n copies of the Software, and to permit persons to whom the Software is\n furnished to do so, subject to the following conditions:\n\n The above copyright notice and this permission notice shall be included in all\n copies or substantial portions of the Software.\n\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n SOFTWARE.\n */\n\n\n\n var Packer = function(w, h) {\n this.init(w, h);\n };\n\n Packer.prototype = {\n\n init: function(w, h) {\n this.root = { x: 0, y: 0, w: w, h: h };\n },\n\n fit: function(blocks) {\n var n, node, block;\n for (n = 0; n < blocks.length; ++n) {\n block = blocks[n];\n if (node = this.findNode(this.root, block.w, block.h)) {\n block.fit = this.splitNode(node, block.w, block.h);\n }\n }\n },\n\n findNode: function(root, w, h) {\n if (root.used) {\n return this.findNode(root.right, w, h) || this.findNode(root.down, w, h);\n }\n else if ((w <= root.w) && (h <= root.h)) {\n return root;\n }\n else {\n return null;\n }\n },\n\n splitNode: function(node, w, h) {\n node.used = true;\n node.down = { x: node.x, y: node.y + h, w: node.w, h: node.h - h };\n node.right = { x: node.x + w, y: node.y, w: node.w - w, h: h };\n return node;\n }\n\n };\n\n\n\n var GrowingPacker = function() {};\n\n GrowingPacker.prototype = {\n\n fit: function(blocks) {\n var n, node, block, len = blocks.length;\n var w = len > 0 ? blocks[0].w : 0;\n var h = len > 0 ? blocks[0].h : 0;\n this.root = { x: 0, y: 0, w: w, h: h };\n for (n = 0; n < len ; n++) {\n block = blocks[n];\n if (node = this.findNode(this.root, block.w, block.h)) {\n block.fit = this.splitNode(node, block.w, block.h);\n }\n else {\n block.fit = this.growNode(block.w, block.h);\n }\n }\n },\n\n findNode: function(root, w, h) {\n if (root.used) {\n return this.findNode(root.right, w, h) || this.findNode(root.down, w, h);\n }\n else if ((w <= root.w) && (h <= root.h)) {\n return root;\n }\n else {\n return null;\n }\n },\n\n splitNode: function(node, w, h) {\n node.used = true;\n node.down = { x: node.x, y: node.y + h, w: node.w, h: node.h - h };\n node.right = { x: node.x + w, y: node.y, w: node.w - w, h: h };\n return node;\n },\n\n growNode: function(w, h) {\n var canGrowDown = (w <= this.root.w);\n var canGrowRight = (h <= this.root.h);\n\n var shouldGrowRight = canGrowRight && (this.root.h >= (this.root.w + w)); // attempt to keep square-ish by growing right when height is much greater than width\n var shouldGrowDown = canGrowDown && (this.root.w >= (this.root.h + h)); // attempt to keep square-ish by growing down when width is much greater than height\n\n if (shouldGrowRight) {\n return this.growRight(w, h);\n }\n else if (shouldGrowDown) {\n return this.growDown(w, h);\n }\n else if (canGrowRight) {\n return this.growRight(w, h);\n }\n else if (canGrowDown) {\n return this.growDown(w, h);\n }\n else {\n return null; // need to ensure sensible root starting size to avoid this happening\n }\n },\n\n growRight: function(w, h) {\n this.root = {\n used: true,\n x: 0,\n y: 0,\n w: this.root.w + w,\n h: this.root.h,\n down: this.root,\n right: { x: this.root.w, y: 0, w: w, h: this.root.h }\n };\n var node;\n if (node = this.findNode(this.root, w, h)) {\n return this.splitNode(node, w, h);\n }\n else {\n return null;\n }\n },\n\n growDown: function(w, h) {\n this.root = {\n used: true,\n x: 0,\n y: 0,\n w: this.root.w,\n h: this.root.h + h,\n down: { x: 0, y: this.root.h, w: this.root.w, h: h },\n right: this.root\n };\n var node;\n if (node = this.findNode(this.root, w, h)) {\n return this.splitNode(node, w, h);\n }\n else {\n return null;\n }\n }\n\n };\n\n\n\n var sorts = {\n random: function() { return Math.random() - 0.5; },\n w: function(a, b) { return b.w - a.w; },\n h: function(a, b) { return b.h - a.h; },\n a: function(a, b) { return b.area - a.area; },\n max: function(a, b) { return Math.max(b.w, b.h) - Math.max(a.w, a.h); },\n min: function(a, b) { return Math.min(b.w, b.h) - Math.min(a.w, a.h); },\n height: function(a, b) { return sorts.msort(a, b, ['h', 'w']); },\n width: function(a, b) { return sorts.msort(a, b, ['w', 'h']); },\n area: function(a, b) { return sorts.msort(a, b, ['a', 'h', 'w']); },\n maxside: function(a, b) { return sorts.msort(a, b, ['max', 'min', 'h', 'w']); },\n msort: function(a, b, criteria) { /* sort by multiple criteria */\n var diff, n;\n for (n = 0; n < criteria.length; ++n) {\n diff = sorts[ criteria[n] ](a, b);\n if (diff !== 0) {\n return diff;\n }\n }\n return 0;\n }\n };\n\n\n\n // end of Jake's code\n\n\n\n // aux, used to display blocks in unfitted property\n var toString = function() {\n return [this.w, ' x ', this.h].join('');\n };\n\n\n\n /**\n * Binary Packing algorithm implementation\n *\n * Based on the work of Jake Gordon\n *\n * see https://github.com/jakesgordon/bin-packing/\n *\n * @namespace Ink.Util.BinPack\n * @version 1\n * @static\n */\n var BinPack = {\n\n /**\n * @method binPack\n * @param {Object} o Options\n * @param {Array} o.blocks Array of items with width and height integer attributes.\n * @param {Array} [o.dimensions] Flag to fix container dimensions\n * @param {String} [o.sorter] Sorting function. One of: random, height, width, area, maxside\n * @return {Object} Returns an object containing container dimensions, filled ratio, fitted blocks, unfitted blocks and all blocks\n * @static\n */\n binPack: function(o) {\n var i, f, bl;\n\n\n\n // calculate area if not there already\n for (i = 0, f = o.blocks.length; i < f; ++i) {\n bl = o.blocks[i];\n if (! ('area' in bl) ) {\n bl.area = bl.w * bl.h;\n }\n }\n\n\n\n // apply algorithm\n var packer = o.dimensions ? new Packer(o.dimensions[0], o.dimensions[1]) : new GrowingPacker();\n\n if (!o.sorter) { o.sorter = 'maxside'; }\n\n o.blocks.sort( sorts[ o.sorter ] );\n\n packer.fit(o.blocks);\n\n var dims2 = [packer.root.w, packer.root.h];\n\n\n\n // layout is done here, generating report data...\n var fitted = [];\n var unfitted = [];\n\n for (i = 0, f = o.blocks.length; i < f; ++i) {\n bl = o.blocks[i];\n if (bl.fit) {\n fitted.push(bl);\n }\n else {\n bl.toString = toString; // TO AID SERIALIZATION\n unfitted.push(bl);\n }\n }\n\n var area = dims2[0] * dims2[1];\n var fit = 0;\n for (i = 0, f = fitted.length; i < f; ++i) {\n bl = fitted[i];\n fit += bl.area;\n }\n\n return {\n dimensions: dims2,\n filled: fit / area,\n blocks: o.blocks,\n fitted: fitted,\n unfitted: unfitted\n };\n }\n };\n\n\n\n return BinPack;\n\n});","/**\n * Cookie Utilities\n * @module Ink.Util.Cookie_1\n * @version 1\n */\n\nInk.createModule('Ink.Util.Cookie', '1', [], function() {\n\n 'use strict';\n\n /**\n * @namespace Ink.Util.Cookie_1\n */\n var Cookie = {\n\n /**\n * Gets an object with the current page cookies.\n *\n * @method get\n * @param {String} name The cookie name.\n * @return {String|Object} If the name is specified, it returns the value of that key. Otherwise it returns the full cookie object\n * @public\n * @static\n * @sample Ink_Util_Cookie_get.html\n */\n get: function(name)\n {\n var cookie = document.cookie || false;\n\n var _Cookie = {};\n if(cookie) {\n cookie = cookie.replace(new RegExp(\"; \", \"g\"), ';');\n var aCookie = cookie.split(';');\n var aItem = [];\n if(aCookie.length > 0) {\n for(var i=0; i < aCookie.length; i++) {\n aItem = aCookie[i].split('=');\n if(aItem.length === 2) {\n _Cookie[aItem[0]] = decodeURIComponent(aItem[1]);\n }\n aItem = [];\n }\n }\n }\n if(name) {\n if(typeof(_Cookie[name]) !== 'undefined') {\n return _Cookie[name];\n } else {\n return null;\n }\n }\n return _Cookie;\n },\n\n /**\n * Sets a cookie.\n *\n * @method set\n * @param {String} name Cookie name.\n * @param {String} value Cookie value.\n * @param {Number} [expires] Number of seconds the cookie will be valid for.\n * @param {String} [path] Path for the cookie. Defaults to '/'.\n * @param {String} [domain] Domain for the cookie. Defaults to current hostname.\n * @param {Boolean} [secure] Flag for secure. Default 'false'.\n * @public\n * @static\n * @sample Ink_Util_Cookie_set.html\n */\n set: function(name, value, expires, path, domain, secure)\n {\n var sName;\n if(!name || value===false || typeof(name) === 'undefined' || typeof(value) === 'undefined') {\n return false;\n } else {\n sName = name+'='+encodeURIComponent(value);\n }\n var sExpires = false;\n var sPath = false;\n var sDomain = false;\n var sSecure = false;\n\n if(expires && typeof(expires) !== 'undefined' && !isNaN(expires)) {\n var oDate = new Date();\n var sDate = (parseInt(Number(oDate.valueOf()), 10) + (Number(parseInt(expires, 10)) * 1000));\n\n var nDate = new Date(sDate);\n var expiresString = nDate.toGMTString();\n\n var re = new RegExp(\"([^\\\\s]+)(\\\\s\\\\d\\\\d)\\\\s(\\\\w\\\\w\\\\w)\\\\s(.*)\");\n expiresString = expiresString.replace(re, \"$1$2-$3-$4\");\n\n sExpires = 'expires='+expiresString;\n } else {\n if(typeof(expires) !== 'undefined' && !isNaN(expires) && Number(parseInt(expires, 10))===0) {\n sExpires = '';\n } else {\n sExpires = 'expires=Thu, 01-Jan-2037 00:00:01 GMT';\n }\n }\n\n if(path && typeof(path) !== 'undefined') {\n sPath = 'path='+path;\n } else {\n sPath = 'path=/';\n }\n\n if(domain && typeof(domain) !== 'undefined') {\n sDomain = 'domain='+domain;\n } else {\n var portClean = new RegExp(\":(.*)\");\n sDomain = 'domain='+window.location.host;\n sDomain = sDomain.replace(portClean,\"\");\n }\n\n if(secure && typeof(secure) !== 'undefined') {\n sSecure = secure;\n } else {\n sSecure = false;\n }\n\n document.cookie = sName+'; '+sExpires+'; '+sPath+'; '+sDomain+'; '+sSecure;\n },\n\n /**\n * Deletes a cookie.\n *\n * @method remove\n * @param {String} cookieName Cookie name.\n * @param {String} [path] Path of the cookie. Defaults to '/'.\n * @param {String} [domain] Domain of the cookie. Defaults to current hostname.\n * @public\n * @static\n * @sample Ink_Util_Cookie_remove.html\n */\n remove: function(cookieName, path, domain)\n {\n //var expiresDate = 'Thu, 01-Jan-1970 00:00:01 GMT';\n var sPath = false;\n var sDomain = false;\n var expiresDate = -999999999;\n\n if(path && typeof(path) !== 'undefined') {\n sPath = path;\n } else {\n sPath = '/';\n }\n\n if(domain && typeof(domain) !== 'undefined') {\n sDomain = domain;\n } else {\n sDomain = window.location.host;\n }\n\n this.set(cookieName, 'deleted', expiresDate, sPath, sDomain);\n }\n };\n\n return Cookie;\n\n});\n","/**\n * Date utility functions\n * @module Ink.Util.Date_1\n * @version 1\n */\n\nInk.createModule('Ink.Util.Date', '1', [], function() {\n\n 'use strict';\n\n /**\n * @namespace Ink.Util.Date_1 \n */\n var InkDate = {\n\n /**\n * Function that returns the string representation of the month [PT only]\n *\n * @method _months\n * @param {Number} index Month javascript (0 to 11)\n * @return {String} The month's name\n * @private\n * @static\n * @example\n * console.log( InkDate._months(0) ); // Result: Janeiro\n */\n _months: function(index){\n var _m = ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'];\n return _m[index];\n },\n\n /**\n * Function that returns the month [PT only] ( 0 to 11 )\n *\n * @method _iMonth\n * @param {String} month Month javascript (0 to 11)\n * @return {Number} The month's number\n * @private\n * @static\n * @example\n * console.log( InkDate._iMonth('maio') ); // Result: 4\n */\n _iMonth : function( month )\n {\n if ( Number( month ) ) { return +month - 1; }\n return {\n 'janeiro' : 0 ,\n 'jan' : 0 ,\n 'fevereiro' : 1 ,\n 'fev' : 1 ,\n 'março' : 2 ,\n 'mar' : 2 ,\n 'abril' : 3 ,\n 'abr' : 3 ,\n 'maio' : 4 ,\n 'mai' : 4 ,\n 'junho' : 5 ,\n 'jun' : 5 ,\n 'julho' : 6 ,\n 'jul' : 6 ,\n 'agosto' : 7 ,\n 'ago' : 7 ,\n 'setembro' : 8 ,\n 'set' : 8 ,\n 'outubro' : 9 ,\n 'out' : 9 ,\n 'novembro' : 10 ,\n 'nov' : 10 ,\n 'dezembro' : 11 ,\n 'dez' : 11\n }[ month.toLowerCase( ) ];\n } ,\n\n /**\n * Function that returns the representation the day of the week [PT Only]\n *\n * @method _wDays\n * @param {Number} index Week's day index\n * @return {String} The week's day name\n * @private\n * @static\n * @example\n * console.log( InkDate._wDays(0) ); // Result: Domingo\n */\n _wDays: function(index){\n var _d = ['Domingo', 'Segunda', 'Terça', 'Quarta', 'Quinta', 'Sexta', 'Sábado'];\n return _d[index];\n },\n\n /**\n * Function that returns day of the week in javascript 1 to 7\n *\n * @method _iWeek\n * @param {String} week Week's day name\n * @return {Number} The week's day index\n * @private\n * @static\n * @example\n * console.log( InkDate._iWeek('quarta') ); // Result: 3\n */\n _iWeek: function( week )\n {\n if ( Number( week ) ) { return +week || 7; }\n return {\n 'segunda' : 1 ,\n 'seg' : 1 ,\n 'terça' : 2 ,\n 'ter' : 2 ,\n 'quarta' : 3 ,\n 'qua' : 3 ,\n 'quinta' : 4 ,\n 'qui' : 4 ,\n 'sexta' : 5 ,\n 'sex' : 5 ,\n 'sábado' : 6 ,\n 'sáb' : 6 ,\n 'domingo' : 7 ,\n 'dom' : 7\n }[ week.toLowerCase( ) ];\n },\n\n /**\n * Function that returns the number of days of a given month (m) on a given year (y)\n *\n * @method _daysInMonth\n * @param {Number} _m Month\n * @param {Number} _y Year\n * @return {Number} Number of days of a give month on a given year\n * @private\n * @static\n * @example\n * console.log( InkDate._daysInMonth(2,2013) ); // Result: 28\n */\n _daysInMonth: function(_m,_y){\n var nDays;\n\n if(_m===1 || _m===3 || _m===5 || _m===7 || _m===8 || _m===10 || _m===12)\n {\n nDays= 31;\n }\n else if ( _m===4 || _m===6 || _m===9 || _m===11)\n {\n nDays = 30;\n }\n else\n {\n if((_y%400===0) || (_y%4===0 && _y%100!==0))\n {\n nDays = 29;\n }\n else\n {\n nDays = 28;\n }\n }\n return nDays;\n },\n\n /**\n * Formats a date object.\n * This works exactly as php date() function. http://php.net/manual/en/function.date.php\n *\n * @method get\n * @param {String} format The format in which the date it will be formatted.\n * @param {Date} [_date] The date to format. Can receive unix timestamp or a date object. Defaults to current time.\n * @return {String} Formatted date\n * @public\n * @static\n * @sample Ink_Util_Date_get.html \n */\n get: function(format, _date){\n /*jshint maxcomplexity:65 */\n if(typeof(format) === 'undefined' || format === ''){\n format = \"Y-m-d\";\n }\n\n\n var iFormat = format.split(\"\");\n var result = new Array(iFormat.length);\n var escapeChar = \"\\\\\";\n var jsDate;\n\n if (typeof(_date) === 'undefined'){\n jsDate = new Date();\n } else if (typeof(_date)==='number'){\n jsDate = new Date(_date*1000);\n } else {\n jsDate = new Date(_date);\n }\n\n var jsFirstDay, jsThisDay, jsHour;\n /* This switch is presented in the same order as in php date function (PHP 5.2.2) */\n for (var i = 0; i < iFormat.length; i++) {\n switch(iFormat[i]) {\n case escapeChar:\n result[i] = iFormat[i+1];\n i++;\n break;\n\n\n /* DAY */\n case \"d\": /* Day of the month, 2 digits with leading zeros; ex: 01 to 31 */\n var jsDay = jsDate.getDate();\n result[i] = (String(jsDay).length > 1) ? jsDay : \"0\" + jsDay;\n break;\n\n case \"D\": /* A textual representation of a day, three letters; Seg to Dom */\n result[i] = this._wDays(jsDate.getDay()).substring(0, 3);\n break;\n\n case \"j\": /* Day of the month without leading zeros; ex: 1 to 31 */\n result[i] = jsDate.getDate();\n break;\n\n case \"l\": /* A full textual representation of the day of the week; Domingo to Sabado */\n result[i] = this._wDays(jsDate.getDay());\n break;\n\n case \"N\": /* ISO-8601 numeric representation of the day of the week; 1 (Segunda) to 7 (Domingo) */\n result[i] = jsDate.getDay() || 7;\n break;\n\n case \"S\": /* English ordinal suffix for the day of the month, 2 characters; st, nd, rd or th. Works well with j */\n var temp = jsDate.getDate();\n var suffixes = [\"st\", \"nd\", \"rd\"];\n var suffix = \"\";\n\n if (temp >= 11 && temp <= 13) {\n result[i] = \"th\";\n } else {\n result[i] = (suffix = suffixes[String(temp).substr(-1) - 1]) ? (suffix) : (\"th\");\n }\n break;\n\n case \"w\": /* Numeric representation of the day of the week; 0 (for Sunday) through 6 (for Saturday) */\n result[i] = jsDate.getDay();\n break;\n\n case \"z\": /* The day of the year (starting from 0); 0 to 365 */\n jsFirstDay = Date.UTC(jsDate.getFullYear(), 0, 0);\n jsThisDay = Date.UTC(jsDate.getFullYear(), jsDate.getMonth(), jsDate.getDate());\n result[i] = Math.floor((jsThisDay - jsFirstDay) / (1000 * 60 * 60 * 24));\n break;\n\n /* WEEK */\n case \"W\": /* ISO-8601 week number of year, weeks starting on Monday; ex: 42 (the 42nd week in the year) */\n var jsYearStart = new Date( jsDate.getFullYear( ) , 0 , 1 );\n jsFirstDay = jsYearStart.getDay() || 7;\n\n var days = Math.floor( ( jsDate - jsYearStart ) / ( 24 * 60 * 60 * 1000 ) + 1 );\n\n result[ i ] = Math.ceil( ( days - ( 8 - jsFirstDay ) ) / 7 ) + 1;\n break;\n\n\n /* MONTH */\n case \"F\": /* A full textual representation of a month, such as Janeiro or Marco; Janeiro a Dezembro */\n result[i] = this._months(jsDate.getMonth());\n break;\n\n case \"m\": /* Numeric representation of a month, with leading zeros; 01 to 12 */\n var jsMonth = String(jsDate.getMonth() + 1);\n result[i] = (jsMonth.length > 1) ? jsMonth : \"0\" + jsMonth;\n break;\n\n case \"M\": /* A short textual representation of a month, three letters; Jan a Dez */\n result[i] = this._months(jsDate.getMonth()).substring(0,3);\n break;\n\n case \"n\": /* Numeric representation of a month, without leading zeros; 1 a 12 */\n result[i] = jsDate.getMonth() + 1;\n break;\n\n case \"t\": /* Number of days in the given month; ex: 28 */\n result[i] = this._daysInMonth(jsDate.getMonth()+1,jsDate.getYear());\n break;\n\n /* YEAR */\n case \"L\": /* Whether it's a leap year; 1 if it is a leap year, 0 otherwise. */\n var jsYear = jsDate.getFullYear();\n result[i] = (jsYear % 4) ? false : ( (jsYear % 100) ? true : ( (jsYear % 400) ? false : true ) );\n break;\n\n case \"o\": /* ISO-8601 year number. This has the same value as Y, except that if the ISO week number (W) belongs to the previous or next year, that year is used instead. */\n throw '\"o\" not implemented!';\n\n case \"Y\": /* A full numeric representation of a year, 4 digits; 1999 */\n result[i] = jsDate.getFullYear();\n break;\n\n case \"y\": /* A two digit representation of a year; 99 */\n result[i] = String(jsDate.getFullYear()).substring(2);\n break;\n\n /* TIME */\n case \"a\": /* Lowercase Ante meridiem and Post meridiem; am or pm */\n result[i] = (jsDate.getHours() < 12) ? \"am\" : \"pm\";\n break;\n\n case \"A\": /* Uppercase Ante meridiem and Post meridiem; AM or PM */\n result[i] = (jsDate.getHours < 12) ? \"AM\" : \"PM\";\n break;\n\n case \"B\": /* Swatch Internet time; 000 through 999 */\n throw '\"B\" not implemented!';\n\n case \"g\": /* 12-hour format of an hour without leading zeros; 1 to 12 */\n jsHour = jsDate.getHours();\n result[i] = (jsHour <= 12) ? jsHour : (jsHour - 12);\n break;\n\n case \"G\": /* 24-hour format of an hour without leading zeros; 1 to 23 */\n result[i] = String(jsDate.getHours());\n break;\n\n case \"h\": /* 12-hour format of an hour with leading zeros; 01 to 12 */\n jsHour = String(jsDate.getHours());\n jsHour = (jsHour <= 12) ? jsHour : (jsHour - 12);\n result[i] = (jsHour.length > 1) ? jsHour : \"0\" + jsHour;\n break;\n\n case \"H\": /* 24-hour format of an hour with leading zeros; 01 to 24 */\n jsHour = String(jsDate.getHours());\n result[i] = (jsHour.length > 1) ? jsHour : \"0\" + jsHour;\n break;\n\n case \"i\": /* Minutes with leading zeros; 00 to 59 */\n var jsMinute = String(jsDate.getMinutes());\n result[i] = (jsMinute.length > 1) ? jsMinute : \"0\" + jsMinute;\n break;\n\n case \"s\": /* Seconds with leading zeros; 00 to 59; */\n var jsSecond = String(jsDate.getSeconds());\n result[i] = (jsSecond.length > 1) ? jsSecond : \"0\" + jsSecond;\n break;\n\n case \"u\": /* Microseconds */\n throw '\"u\" not implemented!';\n\n\n /* TIMEZONE */\n\n case \"e\": /* Timezone identifier */\n throw '\"e\" not implemented!';\n\n case \"I\": /* \"1\" if Daylight Savings Time, \"0\" otherwise. Works only on the northern hemisphere */\n jsFirstDay = new Date(jsDate.getFullYear(), 0, 1);\n result[i] = (jsDate.getTimezoneOffset() !== jsFirstDay.getTimezoneOffset()) ? (1) : (0);\n break;\n\n case \"O\": /* Difference to Greenwich time (GMT) in hours */\n var jsMinZone = jsDate.getTimezoneOffset();\n var jsMinutes = jsMinZone % 60;\n jsHour = String(((jsMinZone - jsMinutes) / 60) * -1);\n\n if (jsHour.charAt(0) !== \"-\") {\n jsHour = \"+\" + jsHour;\n }\n\n jsHour = (jsHour.length === 3) ? (jsHour) : (jsHour.replace(/([+\\-])(\\d)/, \"$1\" + 0 + \"$2\"));\n result[i] = jsHour + jsMinutes + \"0\";\n break;\n\n case \"P\": /* Difference to Greenwich time (GMT) with colon between hours and minutes */\n throw '\"P\" not implemented!';\n\n case \"T\": /* Timezone abbreviation */\n throw '\"T\" not implemented!';\n\n case \"Z\": /* Timezone offset in seconds. The offset for timezones west of UTC is always negative, and for those east of UTC is always positive. */\n result[i] = jsDate.getTimezoneOffset() * 60;\n break;\n\n\n /* FULL DATE/TIME */\n\n case \"c\": /* ISO 8601 date */\n throw '\"c\" not implemented!';\n\n case \"r\": /* RFC 2822 formatted date */\n var jsDayName = this._wDays(jsDate.getDay()).substr(0, 3);\n var jsMonthName = this._months(jsDate.getMonth()).substr(0, 3);\n result[i] = jsDayName + \", \" + jsDate.getDate() + \" \" + jsMonthName + this.get(\" Y H:i:s O\",jsDate);\n break;\n\n case \"U\": /* Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) */\n result[i] = Math.floor(jsDate.getTime() / 1000);\n break;\n\n default:\n result[i] = iFormat[i];\n }\n }\n\n return result.join('');\n\n },\n\n /**\n * Creates a date object based on a format string.\n * This works exactly as php date() function. http://php.net/manual/en/function.date.php\n *\n * @method set\n * @param {String} [format] The format in which the date will be formatted. Defaults to 'Y-m-d'\n * @param {String} str_date The date formatted.\n * @return {Date} Date object based on the formatted date and format\n * @public\n * @static\n * @sample Ink_Util_Date_set.html \n */\n set : function( format , str_date ) {\n if ( typeof str_date === 'undefined' ) { return ; }\n if ( typeof format === 'undefined' || format === '' ) { format = \"Y-m-d\"; }\n\n var iFormat = format.split(\"\");\n var result = new Array( iFormat.length );\n var escapeChar = \"\\\\\";\n var mList;\n\n var objIndex = {\n year : undefined ,\n month : undefined ,\n day : undefined ,\n dayY : undefined ,\n dayW : undefined ,\n week : undefined ,\n hour : undefined ,\n hourD : undefined ,\n min : undefined ,\n sec : undefined ,\n msec : undefined ,\n ampm : undefined ,\n diffM : undefined ,\n diffH : undefined ,\n date : undefined\n };\n\n var matches = 0;\n\n /* This switch is presented in the same order as in php date function (PHP 5.2.2) */\n for ( var i = 0; i < iFormat.length; i++) {\n switch( iFormat[ i ] ) {\n case escapeChar:\n result[i] = iFormat[ i + 1 ];\n i++;\n break;\n\n /* DAY */\n case \"d\": /* Day of the month, 2 digits with leading zeros; ex: 01 to 31 */\n result[ i ] = '(\\\\d{2})';\n objIndex.day = { original : i , match : matches++ };\n break;\n\n case \"j\": /* Day of the month without leading zeros; ex: 1 to 31 */\n result[ i ] = '(\\\\d{1,2})';\n objIndex.day = { original : i , match : matches++ };\n break;\n\n case \"D\": /* A textual representation of a day, three letters; Seg to Dom */\n result[ i ] = '([\\\\wá]{3})';\n objIndex.dayW = { original : i , match : matches++ };\n break;\n\n case \"l\": /* A full textual representation of the day of the week; Domingo to Sabado */\n result[i] = '([\\\\wá]{5,7})';\n objIndex.dayW = { original : i , match : matches++ };\n break;\n\n case \"N\": /* ISO-8601 numeric representation of the day of the week; 1 (Segunda) to 7 (Domingo) */\n result[ i ] = '(\\\\d)';\n objIndex.dayW = { original : i , match : matches++ };\n break;\n\n case \"w\": /* Numeric representation of the day of the week; 0 (for Sunday) through 6 (for Saturday) */\n result[ i ] = '(\\\\d)';\n objIndex.dayW = { original : i , match : matches++ };\n break;\n\n case \"S\": /* English ordinal suffix for the day of the month, 2 characters; st, nd, rd or th. Works well with j */\n result[ i ] = '\\\\w{2}';\n break;\n\n case \"z\": /* The day of the year (starting from 0); 0 to 365 */\n result[ i ] = '(\\\\d{1,3})';\n objIndex.dayY = { original : i , match : matches++ };\n break;\n\n /* WEEK */\n case \"W\": /* ISO-8601 week number of year, weeks starting on Monday; ex: 42 (the 42nd week in the year) */\n result[ i ] = '(\\\\d{1,2})';\n objIndex.week = { original : i , match : matches++ };\n break;\n\n /* MONTH */\n case \"F\": /* A full textual representation of a month, such as Janeiro or Marco; Janeiro a Dezembro */\n result[ i ] = '([\\\\wç]{4,9})';\n objIndex.month = { original : i , match : matches++ };\n break;\n\n case \"M\": /* A short textual representation of a month, three letters; Jan a Dez */\n result[ i ] = '(\\\\w{3})';\n objIndex.month = { original : i , match : matches++ };\n break;\n\n case \"m\": /* Numeric representation of a month, with leading zeros; 01 to 12 */\n result[ i ] = '(\\\\d{2})';\n objIndex.month = { original : i , match : matches++ };\n break;\n\n case \"n\": /* Numeric representation of a month, without leading zeros; 1 a 12 */\n result[ i ] = '(\\\\d{1,2})';\n objIndex.month = { original : i , match : matches++ };\n break;\n\n case \"t\": /* Number of days in the given month; ex: 28 */\n result[ i ] = '\\\\d{2}';\n break;\n\n /* YEAR */\n case \"L\": /* Whether it's a leap year; 1 if it is a leap year, 0 otherwise. */\n result[ i ] = '\\\\w{4,5}';\n break;\n\n case \"o\": /* ISO-8601 year number. This has the same value as Y, except that if the ISO week number (W) belongs to the previous or next year, that year is used instead. */\n throw '\"o\" not implemented!';\n\n case \"Y\": /* A full numeric representation of a year, 4 digits; 1999 */\n result[ i ] = '(\\\\d{4})';\n objIndex.year = { original : i , match : matches++ };\n break;\n\n case \"y\": /* A two digit representation of a year; 99 */\n result[ i ] = '(\\\\d{2})';\n if ( typeof objIndex.year === 'undefined' || iFormat[ objIndex.year.original ] !== 'Y' ) {\n objIndex.year = { original : i , match : matches++ };\n }\n break;\n\n /* TIME */\n case \"a\": /* Lowercase Ante meridiem and Post meridiem; am or pm */\n result[ i ] = '(am|pm)';\n objIndex.ampm = { original : i , match : matches++ };\n break;\n\n case \"A\": /* Uppercase Ante meridiem and Post meridiem; AM or PM */\n result[ i ] = '(AM|PM)';\n objIndex.ampm = { original : i , match : matches++ };\n break;\n\n case \"B\": /* Swatch Internet time; 000 through 999 */\n throw '\"B\" not implemented!';\n\n case \"g\": /* 12-hour format of an hour without leading zeros; 1 to 12 */\n result[ i ] = '(\\\\d{1,2})';\n objIndex.hourD = { original : i , match : matches++ };\n break;\n\n case \"G\": /* 24-hour format of an hour without leading zeros; 1 to 23 */\n result[ i ] = '(\\\\d{1,2})';\n objIndex.hour = { original : i , match : matches++ };\n break;\n\n case \"h\": /* 12-hour format of an hour with leading zeros; 01 to 12 */\n result[ i ] = '(\\\\d{2})';\n objIndex.hourD = { original : i , match : matches++ };\n break;\n\n case \"H\": /* 24-hour format of an hour with leading zeros; 01 to 24 */\n result[ i ] = '(\\\\d{2})';\n objIndex.hour = { original : i , match : matches++ };\n break;\n\n case \"i\": /* Minutes with leading zeros; 00 to 59 */\n result[ i ] = '(\\\\d{2})';\n objIndex.min = { original : i , match : matches++ };\n break;\n\n case \"s\": /* Seconds with leading zeros; 00 to 59; */\n result[ i ] = '(\\\\d{2})';\n objIndex.sec = { original : i , match : matches++ };\n break;\n\n case \"u\": /* Microseconds */\n throw '\"u\" not implemented!';\n\n /* TIMEZONE */\n case \"e\": /* Timezone identifier */\n throw '\"e\" not implemented!';\n\n case \"I\": /* \"1\" if Daylight Savings Time, \"0\" otherwise. Works only on the northern hemisphere */\n result[i] = '\\\\d';\n break;\n\n case \"O\": /* Difference to Greenwich time (GMT) in hours */\n result[ i ] = '([-+]\\\\d{4})';\n objIndex.diffH = { original : i , match : matches++ };\n break;\n\n case \"P\": /* Difference to Greenwich time (GMT) with colon between hours and minutes */\n throw '\"P\" not implemented!';\n\n case \"T\": /* Timezone abbreviation */\n throw '\"T\" not implemented!';\n\n case \"Z\": /* Timezone offset in seconds. The offset for timezones west of UTC is always negative, and for those east of UTC is always positive. */\n result[ i ] = '(\\\\-?\\\\d{1,5})';\n objIndex.diffM = { original : i , match : matches++ };\n break;\n\n /* FULL DATE/TIME */\n case \"c\": /* ISO 8601 date */\n throw '\"c\" not implemented!';\n\n case \"r\": /* RFC 2822 formatted date */\n result[ i ] = '([\\\\wá]{3}, \\\\d{1,2} \\\\w{3} \\\\d{4} \\\\d{2}:\\\\d{2}:\\\\d{2} [+\\\\-]\\\\d{4})';\n objIndex.date = { original : i , match : matches++ };\n break;\n\n case \"U\": /* Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) */\n result[ i ] = '(\\\\d{1,13})';\n objIndex.date = { original : i , match : matches++ };\n break;\n\n default:\n result[ i ] = iFormat[ i ];\n }\n }\n\n var pattr = new RegExp( result.join('') );\n\n try {\n mList = str_date.match( pattr );\n if ( !mList ) { return; }\n }\n catch ( e ) { return ; }\n\n var _haveDatetime = typeof objIndex.date !== 'undefined';\n\n var _haveYear = typeof objIndex.year !== 'undefined';\n\n var _haveYDay = typeof objIndex.dayY !== 'undefined';\n\n var _haveDay = typeof objIndex.day !== 'undefined';\n var _haveMonth = typeof objIndex.month !== 'undefined';\n var _haveMonthDay = _haveMonth && _haveDay;\n var _haveOnlyDay = !_haveMonth && _haveDay;\n\n var _haveWDay = typeof objIndex.dayW !== 'undefined';\n var _haveWeek = typeof objIndex.week !== 'undefined';\n var _haveWeekWDay = _haveWeek && _haveWDay;\n var _haveOnlyWDay = !_haveWeek && _haveWDay;\n\n var _validDate = _haveYDay || _haveMonthDay || !_haveYear && _haveOnlyDay || _haveWeekWDay || !_haveYear && _haveOnlyWDay;\n var _noDate = !_haveYear && !_haveYDay && !_haveDay && !_haveMonth && !_haveWDay && !_haveWeek;\n\n var _haveHour12 = typeof objIndex.hourD !== 'undefined' && typeof objIndex.ampm !== 'undefined';\n var _haveHour24 = typeof objIndex.hour !== 'undefined';\n var _haveHour = _haveHour12 || _haveHour24;\n\n var _haveMin = typeof objIndex.min !== 'undefined';\n var _haveSec = typeof objIndex.sec !== 'undefined';\n var _haveMSec = typeof objIndex.msec !== 'undefined';\n\n var _haveMoreM = !_noDate || _haveHour;\n var _haveMoreS = _haveMoreM || _haveMin;\n\n var _haveDiffM = typeof objIndex.diffM !== 'undefined';\n var _haveDiffH = typeof objIndex.diffH !== 'undefined';\n //var _haveGMT = _haveDiffM || _haveDiffH;\n var hour;\n var min;\n\n if ( _haveDatetime ) {\n if ( iFormat[ objIndex.date.original ] === 'U' ) {\n return new Date( +mList[ objIndex.date.match + 1 ] * 1000 );\n }\n\n var dList = mList[ objIndex.date.match + 1 ].match( /\\w{3}, (\\d{1,2}) (\\w{3}) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) ([+\\-]\\d{4})/ );\n hour = +dList[ 4 ] + ( +dList[ 7 ].slice( 0 , 3 ) );\n min = +dList[ 5 ] + ( dList[ 7 ].slice( 0 , 1 ) + dList[ 7 ].slice( 3 ) ) / 100 * 60;\n\n return new Date( dList[ 3 ] , this._iMonth( dList[ 2 ] ) , dList[ 1 ] , hour , min , dList[ 6 ] );\n }\n\n var _d = new Date( );\n var year;\n var month;\n var day;\n var sec;\n var msec;\n var gmt;\n\n if ( !_validDate && !_noDate ) { return ; }\n\n if ( _validDate ) {\n if ( _haveYear ) {\n var _y = _d.getFullYear( ) - 50 + '';\n year = mList[ objIndex.year.match + 1 ];\n if ( iFormat[ objIndex.year.original ] === 'y' ) {\n year = +_y.slice( 0 , 2 ) + ( year >= ( _y ).slice( 2 ) ? 0 : 1 ) + year;\n }\n } else {\n year = _d.getFullYear();\n }\n\n if ( _haveYDay ) {\n month = 0;\n day = mList[ objIndex.dayY.match + 1 ];\n } else if ( _haveDay ) {\n if ( _haveMonth ) {\n month = this._iMonth( mList[ objIndex.month.match + 1 ] );\n } else {\n month = _d.getMonth( );\n }\n\n day = mList[ objIndex.day.match + 1 ];\n } else {\n month = 0;\n\n var week;\n if ( _haveWeek ) {\n week = mList[ objIndex.week.match + 1 ];\n } else {\n week = this.get( 'W' , _d );\n }\n\n day = ( week - 2 ) * 7 + ( 8 - ( ( new Date( year , 0 , 1 ) ).getDay( ) || 7 ) ) + this._iWeek( mList[ objIndex.week.match + 1 ] );\n }\n\n if ( month === 0 && day > 31 ) {\n var aux = new Date( year , month , day );\n month = aux.getMonth( );\n day = aux.getDate( );\n }\n }\n else {\n year = _d.getFullYear( );\n month = _d.getMonth( );\n day = _d.getDate( );\n }\n\n if ( _haveHour12 ) { hour = +mList[ objIndex.hourD.match + 1 ] + ( mList[ objIndex.ampm.match + 1 ] === 'pm' ? 12 : 0 ); }\n else if ( _haveHour24 ) { hour = mList[ objIndex.hour.match + 1 ]; }\n else if ( _noDate ) { hour = _d.getHours( ); }\n else { hour = '00'; }\n\n if ( _haveMin ) { min = mList[ objIndex.min.match + 1 ]; }\n else if ( !_haveMoreM ) { min = _d.getMinutes( ); }\n else { min = '00'; }\n\n if ( _haveSec ) { sec = mList[ objIndex.sec.match + 1 ]; }\n else if ( !_haveMoreS ) { sec = _d.getSeconds( ); }\n else { sec = '00'; }\n\n if ( _haveMSec ) { msec = mList[ objIndex.msec.match + 1 ]; }\n else { msec = '000'; }\n\n if ( _haveDiffH ) { gmt = mList[ objIndex.diffH.match + 1 ]; }\n else if ( _haveDiffM ) { gmt = String( -1 * mList[ objIndex.diffM.match + 1 ] / 60 * 100 ).replace( /^(\\d)/ , '+$1' ).replace( /(^[\\-+])(\\d{3}$)/ , '$10$2' ); }\n else { gmt = '+0000'; }\n\n return new Date( year, month, day, hour, min, sec );\n }\n };\n\n\n return InkDate;\n\n});\n","/**\n * Dump/Profiling Utilities\n * @module Ink.Util.Dumper_1\n * @version 1\n */\n\nInk.createModule('Ink.Util.Dumper', '1', [], function() {\n\n 'use strict';\n\n /**\n * @namespace Ink.Util.Dumper_1 \n */\n\n var Dumper = {\n\n /**\n * Hex code for the 'tab'\n * \n * @property _tab\n * @type {String}\n * @private\n * @readOnly\n * @static\n *\n */\n _tab: '\\xA0\\xA0\\xA0\\xA0',\n\n /**\n * Function that returns the argument passed formatted\n *\n * @method _formatParam\n * @param {Mixed} param\n * @return {String} The argument passed formatted\n * @private\n * @static\n */\n _formatParam: function(param)\n {\n var formated = '';\n\n switch(typeof(param)) {\n case 'string':\n formated = '(string) '+param;\n break;\n case 'number':\n formated = '(number) '+param;\n break;\n case 'boolean':\n formated = '(boolean) '+param;\n break;\n case 'object':\n if(param !== null) {\n if(param.constructor === Array) {\n formated = 'Array \\n{\\n' + this._outputFormat(param, 0) + '\\n}';\n } else {\n formated = 'Object \\n{\\n' + this._outputFormat(param, 0) + '\\n}';\n }\n } else {\n formated = 'null';\n }\n break;\n default:\n formated = false;\n }\n\n return formated;\n },\n\n /**\n * Function that returns the tabs concatenated\n *\n * @method _getTabs\n * @param {Number} numberOfTabs Number of Tabs\n * @return {String} Tabs concatenated\n * @private\n * @static\n */\n _getTabs: function(numberOfTabs)\n {\n var tabs = '';\n for(var _i = 0; _i < numberOfTabs; _i++) {\n tabs += this._tab;\n }\n return tabs;\n },\n\n /**\n * Function that formats the parameter to display.\n *\n * @method _outputFormat\n * @param {Any} param\n * @param {Number} dim\n * @return {String} The parameter passed formatted to displat\n * @private\n * @static\n */\n _outputFormat: function(param, dim)\n {\n var formated = '';\n //var _strVal = false;\n var _typeof = false;\n for(var key in param) {\n if(param[key] !== null) {\n if(typeof(param[key]) === 'object' && (param[key].constructor === Array || param[key].constructor === Object)) {\n if(param[key].constructor === Array) {\n _typeof = 'Array';\n } else if(param[key].constructor === Object) {\n _typeof = 'Object';\n }\n formated += this._tab + this._getTabs(dim) + '[' + key + '] => '+_typeof+'\\n';\n formated += this._tab + this._getTabs(dim) + '{\\n';\n formated += this._outputFormat(param[key], dim + 1) + this._tab + this._getTabs(dim) + '}\\n';\n } else if(param[key].constructor === Function) {\n continue;\n } else {\n formated = formated + this._tab + this._getTabs(dim) + '[' + key + '] => ' + param[key] + '\\n';\n }\n } else {\n formated = formated + this._tab + this._getTabs(dim) + '[' + key + '] => null \\n';\n }\n }\n return formated;\n },\n\n /**\n * Prints variable structure.\n *\n * @method printDump\n * @param {Any} param Variable to be dumped.\n * @param {DOMElement|String} [target] Element to print the dump on.\n * @public\n * @static\n * @sample Ink_Util_Dumper_printDump.html \n */\n printDump: function(param, target)\n {\n /*jshint evil:true */\n if(!target || typeof(target) === 'undefined') {\n document.write(''+this._formatParam(param)+' ');\n } else {\n if(typeof(target) === 'string') {\n document.getElementById(target).innerHTML = '' + this._formatParam(param) + ' ';\n } else if(typeof(target) === 'object') {\n target.innerHTML = ''+this._formatParam(param)+' ';\n } else {\n throw \"TARGET must be an element or an element ID\";\n }\n }\n },\n\n /**\n * Get a variable's structure.\n *\n * @method returnDump\n * @param {Any} param Variable to get the structure.\n * @return {String} The variable's structure.\n * @public\n * @static\n * @sample Ink_Util_Dumper_returnDump.html \n */\n returnDump: function(param)\n {\n return this._formatParam(param);\n },\n\n /**\n * Alert a variable's structure.\n *\n * @method alertDump\n * @param {Any} param Variable to be dumped.\n * @public\n * @static\n * @sample Ink_Util_Dumper_alertDump.html \n */\n alertDump: function(param)\n {\n window.alert(this._formatParam(param).replace(/()(Array|Object)(<\\/b>)/g, \"$2\"));\n },\n\n /**\n * Prints the variable structure to a new window.\n *\n * @method windowDump\n * @param {Any} param Variable to be dumped.\n * @public\n * @static\n * @sample Ink_Util_Dumper_windowDump.html \n */\n windowDump: function(param)\n {\n var dumperwindow = 'dumperwindow_'+(Math.random() * 10000);\n var win = window.open('',\n dumperwindow,\n 'width=400,height=300,left=50,top=50,status,menubar,scrollbars,resizable'\n );\n win.document.open();\n win.document.write(''+this._formatParam(param)+' ');\n win.document.close();\n win.focus();\n }\n\n };\n\n return Dumper;\n\n});\n","/**\n * Internationalization Utilities \n * @module Ink.Util.I18n_1\n * @version 1\n */\n\nInk.createModule('Ink.Util.I18n', '1', [], function () {\n 'use strict';\n\n var pattrText = /\\{(?:(\\{.*?})|(?:%s:)?(\\d+)|(?:%s)?|([\\w-]+))}/g;\n\n var funcOrVal = function( ret , args ) {\n if ( typeof ret === 'function' ) {\n return ret.apply(this, args);\n } else if (typeof ret !== undefined) {\n return ret;\n } else {\n return '';\n }\n };\n\n /**\n * You can use this module to internationalize your applications. It roughly emulates GNU gettext's API.\n *\n * @class Ink.Util.I18n\n * @constructor\n *\n * @param {Object} dict Object mapping language codes (in the form of `pt_PT`, `pt_BR`, `fr`, `en_US`, etc.) to their `dictionaries`\n * @param {String} [lang='pt_PT'] language code of the target language\n *\n * @sample Ink_Util_I18n_1.html\n */\n var I18n = function( dict , lang , testMode ) {\n if ( !( this instanceof I18n ) ) { return new I18n( dict , lang , testMode ); }\n\n this.reset( )\n .lang( lang )\n .testMode( testMode )\n .append( dict || { } , lang );\n };\n\n I18n.prototype = {\n reset: function( ) {\n this._dicts = [ ];\n this._dict = { };\n this._testMode = false;\n this._lang = this._gLang;\n\n return this;\n },\n /**\n * Adds translation strings for the helper to use.\n *\n * @method append\n * @param {Object} dict Object containing language objects identified by their language code\n *\n * @sample Ink_Util_I18n_1_append.html\n */\n append: function( dict ) {\n this._dicts.push( dict );\n\n this._dict = Ink.extendObj(this._dict , dict[ this._lang ] );\n\n return this;\n },\n /**\n * Gets or sets the language.\n * If there are more dictionaries available in cache, they will be loaded.\n *\n * @method lang\n * @param {String} lang Language code to set this instance to.\n */\n lang: function( lang ) {\n if ( !arguments.length ) { return this._lang; }\n\n if ( lang && this._lang !== lang ) {\n this._lang = lang;\n\n this._dict = { };\n\n for ( var i = 0, l = this._dicts.length; i < l; i++ ) {\n this._dict = Ink.extendObj( this._dict , this._dicts[ i ][ lang ] || { } );\n }\n }\n\n return this;\n },\n /**\n * Sets or unsets test mode.\n * In test mode, unknown strings are wrapped in `[ ... ]`. This is useful for debugging your application and to make sure all your translation keys are in place.\n *\n * @method testMode\n * @param {Boolean} bool Flag to set the test mode state\n */\n testMode: function( bool ) {\n if ( !arguments.length ) { return !!this._testMode; }\n\n if ( bool !== undefined ) { this._testMode = !!bool; }\n\n return this;\n },\n\n /**\n * Gest a key from the current dictionary\n *\n * @method getKey\n * @param {String} key\n * @return {Mixed} The object which happened to be in the current language dictionary on the given key.\n *\n * @sample Ink_Util_I18n_1_getKey.html\n */\n getKey: function( key ) {\n var ret;\n var gLang = this._gLang;\n var lang = this._lang;\n \n if ( key in this._dict ) {\n ret = this._dict[ key ];\n } else {\n I18n.langGlobal( lang );\n \n ret = this._gDict[ key ];\n \n I18n.langGlobal( gLang );\n }\n \n return ret;\n },\n\n /**\n * Translates a string.\n * Given a translation key, return a translated string, with replaced parameters.\n * When a translated string is not available, the original string is returned unchanged.\n *\n * @method text\n * @param {String} str Key to look for in i18n dictionary (which is returned verbatim if unknown)\n * @param {Object} [namedParms] Named replacements. Replaces {named} with values in this object.\n * @param {String} [args] Replacement #1 (replaces first {} and all {1})\n * @param {String} [arg2] Replacement #2 (replaces second {} and all {2})\n * @param {String} [argn*] Replacement #n (replaces nth {} and all {n})\n *\n * @sample Ink_Util_I18n_1_text.html\n */\n text: function( str /*, replacements...*/ ) {\n if ( typeof str !== 'string' ) { return; } // Backwards-compat\n\n var pars = Array.prototype.slice.call( arguments , 1 );\n var idx = 0;\n var isObj = typeof pars[ 0 ] === 'object';\n\n var original = this.getKey( str );\n if ( original === undefined ) { original = this._testMode ? '[' + str + ']' : str; }\n if ( typeof original === 'number' ) { original += ''; }\n\n if (typeof original === 'string') {\n original = original.replace( pattrText , function( m , $1 , $2 , $3 ) {\n var ret =\n $1 ? $1 :\n $2 ? pars[ $2 - ( isObj ? 0 : 1 ) ] :\n $3 ? pars[ 0 ][ $3 ] || '' :\n pars[ (idx++) + ( isObj ? 1 : 0 ) ];\n return funcOrVal( ret , [idx].concat(pars) );\n });\n return original;\n }\n \n return (\n typeof original === 'function' ? original.apply( this , pars ) :\n original instanceof Array ? funcOrVal( original[ pars[ 0 ] ] , pars ) :\n typeof original === 'object' ? funcOrVal( original[ pars[ 0 ] ] , pars ) :\n '');\n },\n\n /**\n * Translates and pluralizes text.\n * Given a singular string, a plural string and a number, translates either the singular or plural string.\n *\n * @method ntext\n * @return {String}\n *\n * @param {String} strSin Word to use when count is 1\n * @param {String} strPlur Word to use otherwise\n * @param {Number} count Number which defines which word to use\n * @param [args*] Extra arguments, to be passed to `text()`\n *\n * @sample Ink_Util_I18n_1_ntext.html\n */\n ntext: function( strSin , strPlur , count ) {\n var pars = Array.prototype.slice.apply( arguments );\n var original;\n\n if ( pars.length === 2 && typeof strPlur === 'number' ) {\n original = this.getKey( strSin );\n if ( !( original instanceof Array ) ) { return ''; }\n\n pars.splice( 0 , 1 );\n original = original[ strPlur === 1 ? 0 : 1 ];\n } else {\n pars.splice( 0 , 2 );\n original = count === 1 ? strSin : strPlur;\n }\n\n return this.text.apply( this , [ original ].concat( pars ) );\n },\n\n /**\n * Gets the ordinal suffix of a number.\n *\n * This works by using transforms (in the form of Objects or Functions) passed into the function or found in the special key `_ordinals` in the active language dictionary.\n *\n * @method ordinal\n *\n * @param {Number} num Input number\n * @param {Object|Function} [options]={} Dictionaries for translating. Each of these options' fallback is found in the current language's dictionary. The lookup order is the following: `exceptions`, `byLastDigit`, `default`. Each of these may be either an `Object` or a `Function`. If it's a function, it is called (with `number` and `digit` for any function except for byLastDigit, which is called with the `lastDigit` of the number in question), and if the function returns a string, that is used. If it's an object, the property is looked up using `obj[prop]`. If what is found is a string, it is used directly.\n * @param {Object|Function} [options.byLastDigit]={} If the language requires the last digit to be considered, mappings of last digits to ordinal suffixes can be created here.\n * @param {Object|Function} [options.exceptions]={} Map unique, special cases to their ordinal suffixes.\n *\n * @returns {String} Ordinal suffix for `num`.\n *\n * @sample Ink_Util_I18n_1_ordinal.html\n **/\n ordinal: function( num ) {\n if ( num === undefined ) { return ''; }\n\n var lastDig = +num.toString( ).slice( -1 );\n\n var ordDict = this.getKey( '_ordinals' );\n if ( ordDict === undefined ) { return ''; }\n\n if ( typeof ordDict === 'string' ) { return ordDict; }\n\n var ret;\n\n if ( typeof ordDict === 'function' ) {\n ret = ordDict( num , lastDig );\n\n if ( typeof ret === 'string' ) { return ret; }\n }\n\n if ( 'exceptions' in ordDict ) {\n ret = typeof ordDict.exceptions === 'function' ? ordDict.exceptions( num , lastDig ) :\n num in ordDict.exceptions ? funcOrVal( ordDict.exceptions[ num ] , [num , lastDig] ) :\n undefined;\n\n if ( typeof ret === 'string' ) { return ret; }\n }\n\n if ( 'byLastDigit' in ordDict ) {\n ret = typeof ordDict.byLastDigit === 'function' ? ordDict.byLastDigit( lastDig , num ) :\n lastDig in ordDict.byLastDigit ? funcOrVal( ordDict.byLastDigit[ lastDig ] , [lastDig , num] ) :\n undefined;\n\n if ( typeof ret === 'string' ) { return ret; }\n }\n\n if ( 'default' in ordDict ) {\n ret = funcOrVal( ordDict['default'] , [ num , lastDig ] );\n\n if ( typeof ret === 'string' ) { return ret; }\n }\n\n return '';\n },\n\n /**\n * Create an alias.\n *\n * Returns an alias to this I18n instance. It contains the I18n methods documented here, but is also a function. If you call it, it just calls `text()`. This is commonly assigned to \"_\".\n *\n * @method alias\n * @returns {Function} an alias to `text()` on this instance. You can also access the rest of the translation API through this alias.\n *\n * @sample Ink_Util_I18n_1_alias.html\n */\n alias: function( ) {\n var ret = Ink.bind( I18n.prototype.text , this );\n ret.ntext = Ink.bind( I18n.prototype.ntext , this );\n ret.append = Ink.bind( I18n.prototype.append , this );\n ret.ordinal = Ink.bind( I18n.prototype.ordinal , this );\n ret.testMode = Ink.bind( I18n.prototype.testMode , this );\n\n return ret;\n }\n };\n\n /**\n * Resets I18n global state (global dictionaries, and default language for instances)\n *\n * @method reset\n * @static\n *\n **/\n I18n.reset = function( ) {\n I18n.prototype._gDicts = [ ];\n I18n.prototype._gDict = { };\n I18n.prototype._gLang = 'pt_PT';\n };\n I18n.reset( );\n\n /**\n * Adds a dictionary to be used in all I18n instances for the corresponding language.\n *\n * @method appendGlobal\n * @static\n *\n * @param dict {Object} Dictionary to be added\n * @param lang {String} Language fo the dictionary being added\n *\n */\n I18n.appendGlobal = function( dict , lang ) {\n if ( lang ) {\n if ( !( lang in dict ) ) {\n var obj = { };\n\n obj[ lang ] = dict;\n\n dict = obj;\n }\n\n if ( lang !== I18n.prototype._gLang ) { I18n.langGlobal( lang ); }\n }\n\n I18n.prototype._gDicts.push( dict );\n\n Ink.extendObj( I18n.prototype._gDict , dict[ I18n.prototype._gLang ] );\n };\n\n I18n.append = function () {\n // [3.1.0] remove this alias\n Ink.warn('Ink.Util.I18n.append() was renamed to appendGlobal().');\n return I18n.appendGlobal.apply(I18n, [].slice.call(arguments));\n };\n\n /**\n * Gets or sets the current default language of I18n instances.\n *\n * @method langGlobal\n * @param lang the new language for all I18n instances\n *\n * @static\n *\n * @return {String} language code\n */\n I18n.langGlobal = function( lang ) {\n if ( !arguments.length ) { return I18n.prototype._gLang; }\n\n if ( lang && I18n.prototype._gLang !== lang ) {\n I18n.prototype._gLang = lang;\n\n I18n.prototype._gDict = { };\n\n for ( var i = 0, l = I18n.prototype._gDicts.length; i < l; i++ ) {\n Ink.extendObj( I18n.prototype._gDict , I18n.prototype._gDicts[ i ][ lang ] || { } );\n }\n }\n };\n\n I18n.lang = function () {\n // [3.1.0] remove this alias\n Ink.warn('Ink.Util.I18n.lang() was renamed to langGlobal().');\n return I18n.langGlobal.apply(I18n, [].slice.call(arguments));\n };\n \n return I18n;\n});","/**\n * JSON Utilities\n * @module Ink.Util.Json_1\n * @version 1\n */\n\nInk.createModule('Ink.Util.Json', '1', [], function() {\n 'use strict';\n\n var function_call = Function.prototype.call;\n var cx = /[\\u0000\\u00ad\\u0600-\\u0604\\u070f\\u17b4\\u17b5\\u200c-\\u200f\\u2028-\\u202f\\u2060-\\u206f\\ufeff\\ufff0-\\uffff]/g;\n\n function twoDigits(n) {\n var r = '' + n;\n if (r.length === 1) {\n return '0' + r;\n } else {\n return r;\n }\n }\n\n var dateToISOString = Date.prototype.toISOString ?\n Ink.bind(function_call, Date.prototype.toISOString) :\n function(date) {\n // Adapted from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString\n return date.getUTCFullYear() +\n '-' + twoDigits( date.getUTCMonth() + 1 ) +\n '-' + twoDigits( date.getUTCDate() ) +\n 'T' + twoDigits( date.getUTCHours() ) +\n ':' + twoDigits( date.getUTCMinutes() ) +\n ':' + twoDigits( date.getUTCSeconds() ) +\n '.' + String( (date.getUTCMilliseconds()/1000).toFixed(3) ).slice( 2, 5 ) +\n 'Z';\n };\n\n /**\n * Use this class to convert JSON strings to JavaScript objects\n * `.parse()` and also to do the opposite operation `.stringify()`.\n * Internally, the standard JSON implementation is used if available\n * Otherwise, the functions mimic the standard implementation.\n *\n * Here's how to produce JSON from an existing object:\n * \n * Ink.requireModules(['Ink.Util.Json_1'], function (Json) {\n * var obj = {\n * key1: 'value1',\n * key2: 'value2',\n * keyArray: ['arrayValue1', 'arrayValue2', 'arrayValue3']\n * };\n * Json.stringify(obj); // The above object as a JSON string\n * });\n *\n * And here is how to parse JSON:\n *\n * Ink.requireModules(['Ink.Util.Json_1'], function (Json) {\n * var source = '{\"key\": \"value\", \"array\": [true, null, false]}';\n * Json.parse(source); // The above JSON string as an object\n * });\n *\n * @namespace Ink.Util.Json_1 \n * @static\n * \n */\n var InkJson = {\n _nativeJSON: window.JSON || null,\n\n _convertToUnicode: false,\n\n // Escape characters so as to embed them in JSON strings\n _escape: function (theString) {\n var _m = { '\\b': '\\\\b', '\\t': '\\\\t', '\\n': '\\\\n', '\\f': '\\\\f', '\\r': '\\\\r', '\"': '\\\\\"', '\\\\': '\\\\\\\\' };\n\n if (/[\"\\\\\\x00-\\x1f]/.test(theString)) {\n theString = theString.replace(/([\\x00-\\x1f\\\\\"])/g, function(a, b) {\n var c = _m[b];\n if (c) {\n return c;\n }\n c = b.charCodeAt();\n return '\\\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);\n });\n }\n\n return theString;\n },\n\n // A character conversion map\n _toUnicode: function (theString)\n {\n if(!this._convertToUnicode) {\n return this._escape(theString);\n } else {\n var unicodeString = '';\n var inInt = false;\n var theUnicode = false;\n var i = 0;\n var total = theString.length;\n while(i < total) {\n inInt = theString.charCodeAt(i);\n if( (inInt >= 32 && inInt <= 126) ||\n //(inInt >= 48 && inInt <= 57) ||\n //(inInt >= 65 && inInt <= 90) ||\n //(inInt >= 97 && inInt <= 122) ||\n inInt === 8 ||\n inInt === 9 ||\n inInt === 10 ||\n inInt === 12 ||\n inInt === 13 ||\n inInt === 32 ||\n inInt === 34 ||\n inInt === 47 ||\n inInt === 58 ||\n inInt === 92) {\n\n if(inInt === 34 || inInt === 92 || inInt === 47) {\n theUnicode = '\\\\'+theString.charAt(i);\n } else if(inInt === 8) {\n theUnicode = '\\\\b';\n } else if(inInt === 9) {\n theUnicode = '\\\\t';\n } else if(inInt === 10) {\n theUnicode = '\\\\n';\n } else if(inInt === 12) {\n theUnicode = '\\\\f';\n } else if(inInt === 13) {\n theUnicode = '\\\\r';\n } else {\n theUnicode = theString.charAt(i);\n }\n } else {\n if(this._convertToUnicode) {\n theUnicode = theString.charCodeAt(i).toString(16)+''.toUpperCase();\n while (theUnicode.length < 4) {\n theUnicode = '0' + theUnicode;\n }\n theUnicode = '\\\\u' + theUnicode;\n } else {\n theUnicode = theString.charAt(i);\n }\n }\n unicodeString += theUnicode;\n\n i++;\n }\n\n return unicodeString;\n }\n\n },\n\n _stringifyValue: function(param) {\n if (typeof param === 'string') {\n return '\"' + this._toUnicode(param) + '\"';\n } else if (typeof param === 'number' && (isNaN(param) || !isFinite(param))) { // Unusable numbers go null\n return 'null';\n } else if (typeof param === 'undefined' || param === null) { // And so does undefined\n return 'null';\n } else if (typeof param.toJSON === 'function') {\n var t = param.toJSON();\n if (typeof t === 'string') {\n return '\"' + this._escape(t) + '\"';\n } else {\n return this._escape(t.toString());\n }\n } else if (typeof param === 'number' || typeof param === 'boolean') { // These ones' toString methods return valid JSON.\n return '' + param;\n } else if (typeof param === 'function') {\n return 'null'; // match JSON.stringify\n } else if (param.constructor === Date) {\n return '\"' + this._escape(dateToISOString(param)) + '\"';\n } else if (param.constructor === Array) {\n var arrayString = '';\n for (var i = 0, len = param.length; i < len; i++) {\n if (i > 0) {\n arrayString += ',';\n }\n arrayString += this._stringifyValue(param[i]);\n }\n return '[' + arrayString + ']';\n } else { // Object\n var objectString = '';\n for (var k in param) {\n if ({}.hasOwnProperty.call(param, k)) {\n if (objectString !== '') {\n objectString += ',';\n }\n objectString += '\"' + this._escape(k) + '\": ' + this._stringifyValue(param[k]);\n }\n }\n return '{' + objectString + '}';\n }\n },\n\n /**\n * Serializes a JSON object into a string.\n *\n * @method stringify\n * @param {Object} input Data to be serialized into JSON\n * @param {Boolean} convertToUnicode When `true`, converts string contents to unicode \\uXXXX\n * @return {String} Serialized string\n *\n * @sample Ink_Util_Json_stringify.html \n */\n stringify: function(input, convertToUnicode) {\n this._convertToUnicode = !!convertToUnicode;\n if(!this._convertToUnicode && this._nativeJSON) {\n return this._nativeJSON.stringify(input);\n }\n return this._stringifyValue(input); // And recurse.\n },\n \n /**\n * Parses a JSON text through a function\n * \n * @method parse\n * @param text {String} Input string\n * @param reviver {Function} Function receiving `(key, value)`, and `this`=(containing object), used to walk objects.\n * \n * @return {Object} JSON object\n *\n * @sample Ink_Util_Json_parse.html \n */\n /* From https://github.com/douglascrockford/JSON-js/blob/master/json.js */\n parse: function (text, reviver) {\n /*jshint evil:true*/\n\n// The parse method takes a text and an optional reviver function, and returns\n// a JavaScript value if the text is a valid JSON text.\n\n var j;\n\n function walk(holder, key) {\n\n// The walk method is used to recursively walk the resulting structure so\n// that modifications can be made.\n\n var k, v, value = holder[key];\n if (value && typeof value === 'object') {\n for (k in value) {\n if (Object.prototype.hasOwnProperty.call(value, k)) {\n v = walk(value, k);\n if (v !== undefined) {\n value[k] = v;\n } else {\n delete value[k];\n }\n }\n }\n }\n return reviver.call(holder, key, value);\n }\n\n\n// Parsing happens in four stages. In the first stage, we replace certain\n// Unicode characters with escape sequences. JavaScript handles many characters\n// incorrectly, either silently deleting them, or treating them as line endings.\n\n text = String(text);\n cx.lastIndex = 0;\n if (cx.test(text)) {\n text = text.replace(cx, function (a) {\n return '\\\\u' +\n ('0000' + a.charCodeAt(0).toString(16)).slice(-4);\n });\n }\n\n// In the second stage, we run the text against regular expressions that look\n// for non-JSON patterns. We are especially concerned with '()' and 'new'\n// because they can cause invocation, and '=' because it can cause mutation.\n// But just to be safe, we want to reject all unexpected forms.\n\n// We split the second stage into 4 regexp operations in order to work around\n// crippling inefficiencies in IE's and Safari's regexp engines. First we\n// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we\n// replace all simple value tokens with ']' characters. Third, we delete all\n// open brackets that follow a colon or comma or that begin the text. Finally,\n// we look to see that the remaining characters are only whitespace or ']' or\n// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.\n\n if (/^[\\],:{}\\s]*$/\n .test(text.replace(/\\\\(?:[\"\\\\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')\n .replace(/\"[^\"\\\\\\n\\r]*\"|true|false|null|-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?/g, ']')\n .replace(/(?:^|:|,)(?:\\s*\\[)+/g, ''))) {\n\n// In the third stage we use the eval function to compile the text into a\n// JavaScript structure. The '{' operator is subject to a syntactic ambiguity\n// in JavaScript: it can begin a block or an object literal. We wrap the text\n// in parens to eliminate the ambiguity.\n\n j = eval('(' + text + ')');\n\n// In the optional fourth stage, we recursively walk the new structure, passing\n// each name/value pair to a reviver function for possible transformation.\n\n return typeof reviver === 'function' ?\n walk({'': j}, '') :\n j;\n }\n\n// If the text is not JSON parseable, then a SyntaxError is thrown.\n\n throw new SyntaxError('JSON.parse');\n }\n };\n\n return InkJson;\n});\n","/**\n * String Utilities\n * @module Ink.Util.String_1\n * @version 1\n */\n\nInk.createModule('Ink.Util.String', '1', [], function() {\n\n 'use strict';\n\n /**\n * @namespace Ink.Util.String_1 \n */\n var InkUtilString = {\n\n /**\n * List of special chars\n * \n * @property _chars\n * @type {Array}\n * @private\n * @readOnly\n * @static\n */\n _chars: ['&','à','á','â','ã','ä','å','æ','ç','è','é',\n 'ê','ë','ì','í','î','ï','ð','ñ','ò','ó','ô',\n 'õ','ö','ø','ù','ú','û','ü','ý','þ','ÿ','À',\n 'Á','Â','Ã','Ä','Å','Æ','Ç','È','É','Ê','Ë',\n 'Ì','Í','Î','Ï','Ð','Ñ','Ò','Ó','Ô','Õ','Ö',\n 'Ø','Ù','Ú','Û','Ü','Ý','Þ','€','\\\"','ß','<',\n '>','¢','£','¤','¥','¦','§','¨','©','ª','«',\n '¬','\\xad','®','¯','°','±','²','³','´','µ','¶',\n '·','¸','¹','º','»','¼','½','¾'],\n\n /**\n * List of the special characters' html entities\n * \n * @property _entities\n * @type {Array}\n * @private\n * @readOnly\n * @static\n */\n _entities: ['amp','agrave','aacute','acirc','atilde','auml','aring',\n 'aelig','ccedil','egrave','eacute','ecirc','euml','igrave',\n 'iacute','icirc','iuml','eth','ntilde','ograve','oacute',\n 'ocirc','otilde','ouml','oslash','ugrave','uacute','ucirc',\n 'uuml','yacute','thorn','yuml','Agrave','Aacute','Acirc',\n 'Atilde','Auml','Aring','AElig','Ccedil','Egrave','Eacute',\n 'Ecirc','Euml','Igrave','Iacute','Icirc','Iuml','ETH','Ntilde',\n 'Ograve','Oacute','Ocirc','Otilde','Ouml','Oslash','Ugrave',\n 'Uacute','Ucirc','Uuml','Yacute','THORN','euro','quot','szlig',\n 'lt','gt','cent','pound','curren','yen','brvbar','sect','uml',\n 'copy','ordf','laquo','not','shy','reg','macr','deg','plusmn',\n 'sup2','sup3','acute','micro','para','middot','cedil','sup1',\n 'ordm','raquo','frac14','frac12','frac34'],\n\n /**\n * List of accented chars\n * \n * @property _accentedChars\n * @type {Array}\n * @private\n * @readOnly\n * @static\n */\n _accentedChars:['à','á','â','ã','ä','å',\n 'è','é','ê','ë',\n 'ì','í','î','ï',\n 'ò','ó','ô','õ','ö',\n 'ù','ú','û','ü',\n 'ç','ñ',\n 'À','Á','Â','Ã','Ä','Å',\n 'È','É','Ê','Ë',\n 'Ì','Í','Î','Ï',\n 'Ò','Ó','Ô','Õ','Ö',\n 'Ù','Ú','Û','Ü',\n 'Ç','Ñ'],\n\n /**\n * List of the accented chars (above), but without the accents\n * \n * @property _accentedRemovedChars\n * @type {Array}\n * @private\n * @readOnly\n * @static\n */\n _accentedRemovedChars:['a','a','a','a','a','a',\n 'e','e','e','e',\n 'i','i','i','i',\n 'o','o','o','o','o',\n 'u','u','u','u',\n 'c','n',\n 'A','A','A','A','A','A',\n 'E','E','E','E',\n 'I','I','I','I',\n 'O','O','O','O','O',\n 'U','U','U','U',\n 'C','N'],\n /**\n * Object that contains the basic HTML unsafe chars, as keys, and their HTML entities as values\n * \n * @property _htmlUnsafeChars\n * @type {Object}\n * @private\n * @readOnly\n * @static\n */\n _htmlUnsafeChars:{'<':'<','>':'>','&':'&','\"':'"',\"'\":'''},\n\n /**\n * Capitalizes a word.\n * If param as more than one word, it converts first letter of all words that have more than 2 letters\n *\n * @method ucFirst\n * @param {String} string String to capitalize.\n * @param {Boolean} [firstWordOnly]=false Flag to capitalize only the first word.\n * @return {String} Camel cased string.\n * @public\n * @static\n * @sample Ink_Util_String_ucFirst.html \n */\n ucFirst: function(string, firstWordOnly) {\n var replacer = firstWordOnly ? /(^|\\s)(\\w)(\\S{2,})/ : /(^|\\s)(\\w)(\\S{2,})/g;\n return string ? String(string).replace(replacer, function(_, $1, $2, $3){\n return $1 + $2.toUpperCase() + $3.toLowerCase();\n }) : string;\n },\n\n /**\n * Trims whitespace from strings\n *\n * @method trim\n * @param {String} string String to be trimmed\n * @return {String} Trimmed string\n * @public\n * @static\n * @sample Ink_Util_String_trim.html \n */\n trim: function(string)\n {\n if (typeof string === 'string') {\n return string.replace(/^\\s+|\\s+$|\\n+$/g, '');\n }\n return string;\n },\n\n /**\n * Strips HTML tags from strings\n *\n * @method stripTags\n * @param {String} string String to strip tags from.\n * @param {String} allowed Comma separated list of allowed tags.\n * @return {String} Stripped string\n * @public\n * @static\n * @sample Ink_Util_String_stripTags.html \n */\n stripTags: function(string, allowed)\n {\n if (allowed && typeof allowed === 'string') {\n var aAllowed = InkUtilString.trim(allowed).split(',');\n var aNewAllowed = [];\n var cleanedTag = false;\n for(var i=0; i < aAllowed.length; i++) {\n if(InkUtilString.trim(aAllowed[i]) !== '') {\n cleanedTag = InkUtilString.trim(aAllowed[i].replace(/(<|\\>)/g, '').replace(/\\s/, ''));\n aNewAllowed.push('(<'+cleanedTag+'\\\\s[^>]+>|<(\\\\s|\\\\/)?(\\\\s|\\\\/)?'+cleanedTag+'>)');\n }\n }\n var strAllowed = aNewAllowed.join('|');\n var reAllowed = new RegExp(strAllowed, \"i\");\n\n var aFoundTags = string.match(new RegExp(\"<[^>]*>\", \"g\"));\n\n for(var j=0; j < aFoundTags.length; j++) {\n if(!aFoundTags[j].match(reAllowed)) {\n string = string.replace((new RegExp(aFoundTags[j], \"gm\")), '');\n }\n }\n return string;\n } else {\n return string.replace(/<[^\\>]+\\>/g, '');\n }\n },\n\n /**\n * Encodes string into HTML entities.\n *\n * @method htmlEntitiesEncode\n * @param {String} string\n * @return {String} string encoded\n * @public\n * @static\n * @sample Ink_Util_String_htmlEntitiesEncode.html \n */\n htmlEntitiesEncode: function(string)\n {\n if (string && string.replace) {\n var re = false;\n for (var i = 0; i < InkUtilString._chars.length; i++) {\n re = new RegExp(InkUtilString._chars[i], \"gm\");\n string = string.replace(re, '&' + InkUtilString._entities[i] + ';');\n }\n }\n return string;\n },\n\n /**\n * Decodes string from HTML entities.\n *\n * @method htmlEntitiesDecode\n * @param {String} string String to be decoded\n * @return {String} Decoded string\n * @public\n * @static\n * @sample Ink_Util_String_htmlEntitiesDecode.html \n */\n htmlEntitiesDecode: function(string)\n {\n if (string && string.replace) {\n var re = false;\n for (var i = 0; i < InkUtilString._entities.length; i++) {\n re = new RegExp(\"&\"+InkUtilString._entities[i]+\";\", \"gm\");\n string = string.replace(re, InkUtilString._chars[i]);\n }\n string = string.replace(/[^;]+;?/g, function($0){\n if ($0.charAt(2) === 'x') {\n return String.fromCharCode(parseInt($0.substring(3), 16));\n }\n else {\n return String.fromCharCode(parseInt($0.substring(2), 10));\n }\n });\n }\n return string;\n },\n\n /**\n * Encode a string to UTF-8.\n *\n * @method utf8Encode\n * @param {String} string String to be encoded\n * @return {String} string UTF-8 encoded string\n * @public\n * @static\n */\n utf8Encode: function(string) {\n /*jshint bitwise:false*/\n string = string.replace(/\\r\\n/g,\"\\n\");\n var utfstring = \"\";\n\n for (var n = 0; n < string.length; n++) {\n\n var c = string.charCodeAt(n);\n\n if (c < 128) {\n utfstring += String.fromCharCode(c);\n }\n else if((c > 127) && (c < 2048)) {\n utfstring += String.fromCharCode((c >> 6) | 192);\n utfstring += String.fromCharCode((c & 63) | 128);\n }\n else {\n utfstring += String.fromCharCode((c >> 12) | 224);\n utfstring += String.fromCharCode(((c >> 6) & 63) | 128);\n utfstring += String.fromCharCode((c & 63) | 128);\n }\n\n }\n return utfstring;\n },\n\n /**\n * Truncates a string without breaking words.\n *\n * @method shortString\n * @param {String} str String to truncate\n * @param {Number} n Number of chars of the short string\n * @return {String} \n * @public\n * @static\n * @sample Ink_Util_String_shortString.html \n */\n shortString: function(str,n) {\n var words = str.split(' ');\n var resultstr = '';\n for(var i = 0; i < words.length; i++ ){\n if((resultstr + words[i] + ' ').length>=n){\n resultstr += '…';\n break;\n }\n resultstr += words[i] + ' ';\n }\n return resultstr;\n },\n\n /**\n * Truncates a string, breaking words and adding ... at the end.\n *\n * @method truncateString\n * @param {String} str String to truncate\n * @param {Number} length Limit for the returned string, ellipsis included.\n * @return {String} Truncated String\n * @public\n * @static\n * @sample Ink_Util_String_truncateString.html \n */\n truncateString: function(str, length) {\n if(str.length - 1 > length) {\n return str.substr(0, length - 1) + \"\\u2026\";\n } else {\n return str;\n }\n },\n\n /**\n * Decodes a string from UTF-8.\n *\n * @method utf8Decode\n * @param {String} string String to be decoded\n * @return {String} Decoded string\n * @public\n * @static\n */\n utf8Decode: function(utfstring) {\n /*jshint bitwise:false*/\n var string = \"\";\n var i = 0, c = 0, c2 = 0, c3 = 0;\n\n while ( i < utfstring.length ) {\n\n c = utfstring.charCodeAt(i);\n\n if (c < 128) {\n string += String.fromCharCode(c);\n i++;\n }\n else if((c > 191) && (c < 224)) {\n c2 = utfstring.charCodeAt(i+1);\n string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));\n i += 2;\n }\n else {\n c2 = utfstring.charCodeAt(i+1);\n c3 = utfstring.charCodeAt(i+2);\n string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));\n i += 3;\n }\n\n }\n return string;\n },\n\n /**\n * Removes all accented characters from a string.\n *\n * @method removeAccentedChars\n * @param {String} string String to remove accents from\n * @return {String} String without accented chars\n * @public\n * @static\n * @sample Ink_Util_String_removeAccentedChars.html \n */\n removeAccentedChars: function(string)\n {\n var newString = string;\n var re = false;\n for (var i = 0; i < InkUtilString._accentedChars.length; i++) {\n re = new RegExp(InkUtilString._accentedChars[i], \"gm\");\n newString = newString.replace(re, '' + InkUtilString._accentedRemovedChars[i] + '');\n }\n return newString;\n },\n\n /**\n * Count the number of occurrences of a specific needle in a haystack\n *\n * @method substrCount\n * @param {String} haystack String to search in\n * @param {String} needle String to search for\n * @return {Number} Number of occurrences\n * @public\n * @static\n * @sample Ink_Util_String_substrCount.html \n */\n substrCount: function(haystack,needle)\n {\n return haystack ? haystack.split(needle).length - 1 : 0;\n },\n\n /**\n * Eval a JSON - We recommend you Ink.Util.Json\n *\n * @method evalJSON\n * @param {String} strJSON JSON string to eval\n * @param {Boolean} sanitize Flag to sanitize input\n * @return {Object} JS Object\n * @public\n * @static\n */\n evalJSON: function(strJSON, sanitize) {\n /* jshint evil:true */\n if( (typeof sanitize === 'undefined' || sanitize === null) || InkUtilString.isJSON(strJSON)) {\n try {\n if(typeof(JSON) !== \"undefined\" && typeof(JSON.parse) !== 'undefined'){\n return JSON.parse(strJSON);\n }\n return eval('('+strJSON+')');\n } catch(e) {\n throw new Error('ERROR: Bad JSON string...');\n }\n }\n },\n\n /**\n * Checks if a string is a valid JSON object (string encoded)\n *\n * @method isJSON \n * @param {String} str String to check\n * @return {Boolean}\n * @public\n * @static\n */\n isJSON: function(str)\n {\n str = str.replace(/\\\\./g, '@').replace(/\"[^\"\\\\\\n\\r]*\"/g, '');\n return (/^[,:{}\\[\\]0-9.\\-+Eaeflnr-u \\n\\r\\t]*$/).test(str);\n },\n\n /**\n * Escapes unsafe html chars as HTML entities\n *\n * @method htmlEscapeUnsafe\n * @param {String} str String to escape\n * @return {String} Escaped string\n * @public\n * @static\n * @sample Ink_Util_String_htmlEscapeUnsafe.html \n */\n htmlEscapeUnsafe: function(str){\n var chars = InkUtilString._htmlUnsafeChars;\n return str !== null ? String(str).replace(/[<>&'\"]/g,function(c){return chars[c];}) : str;\n },\n\n /**\n * Normalizes whitespace in string.\n * String is trimmed and sequences of whitespaces are collapsed.\n *\n * @method normalizeWhitespace\n * @param {String} str String to normalize\n * @return {String} Normalized string\n * @public\n * @static\n * @sample Ink_Util_String_normalizeWhitespace.html \n */\n normalizeWhitespace: function(str){\n return str !== null ? InkUtilString.trim(String(str).replace(/\\s+/g,' ')) : str;\n },\n\n /**\n * Converts string to unicode.\n *\n * @method toUnicode\n * @param {String} str String to convert\n * @return {String} Unicoded String\n * @public\n * @static\n * @sample Ink_Util_String_toUnicode.html \n */\n toUnicode: function(str) {\n if (typeof str === 'string') {\n var unicodeString = '';\n var inInt = false;\n var theUnicode = false;\n var total = str.length;\n var i=0;\n\n while(i < total)\n {\n inInt = str.charCodeAt(i);\n if( (inInt >= 32 && inInt <= 126) ||\n inInt === 8 ||\n inInt === 9 ||\n inInt === 10 ||\n inInt === 12 ||\n inInt === 13 ||\n inInt === 32 ||\n inInt === 34 ||\n inInt === 47 ||\n inInt === 58 ||\n inInt === 92) {\n\n /*\n if(inInt == 34 || inInt == 92 || inInt == 47) {\n theUnicode = '\\\\'+str.charAt(i);\n } else {\n }\n */\n if(inInt === 8) {\n theUnicode = '\\\\b';\n } else if(inInt === 9) {\n theUnicode = '\\\\t';\n } else if(inInt === 10) {\n theUnicode = '\\\\n';\n } else if(inInt === 12) {\n theUnicode = '\\\\f';\n } else if(inInt === 13) {\n theUnicode = '\\\\r';\n } else {\n theUnicode = str.charAt(i);\n }\n } else {\n theUnicode = str.charCodeAt(i).toString(16)+''.toUpperCase();\n while (theUnicode.length < 4) {\n theUnicode = '0' + theUnicode;\n }\n theUnicode = '\\\\u' + theUnicode;\n }\n unicodeString += theUnicode;\n\n i++;\n }\n return unicodeString;\n }\n },\n\n /**\n * Escapes a unicode character.\n *\n * @method escape\n * @param {String} c Character to escape\n * @return {String} Escaped character. Returns \\xXX if hex smaller than 0x100, otherwise \\uXXXX\n * @public\n * @static\n * @sample Ink_Util_String_escape.html \n */\n escape: function(c) {\n var hex = (c).charCodeAt(0).toString(16).split('');\n if (hex.length < 3) {\n while (hex.length < 2) { hex.unshift('0'); }\n hex.unshift('x');\n }\n else {\n while (hex.length < 4) { hex.unshift('0'); }\n hex.unshift('u');\n }\n\n hex.unshift('\\\\');\n return hex.join('');\n },\n\n /**\n * Unescapes a unicode character escape sequence\n *\n * @method unescape\n * @param {String} es Escape sequence\n * @return {String} String un-unicoded\n * @public\n * @static\n * @sample Ink_Util_String_unescape.html \n */\n unescape: function(es) {\n var idx = es.lastIndexOf('0');\n idx = idx === -1 ? 2 : Math.min(idx, 2);\n //console.log(idx);\n var hexNum = es.substring(idx);\n //console.log(hexNum);\n var num = parseInt(hexNum, 16);\n return String.fromCharCode(num);\n },\n\n /**\n * Escapes a string to unicode characters\n *\n * @method escapeText\n * @param {String} txt \n * @param {Array} [whiteList] Whitelist of characters\n * @return {String} String escaped to Unicode\n * @public\n * @static\n * @sample Ink_Util_String_escapeText.html \n */\n escapeText: function(txt, whiteList) {\n if (whiteList === undefined) {\n whiteList = ['[', ']', '\\'', ','];\n }\n var txt2 = [];\n var c, C;\n for (var i = 0, f = txt.length; i < f; ++i) {\n c = txt[i];\n C = c.charCodeAt(0);\n if (C < 32 || C > 126 && whiteList.indexOf(c) === -1) {\n c = InkUtilString.escape(c);\n }\n txt2.push(c);\n }\n return txt2.join('');\n },\n\n /**\n * Regex to check escaped strings\n *\n * @property escapedCharRegex\n * @type {Regex}\n * @public\n * @readOnly\n * @static\n */\n escapedCharRegex: /(\\\\x[0-9a-fA-F]{2})|(\\\\u[0-9a-fA-F]{4})/g,\n\n /**\n * Unescapes a string\n *\n * @method unescapeText\n * @param {String} txt\n * @return {String} Unescaped string\n * @public\n * @static\n * @sample Ink_Util_String_unescapeText.html \n */\n unescapeText: function(txt) {\n /*jshint boss:true */\n var m;\n while (m = InkUtilString.escapedCharRegex.exec(txt)) {\n m = m[0];\n txt = txt.replace(m, InkUtilString.unescape(m));\n InkUtilString.escapedCharRegex.lastIndex = 0;\n }\n return txt;\n },\n\n /**\n * Compares two strings.\n *\n * @method strcmp\n * @param {String} str1 First String\n * @param {String} str2 Second String\n * @return {Number}\n * @public\n * @static\n * @sample Ink_Util_String_strcmp.html \n */\n strcmp: function(str1, str2) {\n return ((str1 === str2) ? 0 : ((str1 > str2) ? 1 : -1));\n },\n\n /**\n * Splits a string into smaller chunks\n *\n * @method packetize\n * @param {String} str String to divide\n * @param {Number} maxLen Maximum chunk size (in characters)\n * @return {Array} Chunks of the original string\n * @public\n * @static\n * @sample Ink_Util_String_packetize.html \n */\n packetize: function(str, maxLen) {\n var len = str.length;\n var parts = new Array( Math.ceil(len / maxLen) );\n var chars = str.split('');\n var sz, i = 0;\n while (len) {\n sz = Math.min(maxLen, len);\n parts[i++] = chars.splice(0, sz).join('');\n len -= sz;\n }\n return parts;\n }\n };\n\n return InkUtilString;\n\n});\n","/**\n * URL Utilities\n * @module Ink.Util.Url_1\n * @version 1\n */\n\nInk.createModule('Ink.Util.Url', '1', [], function() {\n\n 'use strict';\n\n /**\n * @namespace Ink.Util.Url_1\n */\n var Url = {\n\n /**\n * Auxiliary string for encoding\n *\n * @property _keyStr\n * @type {String}\n * @readOnly\n * @private\n */\n _keyStr : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',\n\n\n /**\n * Gets URL of current page\n *\n * @method getUrl\n * @return Current URL\n * @public\n * @static\n * @sample Ink_Util_Url_getUrl.html \n */\n getUrl: function()\n {\n return window.location.href;\n },\n\n /**\n * Generates an URL string.\n *\n * @method genQueryString\n * @param {String} uri Base URL\n * @param {Object} params Object to transform to query string\n * @return {String} URI with query string set\n * @public\n * @static\n * @sample Ink_Util_Url_genQueryString.html \n */\n genQueryString: function(uri, params) {\n var hasQuestionMark = uri.indexOf('?') !== -1;\n var sep, pKey, pValue, parts = [uri];\n\n for (pKey in params) {\n if (params.hasOwnProperty(pKey)) {\n if (!hasQuestionMark) {\n sep = '?';\n hasQuestionMark = true;\n } else {\n sep = '&';\n }\n pValue = params[pKey];\n if (typeof pValue !== 'number' && !pValue) {\n pValue = '';\n }\n parts = parts.concat([sep, encodeURIComponent(pKey), '=', encodeURIComponent(pValue)]);\n }\n }\n\n return parts.join('');\n },\n\n /**\n * Gets an object from an URL encoded string.\n *\n * @method getQueryString\n * @param {String} [str] URL String. When not specified it uses the current URL.\n * @return {Object} Key-Value pair object\n * @public\n * @static\n * @sample Ink_Util_Url_getQueryString.html \n */\n getQueryString: function(str)\n {\n var url;\n if(str && typeof(str) !== 'undefined') {\n url = str;\n } else {\n url = this.getUrl();\n }\n var aParams = {};\n if(url.match(/\\?(.+)/i)) {\n var queryStr = url.replace(/^(.*)\\?([^\\#]+)(\\#(.*))?/g, \"$2\");\n if(queryStr.length > 0) {\n var aQueryStr = queryStr.split(/[;&]/);\n for(var i=0; i < aQueryStr.length; i++) {\n var pairVar = aQueryStr[i].split('=');\n aParams[decodeURIComponent(pairVar[0])] = (typeof(pairVar[1]) !== 'undefined' && pairVar[1]) ? decodeURIComponent(pairVar[1]) : false;\n }\n }\n }\n return aParams;\n },\n\n /**\n * Gets the URL hash value\n *\n * @method getAnchor\n * @param {String} [str] URL String. Defaults to current page URL.\n * @return {String|Boolean} Hash in the URL. If there's no hash, returns false.\n * @public\n * @static\n * @sample Ink_Util_Url_getAnchor.html \n */\n getAnchor: function(str)\n {\n var url;\n if(str && typeof(str) !== 'undefined') {\n url = str;\n } else {\n url = this.getUrl();\n }\n var anchor = false;\n if(url.match(/#(.+)/)) {\n anchor = url.replace(/([^#]+)#(.*)/, \"$2\");\n }\n return anchor;\n },\n\n /**\n * Gets the anchor string of an URL\n *\n * @method getAnchorString\n * @param {String} [string] URL to parse. Defaults to current URL.\n * @return {Object} Key-value pair object of the URL's hashtag 'variables'\n * @public\n * @static\n * @sample Ink_Util_Url_getAnchorString.html \n */\n getAnchorString: function(string)\n {\n var url;\n if(string && typeof(string) !== 'undefined') {\n url = string;\n } else {\n url = this.getUrl();\n }\n var aParams = {};\n if(url.match(/#(.+)/i)) {\n var anchorStr = url.replace(/^([^#]+)#(.*)?/g, \"$2\");\n if(anchorStr.length > 0) {\n var aAnchorStr = anchorStr.split(/[;&]/);\n for(var i=0; i < aAnchorStr.length; i++) {\n var pairVar = aAnchorStr[i].split('=');\n aParams[decodeURIComponent(pairVar[0])] = (typeof(pairVar[1]) !== 'undefined' && pairVar[1]) ? decodeURIComponent(pairVar[1]) : false;\n }\n }\n }\n return aParams;\n },\n\n\n /**\n * Parses URL string into URL parts\n *\n * @method parseUrl\n * @param {String} url URL to be parsed\n * @return {Object} Parsed URL as a key-value object.\n * @public\n * @static\n * @sample Ink_Util_Url_parseUrl.html \n */\n parseUrl: function(url) {\n var aURL = {};\n if(url && typeof url === 'string') {\n if(url.match(/^([^:]+):\\/\\//i)) {\n var re = /^([^:]+):\\/\\/([^\\/]*)\\/?([^\\?#]*)\\??([^#]*)#?(.*)/i;\n if(url.match(re)) {\n aURL.scheme = url.replace(re, \"$1\");\n aURL.host = url.replace(re, \"$2\");\n aURL.path = '/'+url.replace(re, \"$3\");\n aURL.query = url.replace(re, \"$4\") || false;\n aURL.fragment = url.replace(re, \"$5\") || false;\n }\n } else {\n var re1 = new RegExp(\"^([^\\\\?]+)\\\\?([^#]+)#(.*)\", \"i\");\n var re2 = new RegExp(\"^([^\\\\?]+)\\\\?([^#]+)#?\", \"i\");\n var re3 = new RegExp(\"^([^\\\\?]+)\\\\??\", \"i\");\n if(url.match(re1)) {\n aURL.scheme = false;\n aURL.host = false;\n aURL.path = url.replace(re1, \"$1\");\n aURL.query = url.replace(re1, \"$2\");\n aURL.fragment = url.replace(re1, \"$3\");\n } else if(url.match(re2)) {\n aURL.scheme = false;\n aURL.host = false;\n aURL.path = url.replace(re2, \"$1\");\n aURL.query = url.replace(re2, \"$2\");\n aURL.fragment = false;\n } else if(url.match(re3)) {\n aURL.scheme = false;\n aURL.host = false;\n aURL.path = url.replace(re3, \"$1\");\n aURL.query = false;\n aURL.fragment = false;\n }\n }\n if(aURL.host) {\n var regPort = /^(.*?)\\\\:(\\\\d+)$/i;\n // check for port\n if(aURL.host.match(regPort)) {\n var tmpHost1 = aURL.host;\n aURL.host = tmpHost1.replace(regPort, \"$1\");\n aURL.port = tmpHost1.replace(regPort, \"$2\");\n } else {\n aURL.port = false;\n }\n // check for user and pass\n if(aURL.host.match(/@/i)) {\n var tmpHost2 = aURL.host;\n aURL.host = tmpHost2.split('@')[1];\n var tmpUserPass = tmpHost2.split('@')[0];\n if(tmpUserPass.match(/\\:/)) {\n aURL.user = tmpUserPass.split(':')[0];\n aURL.pass = tmpUserPass.split(':')[1];\n } else {\n aURL.user = tmpUserPass;\n aURL.pass = false;\n }\n }\n }\n }\n return aURL;\n },\n\n /**\n * Formats an URL object into an URL string.\n *\n * @method format\n * @param urlObj Window.location, a.href, or parseUrl object to format\n * @return {String} Full URL.\n */\n format: function (urlObj) {\n var protocol = '';\n var host = '';\n var path = '';\n var frag = '';\n var query = '';\n\n if (typeof urlObj.protocol === 'string') {\n protocol = urlObj.protocol + '//'; // here it comes with the colon\n } else if (typeof urlObj.scheme === 'string') {\n protocol = urlObj.scheme + '://';\n }\n\n host = urlObj.host || urlObj.hostname || '';\n path = urlObj.path || '';\n\n if (typeof urlObj.query === 'string') {\n query = urlObj.query;\n } else if (typeof urlObj.search === 'string') {\n query = urlObj.search.replace(/^\\?/, '');\n }\n if (typeof urlObj.fragment === 'string') {\n frag = urlObj.fragment;\n } else if (typeof urlObj.hash === 'string') {\n frag = urlObj.hash.replace(/#$/, '');\n }\n\n return [\n protocol,\n host,\n path,\n query && '?' + query,\n frag && '#' + frag\n ].join('');\n },\n\n /**\n * Gets the last loaded script element\n *\n * @method currentScriptElement\n * @param {String} [match] String to match against the script src attribute\n * @return {DOMElement|Boolean} Returns the `script` DOM Element or false if unable to find it.\n * @public\n * @static\n * @sample Ink_Util_Url_currentScriptElement.html \n */\n currentScriptElement: function(match)\n {\n var aScripts = document.getElementsByTagName('script');\n if(typeof(match) === 'undefined') {\n if(aScripts.length > 0) {\n return aScripts[(aScripts.length - 1)];\n } else {\n return false;\n }\n } else {\n var curScript = false;\n var re = new RegExp(\"\"+match+\"\", \"i\");\n for(var i=0, total = aScripts.length; i < total; i++) {\n curScript = aScripts[i];\n if(re.test(curScript.src)) {\n return curScript;\n }\n }\n return false;\n }\n },\n\n \n /*\n base64Encode: function(string)\n {\n /**\n * --function {String} ?\n * --Convert a string to BASE 64\n * @param {String} string - string to convert\n * @return base64 encoded string\n *\n * \n if(!SAPO.Utility.String || typeof(SAPO.Utility.String) === 'undefined') {\n throw \"SAPO.Utility.Url.base64Encode depends of SAPO.Utility.String, which has not been referred.\";\n }\n\n var output = \"\";\n var chr1, chr2, chr3, enc1, enc2, enc3, enc4;\n var i = 0;\n\n var input = SAPO.Utility.String.utf8Encode(string);\n\n while (i < input.length) {\n\n chr1 = input.charCodeAt(i++);\n chr2 = input.charCodeAt(i++);\n chr3 = input.charCodeAt(i++);\n\n enc1 = chr1 >> 2;\n enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);\n enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);\n enc4 = chr3 & 63;\n\n if (isNaN(chr2)) {\n enc3 = enc4 = 64;\n } else if (isNaN(chr3)) {\n enc4 = 64;\n }\n\n output = output +\n this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +\n this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);\n }\n return output;\n },\n base64Decode: function(string)\n {\n * --function {String} ?\n * Decode a BASE 64 encoded string\n * --param {String} string base64 encoded string\n * --return string decoded\n if(!SAPO.Utility.String || typeof(SAPO.Utility.String) === 'undefined') {\n throw \"SAPO.Utility.Url.base64Decode depends of SAPO.Utility.String, which has not been referred.\";\n }\n\n var output = \"\";\n var chr1, chr2, chr3;\n var enc1, enc2, enc3, enc4;\n var i = 0;\n\n var input = string.replace(/[^A-Za-z0-9\\+\\/\\=]/g, \"\");\n\n while (i < input.length) {\n\n enc1 = this._keyStr.indexOf(input.charAt(i++));\n enc2 = this._keyStr.indexOf(input.charAt(i++));\n enc3 = this._keyStr.indexOf(input.charAt(i++));\n enc4 = this._keyStr.indexOf(input.charAt(i++));\n\n chr1 = (enc1 << 2) | (enc2 >> 4);\n chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);\n chr3 = ((enc3 & 3) << 6) | enc4;\n\n output = output + String.fromCharCode(chr1);\n\n if (enc3 !== 64) {\n output = output + String.fromCharCode(chr2);\n }\n if (enc4 !== 64) {\n output = output + String.fromCharCode(chr3);\n }\n }\n output = SAPO.Utility.String.utf8Decode(output);\n return output;\n },\n */\n\n\n /**\n * Debug function ?\n *\n * @method _debug\n * @private\n * @static\n */\n _debug: function() {}\n\n };\n\n return Url;\n\n});\n","/**\n * Validation Utilities\n * @module Ink.Util.Validator_1\n * @version 1\n */\n \nInk.createModule('Ink.Util.Validator', '1', [], function() {\n\n 'use strict';\n\n /**\n * @namespace Ink.Util.Validator_1 \n */\n var Validator = {\n\n /**\n * List of country codes avaible for the isPhone method\n *\n * @property _countryCodes\n * @type {Array}\n * @private\n * @static\n * @readOnly\n */\n _countryCodes : [\n 'AO',\n 'CV',\n 'MZ',\n 'PT'\n ],\n\n /**\n * International number for portugal\n *\n * @property _internacionalPT\n * @type {Number}\n * @private\n * @static\n * @readOnly\n *\n */\n _internacionalPT: 351,\n\n /**\n * List of all portuguese number prefixes\n *\n * @property _indicativosPT\n * @type {Object}\n * @private\n * @static\n * @readOnly\n *\n */\n _indicativosPT: {\n 21: 'lisboa',\n 22: 'porto',\n 231: 'mealhada',\n 232: 'viseu',\n 233: 'figueira da foz',\n 234: 'aveiro',\n 235: 'arganil',\n 236: 'pombal',\n 238: 'seia',\n 239: 'coimbra',\n 241: 'abrantes',\n 242: 'ponte de sôr',\n 243: 'santarém',\n 244: 'leiria',\n 245: 'portalegre',\n 249: 'torres novas',\n 251: 'valença',\n 252: 'vila nova de famalicão',\n 253: 'braga',\n 254: 'peso da régua',\n 255: 'penafiel',\n 256: 'são joão da madeira',\n 258: 'viana do castelo',\n 259: 'vila real',\n 261: 'torres vedras',\n 262: 'caldas da raínha',\n 263: 'vila franca de xira',\n 265: 'setúbal',\n 266: 'évora',\n 268: 'estremoz',\n 269: 'santiago do cacém',\n 271: 'guarda',\n 272: 'castelo branco',\n 273: 'bragança',\n 274: 'proença-a-nova',\n 275: 'covilhã',\n 276: 'chaves',\n 277: 'idanha-a-nova',\n 278: 'mirandela',\n 279: 'moncorvo',\n 281: 'tavira',\n 282: 'portimão',\n 283: 'odemira',\n 284: 'beja',\n 285: 'moura',\n 286: 'castro verde',\n 289: 'faro',\n 291: 'funchal, porto santo',\n 292: 'corvo, faial, flores, horta, pico',\n 295: 'angra do heroísmo, graciosa, são jorge, terceira',\n 296: 'ponta delgada, são miguel, santa maria',\n\n 91 : 'rede móvel 91 (Vodafone / Yorn)',\n 93 : 'rede móvel 93 (Optimus)',\n 96 : 'rede móvel 96 (TMN)',\n 92 : 'rede móvel 92 (TODOS)',\n //925 : 'rede móvel 925 (TMN 925)',\n //926 : 'rede móvel 926 (TMN 926)',\n //927 : 'rede móvel 927 (TMN 927)',\n //922 : 'rede móvel 922 (Phone-ix)',\n\n 707: 'número único',\n 760: 'número único',\n 800: 'número grátis',\n 808: 'chamada local',\n 30: 'voip'\n },\n /**\n * International number for Cabo Verde\n *\n * @property _internacionalCV\n * @type {Number}\n * @private\n * @static\n * @readOnly\n */\n _internacionalCV: 238,\n\n /**\n * List of all Cabo Verde number prefixes\n *\n * @property _indicativosCV\n * @type {Object}\n * @private\n * @static\n * @readOnly\n */\n _indicativosCV: {\n 2: 'fixo',\n 91: 'móvel 91',\n 95: 'móvel 95',\n 97: 'móvel 97',\n 98: 'móvel 98',\n 99: 'móvel 99'\n },\n /**\n * International number for Angola\n *\n * @property _internacionalAO\n * @type {Number}\n * @private\n * @static\n * @readOnly\n */\n _internacionalAO: 244,\n\n /**\n * List of all Angola number prefixes\n *\n * @property _indicativosAO\n * @type {Object}\n * @private\n * @static\n * @readOnly\n */\n _indicativosAO: {\n 2: 'fixo',\n 91: 'móvel 91',\n 92: 'móvel 92'\n },\n /**\n * International number for Mozambique\n *\n * @property _internacionalMZ\n * @type {Number}\n * @private\n * @static\n * @readOnly\n */\n _internacionalMZ: 258,\n\n /**\n * List of all Mozambique number prefixes\n *\n * @property _indicativosMZ\n * @type {Object}\n * @private\n * @static\n * @readOnly\n */\n _indicativosMZ: {\n 2: 'fixo',\n 82: 'móvel 82',\n 84: 'móvel 84'\n },\n\n /**\n * International number for Timor\n *\n * @property _internacionalTL\n * @type {Number}\n * @private\n * @static\n * @readOnly\n */\n _internacionalTL: 670,\n\n /**\n * List of all Timor number prefixes\n *\n * @property _indicativosTL\n * @type {Object}\n * @private\n * @static\n * @readOnly\n */\n _indicativosTL: {\n 3: 'fixo',\n 7: 'móvel 7'\n },\n\n /**\n * Regular expression groups for several groups of characters\n *\n * http://en.wikipedia.org/wiki/C0_Controls_and_Basic_Latin\n * http://en.wikipedia.org/wiki/Plane_%28Unicode%29#Basic_Multilingual_Plane\n * http://en.wikipedia.org/wiki/ISO_8859-1\n *\n * @property _characterGroups\n * @type {Object}\n * @private\n * @static\n * @readOnly\n */\n _characterGroups: {\n numbers: ['0-9'],\n asciiAlpha: ['a-zA-Z'],\n latin1Alpha: ['a-zA-Z', '\\u00C0-\\u00FF'],\n unicodeAlpha: ['a-zA-Z', '\\u00C0-\\u00FF', '\\u0100-\\u1FFF', '\\u2C00-\\uD7FF'],\n /* whitespace characters */\n space: [' '],\n dash: ['-'],\n underscore: ['_'],\n nicknamePunctuation: ['_.-'],\n\n singleLineWhitespace: ['\\t '],\n newline: ['\\n'],\n whitespace: ['\\t\\n\\u000B\\f\\r\\u00A0 '],\n\n asciiPunctuation: ['\\u0021-\\u002F', '\\u003A-\\u0040', '\\u005B-\\u0060', '\\u007B-\\u007E'],\n latin1Punctuation: ['\\u0021-\\u002F', '\\u003A-\\u0040', '\\u005B-\\u0060', '\\u007B-\\u007E', '\\u00A1-\\u00BF', '\\u00D7', '\\u00F7'],\n unicodePunctuation: ['\\u0021-\\u002F', '\\u003A-\\u0040', '\\u005B-\\u0060', '\\u007B-\\u007E', '\\u00A1-\\u00BF', '\\u00D7', '\\u00F7', '\\u2000-\\u206F', '\\u2E00-\\u2E7F', '\\u3000-\\u303F']\n },\n\n /**\n * Creates a regular expression for several character groups.\n *\n * @method createRegExp\n *\n * @param Groups* {Object}\n * Groups to build regular expressions for. Possible keys are:\n *\n * - **numbers**: 0-9\n * - **asciiAlpha**: a-z, A-Z\n * - **latin1Alpha**: asciiAlpha, plus printable characters in latin-1\n * - **unicodeAlpha**: unicode alphanumeric characters.\n * - **space**: ' ', the space character.\n * - **dash**: dash character.\n * - **underscore**: underscore character.\n * - **nicknamePunctuation**: dash, dot, underscore\n * - **singleLineWhitespace**: space and tab (whitespace which only spans one line).\n * - **newline**: newline character ('\\n')\n * - **whitespace**: whitespace characters in the ASCII character set.\n * - **asciiPunctuation**: punctuation characters in the ASCII character set.\n * - **latin1Punctuation**: punctuation characters in latin-1.\n * - **unicodePunctuation**: punctuation characters in unicode.\n *\n */\n createRegExp: function (groups) {\n var re = '^[';\n for (var key in groups) if (groups.hasOwnProperty(key)) {\n if (!(key in Validator._characterGroups)) {\n throw new Error('group ' + key + ' is not a valid character group');\n } else if (groups[key]) {\n re += Validator._characterGroups[key].join('');\n }\n }\n if (re === '^[') {\n // No changes\n return new RegExp('$^'); // match nothing\n }\n return new RegExp(re + ']*?$');\n },\n\n /**\n * Checks if a field has the required groups.\n *\n * @method checkCharacterGroups\n * @param {String} s The validation string\n * @param {Object} [groups]={} What groups are included. See createRegexp\n * @sample Ink_Util_Validator_checkCharacterGroups.html \n */\n checkCharacterGroups: function (s, groups) {\n return Validator.createRegExp(groups).test(s);\n },\n\n /**\n * Checks if a field contains unicode printable characters.\n *\n * @method unicode\n * @param {String} s The validation string\n * @param {Object} [options]={} Optional configuration object. See createRegexp\n */\n unicode: function (s, options) {\n return Validator.checkCharacterGroups(s, Ink.extendObj({\n unicodeAlpha: true}, options));\n },\n\n /**\n * Checks if a field only contains latin-1 alphanumeric characters. \n * Takes options for allowing singleline whitespace, cross-line whitespace and punctuation.\n *\n * @method latin1\n *\n * @param {String} s The validation string\n * @param {Object} [options]={} Optional configuration object. See createRegexp\n * @sample Ink_Util_Validator_latin1.html \n */\n latin1: function (s, options) {\n return Validator.checkCharacterGroups(s, Ink.extendObj({\n latin1Alpha: true}, options));\n },\n\n /**\n * Checks if a field only contains only ASCII alphanumeric characters. \n * Takes options for allowing singleline whitespace, cross-line whitespace and punctuation.\n *\n * @method ascii\n *\n * @param {String} s The validation string\n * @param {Object} [options]={} Optional configuration object. See createRegexp\n * @sample Ink_Util_Validator_ascii.html \n */\n ascii: function (s, options) {\n return Validator.checkCharacterGroups(s, Ink.extendObj({\n asciiAlpha: true}, options));\n },\n\n /**\n * Checks if a number is a valid\n *\n * @method number\n * @param {String} numb The number\n * @param {Object} [options] Further options\n * @param [options.decimalSep]='.' Allow decimal separator.\n * @param [options.thousandSep]=\",\" Strip this character from the number.\n * @param [options.negative]=false Allow negative numbers.\n * @param [options.decimalPlaces]=null Maximum number of decimal places. Use `0` for an integer number.\n * @param [options.max]=null Maximum number\n * @param [options.min]=null Minimum number\n * @param [options.returnNumber]=false When this option is true, return the number itself when the value is valid.\n * @sample Ink_Util_Validator_number.html \n */\n number: function (numb, inOptions) {\n numb = numb + '';\n var options = Ink.extendObj({\n decimalSep: '.',\n thousandSep: '',\n negative: true,\n decimalPlaces: null,\n maxDigits: null,\n max: null,\n min: null,\n returnNumber: false\n }, inOptions || {});\n // smart recursion thing sets up aliases for options.\n if (options.thousandSep) {\n numb = numb.replace(new RegExp('\\\\' + options.thousandSep, 'g'), '');\n options.thousandSep = '';\n return Validator.number(numb, options);\n }\n if (options.negative === false) {\n options.min = 0;\n options.negative = true;\n return Validator.number(numb, options);\n }\n if (options.decimalSep !== '.') {\n numb = numb.replace(new RegExp('\\\\' + options.decimalSep, 'g'), '.');\n }\n\n if (!/^(-)?(\\d+)?(\\.\\d+)?$/.test(numb) || numb === '') {\n return false; // forbidden character found\n }\n \n var split;\n if (options.decimalSep && numb.indexOf(options.decimalSep) !== -1) {\n split = numb.split(options.decimalSep);\n if (options.decimalPlaces !== null &&\n split[1].length > options.decimalPlaces) {\n return false;\n }\n } else {\n split = ['' + numb, ''];\n }\n \n if (options.maxDigits!== null) {\n if (split[0].replace(/-/g, '').length > options.maxDigits) {\n return split;\n }\n }\n \n // Now look at the actual float\n var ret = parseFloat(numb);\n \n if (options.maxExcl !== null && ret >= options.maxExcl ||\n options.minExcl !== null && ret <= options.minExcl) {\n return false;\n }\n if (options.max !== null && ret > options.max ||\n options.min !== null && ret < options.min) {\n return false;\n }\n \n if (options.returnNumber) {\n return ret;\n } else {\n return true;\n }\n },\n\n /**\n * Checks if a year is Leap \"Bissexto\"\n *\n * @method _isLeapYear\n * @param {Number} year Year to be checked\n * @return {Boolean} True if it is a leap year.\n * @private\n * @static\n * @example\n * Ink.requireModules(['Ink.Util.Validator_1'], function( InkValidator ){\n * console.log( InkValidator._isLeapYear( 2004 ) ); // Result: true\n * console.log( InkValidator._isLeapYear( 2006 ) ); // Result: false\n * });\n */\n _isLeapYear: function(year){\n\n var yearRegExp = /^\\d{4}$/;\n\n if(yearRegExp.test(year)){\n return ((year%4) ? false: ((year%100) ? true : ((year%400)? false : true)) );\n }\n\n return false;\n },\n\n /**\n * Object with the date formats available for validation\n *\n * @property _dateParsers\n * @type {Object}\n * @private\n * @static\n * @readOnly\n */\n _dateParsers: {\n 'yyyy-mm-dd': {day:5, month:3, year:1, sep: '-', parser: /^(\\d{4})(\\-)(\\d{1,2})(\\-)(\\d{1,2})$/},\n 'yyyy/mm/dd': {day:5, month:3, year:1, sep: '/', parser: /^(\\d{4})(\\/)(\\d{1,2})(\\/)(\\d{1,2})$/},\n 'yy-mm-dd': {day:5, month:3, year:1, sep: '-', parser: /^(\\d{2})(\\-)(\\d{1,2})(\\-)(\\d{1,2})$/},\n 'yy/mm/dd': {day:5, month:3, year:1, sep: '/', parser: /^(\\d{2})(\\/)(\\d{1,2})(\\/)(\\d{1,2})$/},\n 'dd-mm-yyyy': {day:1, month:3, year:5, sep: '-', parser: /^(\\d{1,2})(\\-)(\\d{1,2})(\\-)(\\d{4})$/},\n 'dd/mm/yyyy': {day:1, month:3, year:5, sep: '/', parser: /^(\\d{1,2})(\\/)(\\d{1,2})(\\/)(\\d{4})$/},\n 'dd-mm-yy': {day:1, month:3, year:5, sep: '-', parser: /^(\\d{1,2})(\\-)(\\d{1,2})(\\-)(\\d{2})$/},\n 'dd/mm/yy': {day:1, month:3, year:5, sep: '/', parser: /^(\\d{1,2})(\\/)(\\d{1,2})(\\/)(\\d{2})$/}\n },\n\n /**\n * Gets the number of days in a given month of a given year\n *\n * @method _daysInMonth\n * @param {Number} _m Month (1 to 12)\n * @param {Number} _y Year\n * @return {Number} Returns the number of days in a given month of a given year\n * @private\n * @static\n * @example\n * Ink.requireModules(['Ink.Util.Validator_1'], function( InkValidator ){\n * console.log( InkValidator._daysInMonth( 2, 2004 ) ); // Result: 29\n * console.log( InkValidator._daysInMonth( 2, 2006 ) ); // Result: 28\n * });\n */\n _daysInMonth: function(_m,_y){\n var nDays=0;\n\n _m = parseInt(_m, 10);\n _y = parseInt(_y, 10);\n\n if(_m===1 || _m===3 || _m===5 || _m===7 || _m===8 || _m===10 || _m===12) {\n nDays= 31;\n } else if ( _m===4 || _m===6 || _m===9 || _m===11) {\n nDays = 30;\n } else if (_m===2) {\n if((_y%400===0) || (_y%4===0 && _y%100!==0)) {\n nDays = 29;\n } else {\n nDays = 28;\n }\n }\n\n return nDays;\n },\n\n\n\n /**\n * Checks if a date is valid\n *\n * @method _isValidDate\n * @param {Number} year\n * @param {Number} month\n * @param {Number} day\n * @return {Boolean} True if valid\n * @private\n * @static\n * @example\n * Ink.requireModules(['Ink.Util.Validator_1'], function( InkValidator ){\n * console.log( InkValidator._isValidDate( 2004, 2, 29 ) ); // Result: true\n * console.log( InkValidator._isValidDate( 2006, 2, 29 ) ); // Result: false\n * });\n */\n _isValidDate: function(year, month, day){\n\n var yearRegExp = /^\\d{4}$/;\n var validOneOrTwo = /^\\d{1,2}$/;\n if(yearRegExp.test(year) && validOneOrTwo.test(month) && validOneOrTwo.test(day)){\n if(month>=1 && month<=12 && day>=1 && this._daysInMonth(month,year)>=day){\n return true;\n }\n }\n\n return false;\n },\n\n /**\n * Checks if an email is valid\n *\n * @method mail\n * @param {String} email\n * @return {Boolean} True if it's valid\n * @public\n * @static\n * @sample Ink_Util_Validator_mail.html \n */\n email: function(email)\n {\n var emailValido = new RegExp(\"^[_a-z0-9-]+((\\\\.|\\\\+)[_a-z0-9-]+)*@([\\\\w]*-?[\\\\w]*\\\\.)+[a-z]{2,4}$\", \"i\");\n if(!emailValido.test(email)) {\n return false;\n } else {\n return true;\n }\n },\n\n /**\n * Deprecated. Alias for email(). Use it instead.\n *\n * @method mail\n * @public\n * @static\n * @private\n */\n mail: function (mail) { return Validator.email(mail); },\n\n /**\n * Checks if an url is valid\n *\n * @method url\n * @param {String} url URL to be checked\n * @param {Boolean} [full] If true, validates a full URL (one that should start with 'http')\n * @return {Boolean} True if valid\n * @public\n * @static\n * @sample Ink_Util_Validator_url.html \n */\n url: function(url, full)\n {\n if(typeof full === \"undefined\" || full === false) {\n var reHTTP = new RegExp(\"(^(http\\\\:\\\\/\\\\/|https\\\\:\\\\/\\\\/)(.+))\", \"i\");\n if(reHTTP.test(url) === false) {\n url = 'http://'+url;\n }\n }\n\n var reUrl = new RegExp(\"^(http:\\\\/\\\\/|https:\\\\/\\\\/)([\\\\w]*(-?[\\\\w]*)*\\\\.)+[a-z]{2,4}\", \"i\");\n if(reUrl.test(url) === false) {\n return false;\n } else {\n return true;\n }\n },\n\n /**\n * Checks if a phone is valid in Portugal\n *\n * @method isPTPhone\n * @param {Number} phone Phone number to be checked\n * @return {Boolean} True if it's a valid Portuguese Phone\n * @public\n * @static\n * @sample Ink_Util_Validator_isPTPhone.html\n */\n isPTPhone: function(phone)\n {\n\n phone = phone.toString();\n var aInd = [];\n for(var i in this._indicativosPT) {\n if(typeof(this._indicativosPT[i]) === 'string') {\n aInd.push(i);\n }\n }\n var strInd = aInd.join('|');\n\n var re351 = /^(00351|\\+351)/;\n if(re351.test(phone)) {\n phone = phone.replace(re351, \"\");\n }\n\n var reSpecialChars = /(\\s|\\-|\\.)+/g;\n phone = phone.replace(reSpecialChars, '');\n //var reInt = new RegExp(\"\\\\d\", \"i\");\n var reInt = /[\\d]{9}/i;\n if(phone.length === 9 && reInt.test(phone)) {\n var reValid = new RegExp(\"^(\"+strInd+\")\");\n if(reValid.test(phone)) {\n return true;\n }\n }\n\n return false;\n },\n\n /**\n * Alias function for isPTPhone\n *\n * @method isPortuguesePhone\n * @param {Number} phone Phone number to be checked\n * @return {Boolean} True if it's a valid Portuguese Phone\n * @public\n * @static\n */\n isPortuguesePhone: function(phone)\n {\n return this.isPTPhone(phone);\n },\n\n /**\n * Checks if a phone is valid in Cabo Verde\n *\n * @method isCVPhone\n * @param {Number} phone Phone number to be checked\n * @return {Boolean} True if it's a valid Cape Verdean Phone\n * @public\n * @static\n * @sample Ink_Util_Validator_isCVPhone.html \n */\n isCVPhone: function(phone)\n {\n phone = phone.toString();\n var aInd = [];\n for(var i in this._indicativosCV) {\n if(typeof(this._indicativosCV[i]) === 'string') {\n aInd.push(i);\n }\n }\n var strInd = aInd.join('|');\n\n var re238 = /^(00238|\\+238)/;\n if(re238.test(phone)) {\n phone = phone.replace(re238, \"\");\n }\n\n var reSpecialChars = /(\\s|\\-|\\.)+/g;\n phone = phone.replace(reSpecialChars, '');\n //var reInt = new RegExp(\"\\\\d\", \"i\");\n var reInt = /[\\d]{7}/i;\n if(phone.length === 7 && reInt.test(phone)) {\n var reValid = new RegExp(\"^(\"+strInd+\")\");\n if(reValid.test(phone)) {\n return true;\n }\n }\n\n return false;\n },\n\n /**\n * Checks if a phone is valid in Angola\n *\n * @method isAOPhone\n * @param {Number} phone Phone number to be checked\n * @return {Boolean} True if it's a valid Angolan Phone\n * @public\n * @static\n * @sample Ink_Util_Validator_isAOPhone.html \n */\n isAOPhone: function(phone)\n {\n\n phone = phone.toString();\n var aInd = [];\n for(var i in this._indicativosAO) {\n if(typeof(this._indicativosAO[i]) === 'string') {\n aInd.push(i);\n }\n }\n var strInd = aInd.join('|');\n\n var re244 = /^(00244|\\+244)/;\n if(re244.test(phone)) {\n phone = phone.replace(re244, \"\");\n }\n\n var reSpecialChars = /(\\s|\\-|\\.)+/g;\n phone = phone.replace(reSpecialChars, '');\n //var reInt = new RegExp(\"\\\\d\", \"i\");\n var reInt = /[\\d]{9}/i;\n if(phone.length === 9 && reInt.test(phone)) {\n var reValid = new RegExp(\"^(\"+strInd+\")\");\n if(reValid.test(phone)) {\n return true;\n }\n }\n\n return false;\n },\n\n /**\n * Checks if a phone is valid in Mozambique\n *\n * @method isMZPhone\n * @param {Number} phone Phone number to be checked\n * @return {Boolean} True if it's a valid Mozambican Phone\n * @public\n * @static\n * @sample Ink_Util_Validator_isMZPhone.html \n */\n isMZPhone: function(phone)\n {\n\n phone = phone.toString();\n var aInd = [];\n for(var i in this._indicativosMZ) {\n if(typeof(this._indicativosMZ[i]) === 'string') {\n aInd.push(i);\n }\n }\n var strInd = aInd.join('|');\n var re258 = /^(00258|\\+258)/;\n if(re258.test(phone)) {\n phone = phone.replace(re258, \"\");\n }\n\n var reSpecialChars = /(\\s|\\-|\\.)+/g;\n phone = phone.replace(reSpecialChars, '');\n //var reInt = new RegExp(\"\\\\d\", \"i\");\n var reInt = /[\\d]{8,9}/i;\n if((phone.length === 9 || phone.length === 8) && reInt.test(phone)) {\n var reValid = new RegExp(\"^(\"+strInd+\")\");\n if(reValid.test(phone)) {\n if(phone.indexOf('2') === 0 && phone.length === 8) {\n return true;\n } else if(phone.indexOf('8') === 0 && phone.length === 9) {\n return true;\n }\n }\n }\n\n return false;\n },\n\n /**\n * Checks if a phone is valid in Timor\n *\n * @method isTLPhone\n * @param {Number} phone Phone number to be checked\n * @return {Boolean} True if it's a valid phone from Timor-Leste\n * @public\n * @static\n * @sample Ink_Util_Validator_isTLPhone.html \n */\n isTLPhone: function(phone)\n {\n\n phone = phone.toString();\n var aInd = [];\n for(var i in this._indicativosTL) {\n if(typeof(this._indicativosTL[i]) === 'string') {\n aInd.push(i);\n }\n }\n var strInd = aInd.join('|');\n var re670 = /^(00670|\\+670)/;\n if(re670.test(phone)) {\n phone = phone.replace(re670, \"\");\n }\n\n\n var reSpecialChars = /(\\s|\\-|\\.)+/g;\n phone = phone.replace(reSpecialChars, '');\n //var reInt = new RegExp(\"\\\\d\", \"i\");\n var reInt = /[\\d]{7}/i;\n if(phone.length === 7 && reInt.test(phone)) {\n var reValid = new RegExp(\"^(\"+strInd+\")\");\n if(reValid.test(phone)) {\n return true;\n }\n }\n\n return false;\n },\n\n /**\n * Checks if a number is a phone number.\n * This method validates the number in all country codes available the ones set in the second param\n *\n * @method isPhone\n * @param {String} phone Phone number to validate\n * @param {String|Array} [countryCode] Country code or array of countries to validate\n * @return {Boolean} True if it's a valid phone in any country available\n * @public\n * @static\n * @sample Ink_Util_Validator_isPhone.html\n */\n isPhone: function(){\n var index;\n\n if(arguments.length===0){\n return false;\n }\n\n var phone = arguments[0];\n\n if(arguments.length>1){\n if(arguments[1].constructor === Array){\n var func;\n for(index=0; index= 0 && match[i-1] <= 100){\n valid = true;\n } else {\n return false;\n }\n }\n // check 0 to 255 values\n if(i===1 || i===3 || i===5 && (typeof match[i+1] === \"undefined\" || match[i+1] === \"\")){\n if(typeof match[i] !== \"undefined\" && match[i] >= 0 && match[i] <= 255){\n valid = true;\n } else {\n return false;\n }\n }\n }\n }\n\n // hsl range check\n if((match = hsl.exec(str)) !== null || (match = hsla.exec(str)) !== null){\n i = match.length;\n while(i--){\n // check percentage values\n if(i===3 || i===5){\n if(typeof match[i-1] !== \"undefined\" && typeof match[i] !== \"undefined\" && match[i] !== \"\" &&\n match[i-1] >= 0 && match[i-1] <= 100){\n valid = true;\n } else {\n return false;\n }\n }\n // check 0 to 360 value\n if(i===1){\n if(typeof match[i] !== \"undefined\" && match[i] >= 0 && match[i] <= 360){\n valid = true;\n } else {\n return false;\n }\n }\n }\n }\n\n return valid;\n },\n\n /**\n * Checks if the value is a valid IP. \n *\n * @method isIP\n * @param {String} value Value to be checked\n * @param {String} ipType Type of IP to be validated. The values are: ipv4, ipv6. By default is ipv4.\n * @return {Boolean} True if the value is a valid IP address. False if not.\n * @sample Ink_Util_Validator_isIP.html \n */\n isIP: function( value, ipType ){\n if( typeof value !== 'string' ){\n return false;\n }\n\n ipType = (ipType || 'ipv4').toLowerCase();\n\n switch( ipType ){\n case 'ipv4':\n return (/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/).test(value);\n case 'ipv6':\n return (/^\\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?\\s*$/).test(value);\n default:\n return false;\n }\n },\n\n /**\n * Credit Card specifications, to be used in the credit card verification.\n *\n * @property _creditCardSpecs\n * @type {Object}\n * @private\n */\n _creditCardSpecs: {\n 'default': {\n 'length': '13,14,15,16,17,18,19',\n 'prefix': /^.+/,\n 'luhn': true\n },\n\n 'american express': {\n 'length': '15',\n 'prefix': /^3[47]/,\n 'luhn' : true\n },\n\n 'diners club': {\n 'length': '14,16',\n 'prefix': /^36|55|30[0-5]/,\n 'luhn' : true\n },\n\n 'discover': {\n 'length': '16',\n 'prefix': /^6(?:5|011)/,\n 'luhn' : true\n },\n\n 'jcb': {\n 'length': '15,16',\n 'prefix': /^3|1800|2131/,\n 'luhn' : true\n },\n\n 'maestro': {\n 'length': '16,18',\n 'prefix': /^50(?:20|38)|6(?:304|759)/,\n 'luhn' : true\n },\n\n 'mastercard': {\n 'length': '16',\n 'prefix': /^5[1-5]/,\n 'luhn' : true\n },\n\n 'visa': {\n 'length': '13,16',\n 'prefix': /^4/,\n 'luhn' : true\n }\n },\n\n /**\n * Luhn function, to be used when validating credit cards\n *\n */\n _luhn: function (num){\n\n num = parseInt(num,10);\n\n if ( (typeof num !== 'number') && (num % 1 !== 0) ){\n // Luhn can only be used on nums!\n return false;\n }\n\n num = num+'';\n // Check num length\n var length = num.length;\n\n // Checksum of the card num\n var\n i, checksum = 0\n ;\n\n for (i = length - 1; i >= 0; i -= 2)\n {\n // Add up every 2nd digit, starting from the right\n checksum += parseInt(num.substr(i, 1),10);\n }\n\n for (i = length - 2; i >= 0; i -= 2)\n {\n // Add up every 2nd digit doubled, starting from the right\n var dbl = parseInt(num.substr(i, 1) * 2,10);\n\n // Subtract 9 from the dbl where value is greater than 10\n checksum += (dbl >= 10) ? (dbl - 9) : dbl;\n }\n\n // If the checksum is a multiple of 10, the number is valid\n return (checksum % 10 === 0);\n },\n\n /**\n * Checks if a number is of a specific credit card type\n * @method isCreditCard\n * @param {String} num Number to be validates\n * @param {String|Array} creditCardType Credit card type. See _creditCardSpecs for the list of supported values.\n * @return {Boolean}\n * @sample Ink_Util_Validator_isCreditCard.html \n */\n isCreditCard: function(num, creditCardType){\n\n if ( /\\d+/.test(num) === false ){\n return false;\n }\n\n if ( typeof creditCardType === 'undefined' ){\n creditCardType = 'default';\n }\n else if ( creditCardType instanceof Array ){\n var i, ccLength = creditCardType.length;\n for ( i=0; i < ccLength; i++ ){\n // Test each type for validity\n if (this.isCreditCard(num, creditCardType[i]) ){\n return true;\n }\n }\n\n return false;\n }\n\n // Check card type\n creditCardType = creditCardType.toLowerCase();\n\n if ( typeof this._creditCardSpecs[creditCardType] === 'undefined' ){\n return false;\n }\n\n // Check card number length\n var length = num.length+'';\n\n // Validate the card length by the card type\n if ( this._creditCardSpecs[creditCardType]['length'].split(\",\").indexOf(length) === -1 ){\n return false;\n }\n\n // Check card number prefix\n if ( !this._creditCardSpecs[creditCardType]['prefix'].test(num) ){\n return false;\n }\n\n // No Luhn check required\n if (this._creditCardSpecs[creditCardType]['luhn'] === false){\n return true;\n }\n\n return this._luhn(num);\n }\n };\n\n return Validator;\n\n});\n"]} |