/* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ (function(window, document, undefined) { 'use strict'; var ns = window.tau = window.tau || {}, nsConfig = window.tauConfig = window.tauConfig || {}; nsConfig.rootNamespace = 'tau'; nsConfig.fileName = 'tau'; ns.version = '0.13.46'; /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* global window, define */ /* eslint-disable no-console */ /** * #Core namespace * Object contains main framework methods. * @class ns * @author Maciej Urbanski * @author Krzysztof Antoszek * @author Maciej Moczulski * @author Piotr Karny * @author Tomasz Lukawski */ (function (document, console) { "use strict"; var idNumberCounter = 0, currentDate = +new Date(), slice = [].slice, rootNamespace = "", fileName = "", infoForLog = function (args) { var dateNow = new Date(); args.unshift("[" + rootNamespace + "][" + dateNow.toLocaleString() + "]"); }, ns = window.ns || window.tau || {}, nsConfig = window.nsConfig || window.tauConfig || {}; ns.info = ns.info || { profile: "custom" }; ns.tauPerf = ns.tauPerf || {}; window.ns = ns; window.nsConfig = nsConfig; window.tau = ns; window.tauConfig = nsConfig; rootNamespace = nsConfig.rootNamespace; fileName = nsConfig.fileName; /** * Return unique id * @method getUniqueId * @static * @return {string} * @member ns */ ns.getUniqueId = function () { return rootNamespace + "-" + ns.getNumberUniqueId() + "-" + currentDate; }; /** * Return unique id * @method getNumberUniqueId * @static * @return {number} * @member ns */ ns.getNumberUniqueId = function () { return idNumberCounter++; }; /** * logs supplied messages/arguments * @method log * @static * @member ns */ ns.log = function () { var args = slice.call(arguments); infoForLog(args); if (console) { console.log.apply(console, args); } }; /** * logs supplied messages/arguments ad marks it as warning * @method warn * @static * @member ns */ ns.warn = function () { var args = slice.call(arguments); infoForLog(args); if (console) { console.warn.apply(console, args); } }; /** * logs supplied messages/arguments and marks it as error * @method error * @static * @member ns */ ns.error = function () { var args = slice.call(arguments); infoForLog(args); if (console) { console.error.apply(console, args); } }; /** * get from nsConfig * @method getConfig * @param {string} key * @param {*} [defaultValue] value returned when config is not set * @return {*} * @static * @member ns */ ns.getConfig = function (key, defaultValue) { return nsConfig[key] === undefined ? defaultValue : nsConfig[key]; }; /** * set in nsConfig * @method setConfig * @param {string} key * @param {*} value * @param {boolean} [asDefault=false] value should be treated as default (doesn't overwrites * the config[key] if it already exists) * @static * @member ns */ ns.setConfig = function (key, value, asDefault) { if (!asDefault || nsConfig[key] === undefined) { nsConfig[key] = value; } }; /** * Return path for framework script file. * @method getFrameworkPath * @return {?string} * @member ns */ ns.getFrameworkPath = function () { var scripts = document.getElementsByTagName("script"), countScripts = scripts.length, i, url, arrayUrl, count; for (i = 0; i < countScripts; i++) { url = scripts[i].src; arrayUrl = url.split("/"); count = arrayUrl.length; if (arrayUrl[count - 1] === fileName + ".js" || arrayUrl[count - 1] === fileName + ".min.js") { return arrayUrl.slice(0, count - 1).join("/"); } } return null; }; }(window.document, window.console)); /*global window, ns, define*/ /*jslint bitwise: true */ /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @class ns.support * @author Maciej Urbanski */ (function (window, document) { "use strict"; var isTizen = !(typeof window.tizen === "undefined"); function isCircleShape() { var testDivElement = document.createElement("div"), fakeBodyElement = document.createElement("body"), htmlElement = document.getElementsByTagName("html")[0], style = getComputedStyle(testDivElement), isCircle; testDivElement.classList.add("is-circle-test"); fakeBodyElement.appendChild(testDivElement); htmlElement.insertBefore(fakeBodyElement, htmlElement.firstChild); isCircle = style.width === "1px"; htmlElement.removeChild(fakeBodyElement); // to support circle in browser by additional parameter in url /* istanbul ignore if */ if (window.location.search === "?circle") { /* istanbul ignore next: we can't test this part because set location is not supported in test environment */ isCircle = true; } return isCircle; } ns.support = { cssTransitions: true, mediaquery: true, cssPseudoElement: true, touchOverflow: true, cssTransform3d: true, boxShadow: true, scrollTop: 0, dynamicBaseTag: true, cssPointerEvents: false, boundingRect: true, browser: { ie: false, tizen: isTizen }, shape: { circle: isTizen ? window.matchMedia("(-tizen-geometric-shape: circle)").matches : isCircleShape() }, gradeA: function () { return true; }, isCircleShape: isCircleShape }; }(window, window.document)); /*global window, ns, define*/ /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /*jslint bitwise: true */ /* * @author Piotr Karny */ (function () { "use strict"; // Default configuration properties for wearable ns.setConfig("autoBuildOnPageChange", false, true); if (ns.support.shape.circle) { ns.setConfig("pageTransition", "pop", true); ns.setConfig("popupTransition", "pop", true); ns.setConfig("popupFullSize", true, true); ns.setConfig("scrollEndEffectArea", "screen", true); ns.setConfig("enablePageScroll", true, true); ns.setConfig("enablePopupScroll", true, true); } else { ns.setConfig("popupTransition", "slideup", true); ns.setConfig("enablePageScroll", false, true); ns.setConfig("enablePopupScroll", false, true); } // .. other possible options // ns.setConfig('autoInitializePage', true); // ns.setConfig('pageContainer', document.body); // defining application container for wearable }()); /*global window, define, ns*/ /*jslint bitwise: true */ /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * @author Maciej Urbanski * @author Piotr Karny */ (function () { "use strict"; // Default configuration properties ns.setConfig("rootDir", ns.getFrameworkPath(), true); ns.setConfig("version", "", true); ns.setConfig("allowCrossDomainPages", false, true); ns.setConfig("domCache", false, true); // .. other possible options ns.setConfig("autoBuildOnPageChange", true, true); ns.setConfig("autoInitializePage", true, true); ns.setConfig("dynamicBaseEnabled", true, true); ns.setConfig("pageTransition", "none", true); ns.setConfig("popupTransition", "none", true); ns.setConfig("popupFullSize", false, true); ns.setConfig("scrollEndEffectArea", "content", true); ns.setConfig("enablePopupScroll", false, true); // ns.setConfig('container', document.body); // for defining application container // same as above, but for wearable version ns.setConfig("pageContainer", document.body, true); ns.setConfig("findProfileFile", false, true); }()); /*global window, ns, define, XMLHttpRequest, console, Blob */ /*jslint nomen: true, browser: true, plusplus: true */ /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * #Utilities * * The Tizen Advanced UI (TAU) framework provides utilities for easy-developing * and fully replaceable with jQuery method. When user using these DOM and * selector methods, it provide more light logic and it proves performance * of web app. The following table displays the utilities provided by the * TAU framework. * * @class ns.util * @author Maciej Urbanski * @author Krzysztof Antoszek */ (function (window, document, ns) { "use strict"; var currentFrame = null, util = ns.util || {}, // frames callbacks which should be run in next request animation frame waitingFrames = [], slice = [].slice, // inform that loop was added to request animation frame callback loopWork = false; /** * Function which is use as workaround when any type of request animation frame not exists * @param {Function} callback * @method _requestAnimationFrameOnSetTimeout * @static * @member ns.util * @protected */ util._requestAnimationFrameOnSetTimeout = function (callback) { currentFrame = window.setTimeout(callback.bind(callback, +new Date()), 1000 / 60); }; /** * Function which support every request animation frame. * @method _loop * @protected * @static * @member ns.util */ util._loop = function () { var loopWaitingFrames = slice.call(waitingFrames), currentFrameFunction = loopWaitingFrames.shift(), loopTime = performance.now(); waitingFrames = []; while (currentFrameFunction) { currentFrameFunction(); if (performance.now() - loopTime < 15) { currentFrameFunction = loopWaitingFrames.shift(); } else { currentFrameFunction = null; } } if (loopWaitingFrames.length || waitingFrames.length) { waitingFrames.unshift.apply(waitingFrames, loopWaitingFrames); util.windowRequestAnimationFrame(util._loop); } else { loopWork = false; } }; /** * Find browser prefixed request animation frame function. * @method _getRequestAnimationFrame * @protected * @static * @member ns.util */ util._getRequestAnimationFrame = function () { return (window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || util._requestAnimationFrameOnSetTimeout).bind(window); }; /** * Original requestAnimationFrame from object window. * @method windowRequestAnimationFrame * @static * @member ns.util */ util.windowRequestAnimationFrame = util._getRequestAnimationFrame(); /** * Special requestAnimationFrame function which add functions to queue of callbacks * @method requestAnimationFrame * @static * @member ns.util */ util.requestAnimationFrame = function (callback) { waitingFrames.push(callback); if (!loopWork) { util.windowRequestAnimationFrame(util._loop); loopWork = true; } }; util._cancelAnimationFrameOnSetTimeout = function () { // probably wont work if there is any more than 1 // active animationFrame but we are trying anyway window.clearTimeout(currentFrame); }; util._getCancelAnimationFrame = function () { return (window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.oCancelAnimationFrame || window.msCancelAnimationFrame || util._cancelAnimationFrameOnSetTimeout).bind(window); }; util.cancelAnimationFrame = util._getCancelAnimationFrame(); /** * fetchSync retrieves a text document synchronously, returns null on error * @param {string} url * @param {=string} [mime=""] Mime type of the resource * @return {string|null} * @static * @member ns.util */ function fetchSync(url, mime) { var xhr = new XMLHttpRequest(), status; xhr.open("get", url, false); if (mime) { xhr.overrideMimeType(mime); } xhr.send(); if (xhr.readyState === 4) { status = xhr.status; if (status === 200 || (status === 0 && xhr.responseText)) { return xhr.responseText; } } return null; } util.fetchSync = fetchSync; /** * Removes all script tags with src attribute from document and returns them * @param {HTMLElement} container * @return {Array.} * @protected * @static * @member ns.util */ function removeExternalScripts(container) { var scripts = slice.call(container.querySelectorAll("script[src]")), i = scripts.length, script; while (--i >= 0) { script = scripts[i]; script.parentNode.removeChild(script); } return scripts; } util._removeExternalScripts = removeExternalScripts; /** * Evaluates code, reason for a function is for an atomic call to evaluate code * since most browsers fail to optimize functions with try-catch blocks, so this * minimizes the effect, returns the function to run * @param {string} code * @return {Function} * @static * @member ns.util */ function safeEvalWrap(code) { return function () { try { window.eval(code); } catch (e) { if (e.stack) { ns.error(e.stack); } else if (e.name && e.message) { ns.error(e.name, e.message); } else { ns.error(e); } } }; } util.safeEvalWrap = safeEvalWrap; /** * Calls functions in supplied queue (array) * @param {Array.} functionQueue * @static * @member ns.util */ function batchCall(functionQueue) { var i, length = functionQueue.length; for (i = 0; i < length; ++i) { functionQueue[i](); } } util.batchCall = batchCall; /** * Creates new script elements for scripts gathered from a different document * instance, blocks asynchronous evaluation (by renaming src attribute) and * returns an array of functions to run to evaluate those scripts * @param {Array.} scripts * @param {HTMLElement} container * @return {Array.} * @protected * @static * @member ns.util */ function createScriptsSync(scripts, container) { var scriptElement, scriptBody, i, length, queue = []; // proper order of execution for (i = 0, length = scripts.length; i < length; ++i) { scriptBody = util.fetchSync(scripts[i].src, "text/plain"); if (scriptBody) { scriptElement = document.adoptNode(scripts[i]); scriptElement.setAttribute("data-src", scripts[i].src); scriptElement.removeAttribute("src"); // block evaluation queue.push(util.safeEvalWrap(scriptBody)); if (container) { container.appendChild(scriptElement); } } } return queue; } util._createScriptsSync = createScriptsSync; /** * Method make asynchronous call of function * @method async * @inheritdoc #requestAnimationFrame * @member ns.util * @static */ util.async = util.requestAnimationFrame; /** * Appends element from different document instance to current document in the * container element and evaluates scripts (synchronously) * @param {HTMLElement} element * @param {HTMLElement} container * @return {HTMLElement} * @method importEvaluateAndAppendElement * @member ns.util * @static */ util.importEvaluateAndAppendElement = function (element, container) { var externalScriptsQueue = util._createScriptsSync(util._removeExternalScripts(element), element), newNode = document.importNode(element, true); container.appendChild(newNode); // append and eval inline util.batchCall(externalScriptsQueue); return newNode; }; /** * Checks if specified string is a number or not * @method isNumber * @param {string} query * @return {boolean} * @member ns.util * @static */ util.isNumber = function (query) { var parsed = parseFloat(query); return !isNaN(parsed) && isFinite(parsed); }; /** * Reappear script tags to DOM structure to correct run script * @method runScript * @param {string} baseUrl * @param {HTMLScriptElement} script * @member ns.util * @deprecated 2.3 */ util.runScript = function (baseUrl, script) { var newScript = document.createElement("script"), scriptData, i, scriptAttributes = slice.call(script.attributes), src = script.getAttribute("src"), attribute, status; // 'src' may become null when none src attribute is set if (src !== null) { src = util.path.makeUrlAbsolute(src, baseUrl); } //Copy script tag attributes i = scriptAttributes.length; while (--i >= 0) { attribute = scriptAttributes[i]; if (attribute.name !== "src") { newScript.setAttribute(attribute.name, attribute.value); } else { newScript.setAttribute("data-src", attribute.value); } } if (src) { scriptData = util.fetchSync(src, "text/plain"); } else { scriptData = script.textContent; } if (scriptData) { // add the returned content to a newly created script tag newScript.src = window.URL.createObjectURL(new Blob([scriptData], {type: "text/javascript"})); newScript.textContent = scriptData; // for compatibility with some libs ex. template systems } script.parentNode.replaceChild(newScript, script); }; ns.util = util; }(window, window.document, ns)); /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /*global window, ns, define */ /** * #Array Utility * * Utility helps work with arrays. * * @class ns.util.array */ (function (ns) { "use strict"; /** * Convert values to common type and return information about type string or not. * @param {number|string} low * @param {number|string} high * @return {{inival: *, endval: *, chars: boolean}} */ function convertTypes(low, high) { var inival, endval, chars = false; if (isNaN(low) && isNaN(high)) { chars = true; inival = low.charCodeAt(0); endval = high.charCodeAt(0); } else { inival = (isNaN(low) ? 0 : low); endval = (isNaN(high) ? 0 : high); } return { inival: inival, endval: endval, chars: chars }; } /** * Create an array containing the range of integers or characters * from low to high (inclusive) * @method range * @param {number|string} low * @param {number|string} high * @param {number} step * @static * @return {Array} array containing continuos elements * @member ns.util.array */ function range(low, high, step) { // Create an array containing the range of integers or characters // from low to high (inclusive) // // version: 1107.2516 // discuss at: http://phpjs.org/functions/range // + original by: Waldo Malqui Silva // * example 1: range ( 0, 12 ); // * returns 1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] // * example 2: range( 0, 100, 10 ); // * returns 2: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100] // * example 3: range( 'a', 'i' ); // * returns 3: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'] // * example 4: range( 'c', 'a' ); // * returns 4: ['c', 'b', 'a'] var matrix = [], inival, endval, plus, walker = step || 1, chars, typeData; typeData = convertTypes(low, high); inival = typeData.inival; endval = typeData.endval; chars = typeData.chars; plus = inival <= endval; if (plus) { while (inival <= endval) { matrix.push((chars ? String.fromCharCode(inival) : inival)); inival += walker; } } else { while (inival >= endval) { matrix.push((chars ? String.fromCharCode(inival) : inival)); inival -= walker; } } return matrix; } function isCorrectType(object) { return Array.isArray(object) || object instanceof NodeList || typeof object === "function"; } function hasCorrectLength(object) { var length = object.length; return (length === 0 || typeof length === "number" && length > 0 && (length - 1) in object); } /** * Check object is array-like (array-like include array and * collection) * @method isArrayLike * @param {Object} object * @return {boolean} Whether array-like object or not * @member ns.util.array * @static */ function isArrayLike(object) { // if object exists and is different from window // window object has length property if (object && object !== object.window) { // If length value is not number, object is not array and collection. // Collection type is not array but has length value. // e.g) Array.isArray(document.childNodes) ==> false return isCorrectType(object) && hasCorrectLength(object); } return false; } /** * Faster version of standard forEach method in array * Confirmed that this method is 20 times faster then native * @method forEach * @param {Array} array * @param {Function} callback * @member ns.util.array * @static */ function forEach(array, callback) { var i, length, convertedArray = array; if (!(array instanceof Array)) { convertedArray = [].slice.call(array); } length = convertedArray.length; for (i = 0; i < length; i++) { callback(convertedArray[i], i, convertedArray); } } /** * Faster version of standard filter method in array * @method filter * @param {Array} array * @param {Function} callback * @member ns.util.array * @static */ function filter(array, callback) { var result = [], i, length, value, convertedArray = array; if (!(array instanceof Array)) { convertedArray = [].slice.call(array); } length = convertedArray.length; for (i = 0; i < length; i++) { value = convertedArray[i]; if (callback(value, i, convertedArray)) { result.push(value); } } return result; } /** * Faster version of standard map method in array * Confirmed that this method is 60% faster then native * @method map * @param {Array} array * @param {Function} callback * @member ns.util.array * @static */ function map(array, callback) { var result = [], i, length, convertedArray = array; if (!(array instanceof Array)) { convertedArray = [].slice.call(array); } length = convertedArray.length; for (i = 0; i < length; i++) { result.push(callback(convertedArray[i], i, convertedArray)); } return result; } /** * Faster version of standard reduce method in array * Confirmed that this method is 60% faster then native * @method reduce * @param {Array} array * @param {Function} callback * @param {*} [initialValue] * @member ns.util.array * @return {*} * @static */ function reduce(array, callback, initialValue) { var i, length, value, result = initialValue, convertedArray = array; if (!(array instanceof Array)) { convertedArray = [].slice.call(array); } length = convertedArray.length; for (i = 0; i < length; i++) { value = convertedArray[i]; if (result === undefined && i === 0) { result = value; } else { result = callback(result, value, i, convertedArray); } } return result; } ns.util.array = { range: range, isArrayLike: isArrayLike, forEach: forEach, filter: filter, map: map, reduce: reduce }; }(ns)); /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ /* global ns, define, CustomEvent */ /** * #Events * * The Tizen Advanced UI (TAU) framework provides events optimized for the Tizen * Web application. The following table displays the events provided by the TAU * framework. * @class ns.event */ (function (window, ns) { "use strict"; /** * Checks if specified variable is a array or not * @method isArray * @return {boolean} * @member ns.event * @private * @static */ var instances = [], isArray = Array.isArray, isArrayLike = ns.util.array.isArrayLike, /** * @property {RegExp} SPLIT_BY_SPACES_REGEXP */ SPLIT_BY_SPACES_REGEXP = /\s+/g, /** * Returns trimmed value * @method trim * @param {string} value * @return {string} trimmed string * @static * @private * @member ns.event */ trim = function (value) { return value.trim(); }, /** * Split string to array * @method getEventsListeners * @param {string|Array|Object} names string with one name of event, many names of events * divided by spaces, array with names of widgets or object in which keys are names of * events and values are callbacks * @param {Function} globalListener * @return {Array} * @static * @private * @member ns.event */ getEventsListeners = function (names, globalListener) { var name, result = [], i; if (typeof names === "string") { names = names.split(SPLIT_BY_SPACES_REGEXP).map(trim); } if (isArray(names)) { for (i = 0; i < names.length; i++) { result.push({type: names[i], callback: globalListener}); } } else { for (name in names) { if (names.hasOwnProperty(name)) { result.push({type: name, callback: names[name]}); } } } return result; }; /** * Find instance by element * @method findInstance * @param {HTMLElement} element * @return {ns.event.gesture.Instance} * @member ns.event * @static * @private */ function findInstance(element) { var instance; instances.forEach(function (item) { if (item.element === element) { instance = item.instance; } }); return instance; } /** * Remove instance from instances by element * @method removeInstance * @param {HTMLElement} element * @member ns.event * @static * @private */ function removeInstance(element) { instances.forEach(function (item, key) { if (item.element === element) { instances.splice(key, 1); } }); } ns.event = { /** * Triggers custom event fastOn element * The return value is false, if at least one of the event * handlers which handled this event, called preventDefault. * Otherwise it returns true. * @method trigger * @param {HTMLElement|HTMLDocument} element * @param {string} type * @param {?*} [data=null] * @param {boolean=} [bubbles=true] * @param {boolean=} [cancelable=true] * @return {boolean} * @member ns.event * @static */ trigger: function (element, type, data, bubbles, cancelable) { var evt = new CustomEvent(type, { "detail": data, //allow event to bubble up, required if we want to allow to listen fastOn document etc bubbles: typeof bubbles === "boolean" ? bubbles : true, cancelable: typeof cancelable === "boolean" ? cancelable : true }); return element.dispatchEvent(evt); }, /** * Prevent default on original event * @method preventDefault * @param {Event} event * @member ns.event * @static */ preventDefault: function (event) { var originalEvent = event._originalEvent; // @todo this.isPropagationStopped = returnTrue; if (originalEvent && originalEvent.preventDefault) { originalEvent.preventDefault(); } event.preventDefault(); }, /** * Stop event propagation * @method stopPropagation * @param {Event} event * @member ns.event * @static */ stopPropagation: function (event) { var originalEvent = event._originalEvent; // @todo this.isPropagationStopped = returnTrue; if (originalEvent && originalEvent.stopPropagation) { originalEvent.stopPropagation(); } event.stopPropagation(); }, /** * Stop event propagation immediately * @method stopImmediatePropagation * @param {Event} event * @member ns.event * @static */ stopImmediatePropagation: function (event) { var originalEvent = event._originalEvent; // @todo this.isPropagationStopped = returnTrue; if (originalEvent && originalEvent.stopImmediatePropagation) { originalEvent.stopImmediatePropagation(); } event.stopImmediatePropagation(); }, /** * Return document relative cords for event * @method documentRelativeCoordsFromEvent * @param {Event} event * @return {Object} * @return {number} return.x * @return {number} return.y * @member ns.event * @static */ documentRelativeCoordsFromEvent: function (event) { var _event = event ? event : window.event, client = { x: _event.clientX, y: _event.clientY }, page = { x: _event.pageX, y: _event.pageY }, posX = 0, posY = 0, touch0, body = document.body, documentElement = document.documentElement; if (event.type.match(/^touch/)) { touch0 = _event.targetTouches[0] || _event.originalEvent.targetTouches[0]; page = { x: touch0.pageX, y: touch0.pageY }; client = { x: touch0.clientX, y: touch0.clientY }; } if (page.x || page.y) { posX = page.x; posY = page.y; } else if (client.x || client.y) { posX = client.x + body.scrollLeft + documentElement.scrollLeft; posY = client.y + body.scrollTop + documentElement.scrollTop; } return {x: posX, y: posY}; }, /** * Return target relative cords for event * @method targetRelativeCoordsFromEvent * @param {Event} event * @return {Object} * @return {number} return.x * @return {number} return.y * @member ns.event * @static */ targetRelativeCoordsFromEvent: function (event) { var target = event.target, cords = { x: event.offsetX, y: event.offsetY }; if (cords.x === undefined || isNaN(cords.x) || cords.y === undefined || isNaN(cords.y)) { cords = ns.event.documentRelativeCoordsFromEvent(event); cords.x -= target.offsetLeft; cords.y -= target.offsetTop; } return cords; }, /** * Add event listener to element * @method fastOn * @param {HTMLElement} element * @param {string} type * @param {Function} listener * @param {boolean} [useCapture=false] * @member ns.event * @static */ fastOn: function (element, type, listener, useCapture) { element.addEventListener(type, listener, useCapture || false); }, /** * Remove event listener to element * @method fastOff * @param {HTMLElement} element * @param {string} type * @param {Function} listener * @param {boolean} [useCapture=false] * @member ns.event * @static */ fastOff: function (element, type, listener, useCapture) { element.removeEventListener(type, listener, useCapture || false); }, /** * Add event listener to element with prefixes for all browsers * * @example * tau.event.prefixedFastOn(document, "animationEnd", function() { * console.log("animation ended"); * }); * // write "animation ended" on console on event "animationEnd", "webkitAnimationEnd", "mozAnimationEnd", "msAnimationEnd", "oAnimationEnd" * * @method fastPrefixedOn * @param {HTMLElement} element * @param {string} type * @param {Function} listener * @param {boolean} [useCapture=false] * @member ns.event * @static */ prefixedFastOn: function (element, type, listener, useCapture) { var nameForPrefix = type.charAt(0).toLocaleUpperCase() + type.substring(1); element.addEventListener(type.toLowerCase(), listener, useCapture || false); element.addEventListener("webkit" + nameForPrefix, listener, useCapture || false); element.addEventListener("moz" + nameForPrefix, listener, useCapture || false); element.addEventListener("ms" + nameForPrefix, listener, useCapture || false); element.addEventListener("o" + nameForPrefix.toLowerCase(), listener, useCapture || false); }, /** * Remove event listener to element with prefixes for all browsers * * @example * tau.event.prefixedFastOff(document, "animationEnd", functionName); * // remove listeners functionName on events "animationEnd", "webkitAnimationEnd", "mozAnimationEnd", "msAnimationEnd", "oAnimationEnd" * * @method fastPrefixedOff * @param {HTMLElement} element * @param {string} type * @param {Function} listener * @param {boolean} [useCapture=false] * @member ns.event * @static */ prefixedFastOff: function (element, type, listener, useCapture) { var nameForPrefix = type.charAt(0).toLocaleUpperCase() + type.substring(1); element.removeEventListener(type.toLowerCase(), listener, useCapture || false); element.removeEventListener("webkit" + nameForPrefix, listener, useCapture || false); element.removeEventListener("moz" + nameForPrefix, listener, useCapture || false); element.removeEventListener("ms" + nameForPrefix, listener, useCapture || false); element.removeEventListener("o" + nameForPrefix.toLowerCase(), listener, useCapture || false); }, /** * Add event listener to element that can be added addEventListener * @method on * @param {HTMLElement|HTMLDocument|Window} element * @param {string|Array|Object} type * @param {Function} listener * @param {boolean} [useCapture=false] * @member ns.event * @static */ on: function (element, type, listener, useCapture) { var i, j, elementsLength, typesLength, elements, listeners; if (isArrayLike(element)) { elements = element; } else { elements = [element]; } elementsLength = elements.length; listeners = getEventsListeners(type, listener); typesLength = listeners.length; for (i = 0; i < elementsLength; i++) { if (typeof elements[i].addEventListener === "function") { for (j = 0; j < typesLength; j++) { ns.event.fastOn(elements[i], listeners[j].type, listeners[j].callback, useCapture); } } } }, /** * Remove event listener to element * @method off * @param {HTMLElement|HTMLDocument|Window} element * @param {string|Array|Object} type * @param {Function} listener * @param {boolean} [useCapture=false] * @member ns.event * @static */ off: function (element, type, listener, useCapture) { var i, j, elementsLength, typesLength, elements, listeners; if (isArrayLike(element)) { elements = element; } else { elements = [element]; } elementsLength = elements.length; listeners = getEventsListeners(type, listener); typesLength = listeners.length; for (i = 0; i < elementsLength; i++) { if (typeof elements[i].addEventListener === "function") { for (j = 0; j < typesLength; j++) { ns.event.fastOff(elements[i], listeners[j].type, listeners[j].callback, useCapture); } } } }, /** * Add event listener to element only for one trigger * @method one * @param {HTMLElement|HTMLDocument|window} element * @param {string|Array|Object} type * @param {Function} listener * @param {boolean} [useCapture=false] * @member ns.event * @static */ one: function (element, type, listener, useCapture) { var arraySlice = [].slice, i, j, elementsLength, typesLength, elements, listeners, callbacks = []; if (isArrayLike(element)) { elements = arraySlice.call(element); } else { elements = [element]; } elementsLength = elements.length; // pair type with listener listeners = getEventsListeners(type, listener); typesLength = listeners.length; // on each element for (i = 0; i < elementsLength; i++) { // if element has possibility of add listener if (typeof elements[i].addEventListener === "function") { callbacks[i] = []; // for each event type for (j = 0; j < typesLength; j++) { callbacks[i][j] = (function (i, j) { var args = arraySlice.call(arguments); ns.event.fastOff(elements[i], listeners[j].type, callbacks[i][j], useCapture); // remove the first argument of binding function args.shift(); // remove the second argument of binding function args.shift(); listeners[j].callback.apply(this, args); }).bind(null, i, j); ns.event.fastOn(elements[i], listeners[j].type, callbacks[i][j], useCapture); } } } }, // disable is required because method has changing arguments /* eslint-disable jsdoc/check-param-names */ /** * Enable gesture handling on given HTML element or object * @method enableGesture * @param {HTMLElement} element * @param {...Object} [gesture] Gesture object {@link ns.event.gesture} * @member ns.event */ enableGesture: function (element) { var gestureInstance = findInstance(element), length = arguments.length, i = 1; if (!gestureInstance) { gestureInstance = new ns.event.gesture.Instance(element); instances.push({element: element, instance: gestureInstance}); } for (; i < length; i++) { gestureInstance.addDetector(arguments[i]); } }, /** * Disable gesture handling from given HTML element or object * @method disableGesture * @param {HTMLElement} element * @param {...Object} [gesture] Gesture object {@link ns.event.gesture} * @member ns.event */ disableGesture: function (element) { var gestureInstance = findInstance(element), length = arguments.length, i = 1; if (!gestureInstance) { return; } if (length > 1) { gestureInstance.removeDetector(arguments[i]); } else { gestureInstance.destroy(); removeInstance(element); } } /* eslint-disable jsdoc/check-param-names */ }; }(window, ns)); /*global window, ns, define */ /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * #Info * * Various TAU information * @class ns.info */ (function (window, document) { "use strict"; /** * @property {Object} info * @property {string} [info.profile="default"] Current runtime profile * @property {string} [info.theme="default"] Current runtime theme * @property {string} info.version Current runtime version * @member ns.info * @static */ var eventUtils = ns.event, info = { profile: "default", theme: "default", version: ns.version, /** * Refreshes information about runtime * @method refreshTheme * @param {Function} done Callback run when the theme is discovered * @member ns.info * @return {null|String} * @static */ refreshTheme: function (done) { var el = document.createElement("span"), parent = document.body, themeName; if (document.readyState !== "interactive" && document.readyState !== "complete") { eventUtils.fastOn(document, "DOMContentLoaded", this.refreshTheme.bind(this, done)); return null; } el.classList.add("tau-info-theme"); parent.appendChild(el); themeName = window.getComputedStyle(el, ":after").content; parent.removeChild(el); if (themeName && themeName.length > 0) { this.theme = themeName; } themeName = themeName || null; if (done) { done(themeName); } return themeName; } }; info.refreshTheme(); ns.info = info; }(window, window.document)); /*global define: false, window: false, ns: false */ /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * #Selectors Utility * Object contains functions to get HTML elements by different selectors. * @class ns.util.selectors * @author Maciej Urbanski * @author Krzysztof Antoszek * @author Jadwiga Sosnowska * @author Damian Osipiuk */ (function (document, ns) { "use strict"; /** * @method slice Alias for array slice method * @member ns.util.selectors * @private * @static */ var slice = [].slice, /** * @method matchesSelectorType * @return {string|boolean} * @member ns.util.selectors * @private * @static */ matchesSelectorType = (function () { var el = document.createElement("div"); if (typeof el.webkitMatchesSelector === "function") { return "webkitMatchesSelector"; } if (typeof el.mozMatchesSelector === "function") { return "mozMatchesSelector"; } if (typeof el.msMatchesSelector === "function") { return "msMatchesSelector"; } if (typeof el.matchesSelector === "function") { return "matchesSelector"; } if (typeof el.matches === "function") { return "matches"; } return ""; }()); /** * Prefix selector with 'data-' and namespace if present * @method getDataSelector * @param {string} selector * @return {string} * @member ns.util.selectors * @private * @static */ function getDataSelector(selector) { var namespace = ns.getConfig("namespace"); return "[data-" + (namespace ? namespace + "-" : "") + selector + "]"; } /** * Runs matches implementation of matchesSelector * method on specified element * @method matchesSelector * @param {HTMLElement} element * @param {string} selector * @return {boolean} * @static * @member ns.util.selectors */ function matchesSelector(element, selector) { if (matchesSelectorType && element[matchesSelectorType]) { return element[matchesSelectorType](selector); } return false; } /** * Return array with all parents of element. * @method parents * @param {HTMLElement} element * @return {Array} * @member ns.util.selectors * @private * @static */ function parents(element) { var items = [], current = element.parentNode; while (current && current !== document) { items.push(current); current = current.parentNode; } return items; } /** * Checks if given element and its ancestors matches given function * @method closest * @param {HTMLElement} element * @param {Function} testFunction * @return {?HTMLElement} * @member ns.util.selectors * @static * @private */ function closest(element, testFunction) { var current = element; while (current && current !== document) { if (testFunction(current)) { return current; } current = current.parentNode; } return null; } /** * @method testSelector * @param {string} selector * @param {HTMLElement} node * @return {boolean} * @member ns.util.selectors * @static * @private */ function testSelector(selector, node) { return matchesSelector(node, selector); } /** * @method testClass * @param {string} className * @param {HTMLElement} node * @return {boolean} * @member ns.util.selectors * @static * @private */ function testClass(className, node) { return node && node.classList && node.classList.contains(className); } /** * @method testTag * @param {string} tagName * @param {HTMLElement} node * @return {boolean} * @member ns.util.selectors * @static * @private */ function testTag(tagName, node) { return node.tagName.toLowerCase() === tagName; } /** * @class ns.util.selectors */ ns.util.selectors = { matchesSelector: matchesSelector, /** * Return array with children pass by given selector. * @method getChildrenBySelector * @param {HTMLElement} context * @param {string} selector * @return {Array} * @static * @member ns.util.selectors */ getChildrenBySelector: function (context, selector) { return slice.call(context.children).filter(testSelector.bind(null, selector)); }, /** * Return array with children pass by given data-namespace-selector. * @method getChildrenByDataNS * @param {HTMLElement} context * @param {string} dataSelector * @return {Array} * @static * @member ns.util.selectors */ getChildrenByDataNS: function (context, dataSelector) { return slice.call(context.children).filter(testSelector.bind(null, getDataSelector(dataSelector))); }, /** * Return array with children with given class name. * @method getChildrenByClass * @param {HTMLElement} context * @param {string} className * @return {Array} * @static * @member ns.util.selectors */ getChildrenByClass: function (context, className) { return slice.call(context.children).filter(testClass.bind(null, className)); }, /** * Return array with children with given tag name. * @method getChildrenByTag * @param {HTMLElement} context * @param {string} tagName * @return {Array} * @static * @member ns.util.selectors */ getChildrenByTag: function (context, tagName) { return slice.call(context.children).filter(testTag.bind(null, tagName)); }, /** * Return array with all parents of element. * @method getParents * @param {HTMLElement} context * @param {string} selector * @return {Array} * @static * @member ns.util.selectors */ getParents: parents, /** * Return array with all parents of element pass by given selector. * @method getParentsBySelector * @param {HTMLElement} context * @param {string} selector * @return {Array} * @static * @member ns.util.selectors */ getParentsBySelector: function (context, selector) { return parents(context).filter(testSelector.bind(null, selector)); }, /** * Return array with all parents of element pass by given selector with namespace. * @method getParentsBySelectorNS * @param {HTMLElement} context * @param {string} selector * @return {Array} * @static * @member ns.util.selectors */ getParentsBySelectorNS: function (context, selector) { return parents(context).filter(testSelector.bind(null, getDataSelector(selector))); }, /** * Return array with all parents of element with given class name. * @method getParentsByClass * @param {HTMLElement} context * @param {string} className * @return {Array} * @static * @member ns.util.selectors */ getParentsByClass: function (context, className) { return parents(context).filter(testClass.bind(null, className)); }, /** * Return array with all parents of element with given tag name. * @method getParentsByTag * @param {HTMLElement} context * @param {string} tagName * @return {Array} * @static * @member ns.util.selectors */ getParentsByTag: function (context, tagName) { return parents(context).filter(testTag.bind(null, tagName)); }, /** * Return first element from parents of element pass by selector. * @method getClosestBySelector * @param {HTMLElement} context * @param {string} selector * @return {HTMLElement} * @static * @member ns.util.selectors */ getClosestBySelector: function (context, selector) { return closest(context, testSelector.bind(null, selector)); }, /** * Return first element from parents of element pass by selector with namespace. * @method getClosestBySelectorNS * @param {HTMLElement} context * @param {string} selector * @return {HTMLElement} * @static * @member ns.util.selectors */ getClosestBySelectorNS: function (context, selector) { return closest(context, testSelector.bind(null, getDataSelector(selector))); }, /** * Return first element from parents of element with given class name. * @method getClosestByClass * @param {HTMLElement} context * @param {string} selector * @return {HTMLElement} * @static * @member ns.util.selectors */ getClosestByClass: function (context, selector) { return closest(context, testClass.bind(null, selector)); }, /** * Return first element from parents of element with given tag name. * @method getClosestByTag * @param {HTMLElement} context * @param {string} selector * @return {HTMLElement} * @static * @member ns.util.selectors */ getClosestByTag: function (context, selector) { return closest(context, testTag.bind(null, selector)); }, /** * Return array of elements from context with given data-selector * @method getAllByDataNS * @param {HTMLElement} context * @param {string} dataSelector * @return {Array} * @static * @member ns.util.selectors */ getAllByDataNS: function (context, dataSelector) { return slice.call(context.querySelectorAll(getDataSelector(dataSelector))); }, /** * Get scrollable parent element * @method getScrollableParent * @param {HTMLElement} element * @return {HTMLElement} * @static * @member ns.util.selectors */ getScrollableParent: function (element) { var overflow, style; while (element && element !== document.body) { style = window.getComputedStyle(element); if (style) { overflow = style.getPropertyValue("overflow-y"); if (overflow === "scroll" || (overflow === "auto" && element.scrollHeight > element.clientHeight)) { return element; } } element = element.parentNode; } return null; } }; }(window.document, ns)); /*global define, ns */ /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Copyright (c) 2010 - 2014 Samsung Electronics Co., Ltd. * License : MIT License V2 */ /** * #Object Utility * Object contains functions help work with objects. * @class ns.util.object * @author Maciej Urbanski * @author Piotr Karny */ (function () { "use strict"; var object = { /** * Copy object to new object * @method copy * @param {Object} orgObject * @return {Object} * @static * @member ns.util.object */ copy: function (orgObject) { return object.merge({}, orgObject); }, /** * Attach fields from second object to first object. * @method fastMerge * @param {Object} newObject * @param {Object} orgObject * @return {Object} * @static * @member ns.util.object */ fastMerge: function (newObject, orgObject) { var key; for (key in orgObject) { if (orgObject.hasOwnProperty(key)) { newObject[key] = orgObject[key]; } } return newObject; }, /** * Attach fields from second and next object to first object. * @method merge * @return {Object} * @static * @member ns.util.object */ merge: function (/* newObject, orgObject, override */) { var newObject, orgObject, override, key, args = [].slice.call(arguments), argsLength = args.length, i; newObject = args.shift(); override = true; if (typeof arguments[argsLength - 1] === "boolean") { override = arguments[argsLength - 1]; argsLength--; } for (i = 0; i < argsLength; i++) { orgObject = args.shift(); if (orgObject !== null) { for (key in orgObject) { if (orgObject.hasOwnProperty(key) && (override || newObject[key] === undefined)) { newObject[key] = orgObject[key]; } } } } return newObject; }, /** * Function add to Constructor prototype Base object and add to prototype properties and methods from * prototype object. * @method inherit * @param {Function} Constructor * @param {Function} Base * @param {Object} prototype * @static * @member ns.util.object */ /* jshint -W083 */ inherit: function (Constructor, Base, prototype) { var basePrototype = new Base(), property, value; for (property in prototype) { if (prototype.hasOwnProperty(property)) { value = prototype[property]; if (typeof value === "function") { basePrototype[property] = (function createFunctionWithSuper(Base, property, value) { var _super = function () { var superFunction = Base.prototype[property]; if (superFunction) { return superFunction.apply(this, arguments); } return null; }; return function () { var __super = this._super, returnValue; this._super = _super; returnValue = value.apply(this, arguments); this._super = __super; return returnValue; }; }(Base, property, value)); } else { basePrototype[property] = value; } } } Constructor.prototype = basePrototype; Constructor.prototype.constructor = Constructor; }, /** * Returns true if every property value corresponds value from 'value' argument * @method hasPropertiesOfValue * @param {Object} obj * @param {*} [value=undefined] * @return {boolean} */ hasPropertiesOfValue: function (obj, value) { var keys = Object.keys(obj), i = keys.length; // Empty array should return false if (i === 0) { return false; } while (--i >= 0) { if (obj[keys[i]] !== value) { return false; } } return true; }, /** * Remove properties from object. * @method removeProperties * @param {Object} object * @param {Array} propertiesToRemove * @return {Object} */ removeProperties: function (object, propertiesToRemove) { var length = propertiesToRemove.length, property, i; for (i = 0; i < length; i++) { property = propertiesToRemove[i]; if (object.hasOwnProperty(property)) { delete object[property]; } } return object; } }; ns.util.object = object; }()); /*global window, define, ns, Node */ /*jslint nomen: true, plusplus: true, bitwise: false */ /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Copyright (c) 2010 - 2014 Samsung Electronics Co., Ltd. * License : MIT License V2 */ /** * #Engine * Main class with engine of library which control communication * between parts of framework. * @class ns.engine * @author Maciej Urbanski * @author Krzysztof Antoszek * @author Michal Szepielak * @author Jadwiga Sosnowska * @author Maciej Moczulski * @author Piotr Karny * @author Tomasz Lukawski * @author Przemyslaw Ciezkowski * @author Hyunkook, Cho * @author Hyeoncheol Choi * @author Piotr Ostalski */ (function (window, document) { "use strict"; var slice = [].slice, /** * @property {Object} eventUtils {@link ns.event} * @private * @static * @member ns.engine */ eventUtils = ns.event, util = ns.util, objectUtils = util.object, selectors = util.selectors, arrayUtils = ns.util.array, /** * @property {Object} widgetDefinitions Object with widgets definitions * @private * @static * @member ns.engine */ widgetDefinitions = {}, /** * @property {Object} widgetBindingMap Object with widgets bindings * @private * @static * @member ns.engine */ widgetBindingMap = {}, location = window.location, /** * engine mode, if true then engine only builds widgets * @property {boolean} justBuild * @private * @static * @member ns.engine */ justBuild = location.hash === "#build", /** * @property {string} [TYPE_STRING="string"] local cache of string type name * @private * @static * @readonly * @member ns.engine */ TYPE_STRING = "string", /** * @property {string} [TYPE_FUNCTION="function"] local cache of function type name * @private * @static * @readonly * @member ns.engine */ TYPE_FUNCTION = "function", /** * @property {string} [DATA_BUILT="data-tau-built"] attribute informs that widget id build * @private * @static * @readonly * @member ns.engine */ DATA_BUILT = "data-tau-built", /** * @property {string} [DATA_NAME="data-tau-name"] attribute contains widget name * @private * @static * @readonly * @member ns.engine */ DATA_NAME = "data-tau-name", /** * @property {string} [DATA_BOUND="data-tau-bound"] attribute informs that widget id bound * @private * @static * @readonly * @member ns.engine */ DATA_BOUND = "data-tau-bound", /** * @property {string} NAMES_SEPARATOR * @private * @static * @readonly */ NAMES_SEPARATOR = ",", /** * @property {string} [querySelectorWidgets="*[data-tau-built][data-tau-name]:not([data-tau-bound])"] query selector for all widgets which are built but not bound * @private * @static * @member ns.engine */ querySelectorWidgets = "*[" + DATA_BUILT + "][" + DATA_NAME + "]:not([" + DATA_BOUND + "])", /** * @method excludeBuildAndBound * @private * @static * @param {string} widgetType * @member ns.engine * @return {string} :not([data-tau-built*='widgetName']):not([data-tau-bound*='widgetName']) */ excludeBuiltAndBound = function (widgetType) { return ":not([" + DATA_BUILT + "*='" + widgetType + "']):not([" + DATA_BOUND + "*='" + widgetType + "'])"; }, /** * Engine event types * @property {Object} eventType * @property {string} eventType.INIT="tauinit" INIT of framework init event * @property {string} eventType.WIDGET_BOUND="widgetbound" WIDGET_BOUND of widget bound event * @property {string} eventType.WIDGET_DEFINED="widgetdefined" WIDGET_DEFINED of widget built event * @property {string} eventType.WIDGET_BUILT="widgetbuilt" WIDGET_BUILT of widget built event * @property {string} eventType.BOUND="bound" BOUND of bound event * @static * @readonly * @member ns.engine */ eventType = { INIT: "tauinit", READY: "tauready", WIDGET_BOUND: "widgetbound", WIDGET_DEFINED: "widgetdefined", WIDGET_BUILT: "widgetbuilt", DESTROY: "taudestroy", BOUND: "bound", WIDGET_INIT: "init" }, engine; /** * This function prepares selector for widget' definition * @method selectorChange * @param {string} selectorName * @return {string} new selector * @member ns.engine * @static */ function selectorChange(selectorName) { if (selectorName.match(/\[data-role=/) && !selectorName.match(/:not\(\[data-role=/)) { return selectorName.trim(); } return selectorName.trim() + ":not([data-role='none'])"; } /** * Function to define widget * @method defineWidget * @param {string} name * @param {string} selector * @param {Array} methods * @param {Object} widgetClass * @param {string} [namespace] * @param {boolean} [redefine] * @param {boolean} [widgetNameToLowercase=true] * @return {boolean} * @member ns.engine * @static */ function defineWidget(name, selector, methods, widgetClass, namespace, redefine, widgetNameToLowercase, BaseElement) { var definition; // Widget name is absolutely required if (name) { if (!widgetDefinitions[name] || redefine) { methods = methods || []; methods.push("destroy", "disable", "enable", "option", "refresh", "value"); definition = { name: name, methods: methods, selector: selector || "", selectors: selector ? selector.split(",").map(selectorChange) : [], widgetClass: widgetClass || null, namespace: namespace || "", widgetNameToLowercase: widgetNameToLowercase === undefined ? true : !!widgetNameToLowercase, BaseElement: BaseElement }; widgetDefinitions[name] = definition; if (namespace) { widgetDefinitions[namespace + "." + name] = definition; } eventUtils.trigger(document, "widgetdefined", definition, false); return true; } } else { ns.error("Widget with selector [" + selector + "] defined without a name, aborting!"); } return false; } /** * Get widget instance from binding for given element and type * @method getInstanceByElement * @static * @param {Object} binding * @param {HTMLElement} element * @param {string} [type] widget name, if is empty then return first built widget * @return {?Object} * @member ns.engine */ function getInstanceByElement(binding, element, type) { var widgetInstance, bindingElement, storedWidgetNames, names = type ? type.split(".") : [], name = names.pop(), namespace = names.pop(); // If name is defined it's possible to fetch it instantly if (name) { widgetInstance = binding.instances[name]; } else { storedWidgetNames = Object.keys(binding.instances); widgetInstance = binding.instances[storedWidgetNames[0]]; } if (namespace && widgetInstance && widgetInstance.namespace !== namespace) { widgetInstance = null; } // Return only it instance of the proper widget exists if (widgetInstance) { // Check if widget instance has that same object referenced if (widgetInstance.element === element) { return widgetInstance; } } return null; } /** * Get binding for element * @method getBinding * @static * @param {HTMLElement|string} element * @param {string} [type] widget name * @return {?Object} * @member ns.engine */ function getBinding(element, type) { var id = !element || typeof element === TYPE_STRING ? element : element.id, binding; if (typeof element === TYPE_STRING) { element = document.getElementById(id); } if (element) { // Fetch group of widget defined for this element binding = widgetBindingMap[id]; if (binding && typeof binding === "object") { return getInstanceByElement(binding, element, type); } } return null; } /** * Set binding of widget * @method setBinding * @param {ns.widget.BaseWidget} widgetInstance * @static * @member ns.engine */ function setBinding(widgetInstance) { var id = widgetInstance.element.id, type = widgetInstance.name, widgetBinding = widgetBindingMap[id]; // If the HTMLElement never had a widget declared create an empty object if (!widgetBinding) { widgetBinding = { elementId: id, element: widgetInstance.element, instances: {} }; } widgetBinding.instances[type] = widgetInstance; widgetBindingMap[id] = widgetBinding; } /** * Returns all bindings for element or id gives as parameter * @method getAllBindings * @param {HTMLElement|string} element * @return {?Object} * @static * @member ns.engine */ function getAllBindings(element) { var id = !element || typeof element === TYPE_STRING ? element : element.id; return (widgetBindingMap[id] && widgetBindingMap[id].instances) || null; } /** * Removes given name from attributeValue string. * Names should be separated with a NAMES_SEPARATOR * @param {string} name * @param {string} attributeValue * @private * @static * @return {string} */ function _removeWidgetNameFromAttribute(name, attributeValue) { var widgetNames, searchResultIndex; // Split attribute value by separator widgetNames = attributeValue.split(NAMES_SEPARATOR); searchResultIndex = widgetNames.indexOf(name); if (searchResultIndex > -1) { widgetNames.splice(searchResultIndex, 1); attributeValue = widgetNames.join(NAMES_SEPARATOR); } return attributeValue; } function _removeAllBindingAttributes(element) { element.removeAttribute(DATA_BUILT); element.removeAttribute(DATA_BOUND); element.removeAttribute(DATA_NAME); } /** * Remove binding data attributes for element. * @method _removeBindingAttributes * @param {HTMLElement} element * @param {string} type widget type (name) * @private * @static * @member ns.engine */ function _removeWidgetFromAttributes(element, type) { var dataBuilt, dataBound, dataName; // Most often case is that name is not defined if (!type) { _removeAllBindingAttributes(element); } else { dataBuilt = _removeWidgetNameFromAttribute(type, element.getAttribute(DATA_BUILT) || ""); dataBound = _removeWidgetNameFromAttribute(type, element.getAttribute(DATA_BOUND) || ""); dataName = _removeWidgetNameFromAttribute(type, element.getAttribute(DATA_NAME) || ""); // Check if all attributes have at least one widget if (dataBuilt && dataBound && dataName) { element.setAttribute(DATA_BUILT, dataBuilt); element.setAttribute(DATA_BOUND, dataBound); element.setAttribute(DATA_NAME, dataName); } else { // If something is missing remove everything _removeAllBindingAttributes(element); } } } /** * Method removes binding for single widget. * @method _removeSingleBinding * @param {Object} bindingGroup * @param {string} type * @return {boolean} * @private * @static */ function _removeSingleBinding(bindingGroup, type) { var widgetInstance = bindingGroup[type]; if (widgetInstance) { if (widgetInstance.element && typeof widgetInstance.element.setAttribute === TYPE_FUNCTION) { _removeWidgetFromAttributes(widgetInstance.element, type); } delete bindingGroup[type]; return true; } return false; } /** * Remove group of bindings for all types of widgets based on the same element * @method removeGroupBindingAllTypes * @param {Object} bindingGroup * @param {string} id widget element id * @return {boolean} * @static * @member ns.engine */ function removeGroupBindingAllTypes(bindingGroup, id) { var singleSuccess, widgetName, fullSuccess = true; // Iterate over group of created widgets for (widgetName in bindingGroup) { if (bindingGroup.hasOwnProperty(widgetName)) { singleSuccess = _removeSingleBinding(bindingGroup, widgetName); // As we iterate over keys we are sure we want to remove this element // NOTE: Removing property by delete is slower than assigning null value bindingGroup[widgetName] = null; fullSuccess = (fullSuccess && singleSuccess); } } // If the object bindingGroup is empty or every key has a null value if (objectUtils.hasPropertiesOfValue(bindingGroup, null)) { // NOTE: Removing property by delete is slower than assigning null value widgetBindingMap[id] = null; } return fullSuccess; } /** * Remove group of bindings for widgets based on the same element * @method removeGroupBinding * @param {Object} bindingGroup * @param {string} type object name * @param {string} id widget element id * @return {boolean} * @static * @member ns.engine */ function removeGroupBinding(bindingGroup, type, id) { var success; if (!type) { success = removeGroupBindingAllTypes(bindingGroup, id); } else { success = _removeSingleBinding(bindingGroup, type); if (objectUtils.hasPropertiesOfValue(bindingGroup, null)) { widgetBindingMap[id] = null; } } return success; } /** * Remove binding for widget based on element. * @method removeBinding * @param {HTMLElement|string} element * @param {?string} [type=null] widget name * @return {boolean} * @static * @member ns.engine */ function removeBinding(element, type) { var id = (typeof element === TYPE_STRING) ? element : element.id, binding = widgetBindingMap[id], bindingGroup; // [NOTICE] Due to backward compatibility calling removeBinding // with one parameter should remove all bindings if (binding) { if (typeof element === TYPE_STRING) { // Search based on current document may return bad results, // use previously defined element if it exists element = binding.element; } if (element) { _removeWidgetFromAttributes(element, type); } bindingGroup = widgetBindingMap[id] && widgetBindingMap[id].instances; if (bindingGroup) { return removeGroupBinding(bindingGroup, type, id); } if (widgetBindingMap[id].instances && (Object.keys(widgetBindingMap[id].instances).length === 0)) { widgetBindingMap[id] = null; } } return false; } /** * Removes all bindings of widgets. * @method removeAllBindings * @param {HTMLElement|string} element * @return {boolean} * @static * @member ns.engine */ function removeAllBindings(element) { // @TODO this should be coded in the other way around, removeAll should loop through all bindings and inside call removeBinding // but due to backward compatibility that code should be more readable return removeBinding(element); } /** * If element not exist create base element for widget. * @method ensureElement * @param {HTMLElement} element * @param {ns.widget.BaseWidget} Widget * @return {HTMLElement} * @static * @private * @member ns.engine */ function ensureElement(element, Widget) { if (!element || !(element instanceof HTMLElement)) { if (typeof Widget.createEmptyElement === TYPE_FUNCTION) { element = Widget.createEmptyElement(); } else { element = document.createElement("div"); } } return element; } /** * Process core widget method * - configure * - build * - init * - bindEvents * @method processWidget * @param {HTMLElement} element base element of widget * @param {Object} widgetInstance instance of widget * @param {Object} definition definition of widget * @param {ns.widget.BaseWidget} definition.widgetClass * @param {string} definition.name * @param {Object} [options] options for widget * @private * @static * @member ns.engine */ function coreProcessWidget(element, widgetInstance, definition, options) { var widgetOptions = options || {}, createFunction = widgetOptions.create, buildAttribute; widgetInstance.configure(definition, element, options); // Run .create method from widget options when a [widgetName]create event is triggered if (typeof createFunction === TYPE_FUNCTION) { eventUtils.one(element, definition.name.toLowerCase() + "create", createFunction); } if (element.id) { widgetInstance.id = element.id; } // Check if this type of widget was build for this element before buildAttribute = element.getAttribute(DATA_BUILT); if (!buildAttribute || buildAttribute.split(NAMES_SEPARATOR).indexOf(widgetInstance.name) === -1) { element = widgetInstance.build(element); } if (element) { widgetInstance.element = element; setBinding(widgetInstance); widgetInstance.trigger(eventType.WIDGET_BUILT, widgetInstance, false); if (!justBuild) { widgetInstance.init(element); } widgetInstance.bindEvents(element, justBuild); widgetInstance.trigger(widgetInstance.widgetEventPrefix + eventType.WIDGET_INIT); widgetInstance.trigger(eventType.WIDGET_BOUND, widgetInstance, false); eventUtils.trigger(document, eventType.WIDGET_BOUND, widgetInstance); } else { } } /** * Load widget * @method processWidget * @param {HTMLElement} element base element of widget * @param {Object} definition definition of widget * @param {ns.widget.BaseWidget} definition.widgetClass * @param {string} definition.name * @param {Object} [options] options for widget * @return {?HTMLElement} * @private * @static * @member ns.engine */ function processWidget(element, definition, options) { var Widget = definition.widgetClass, /** * @type {ns.widget.BaseWidget} widgetInstance */ widgetInstance, parentEnhance, existingBinding; element = ensureElement(element, Widget); widgetInstance = Widget ? new Widget(element) : false; // if any parent has attribute data-enhance=false then stop building widgets parentEnhance = selectors.getParentsBySelectorNS(element, "enhance=false"); // While processing widgets queue other widget may built this one before // it reaches it's turn existingBinding = getBinding(element, definition.name); if (existingBinding && existingBinding.element === element) { return element; } if (widgetInstance) { if (!parentEnhance.length) { coreProcessWidget(element, widgetInstance, definition, options); } return widgetInstance.element; } return null; } /** * Destroys widget of given 'type' for given HTMLElement. * [NOTICE] This method won't destroy any children widgets. * @method destroyWidget * @param {HTMLElement|string} element * @param {string} type * @static * @member ns.engine */ function destroyWidget(element, type) { var widgetInstance; if (typeof element === TYPE_STRING) { element = document.getElementById(element); } // If type is not defined all widgets should be removed // this is for backward compatibility widgetInstance = getBinding(element, type); if (widgetInstance) { //Destroy widget widgetInstance.destroy(); widgetInstance.trigger("widgetdestroyed"); removeBinding(element, type); } } /** * Calls destroy on group of widgets connected with given HTMLElement * @method destroyGroupWidgets * @param {HTMLElement|string} element * @static * @private * @member ns.engine */ function destroyGroupWidgets(element) { var widgetName, widgetInstance, widgetGroup; widgetGroup = getAllBindings(element); for (widgetName in widgetGroup) { if (widgetGroup.hasOwnProperty(widgetName)) { widgetInstance = widgetGroup[widgetName]; //Destroy widget if (widgetInstance) { widgetInstance.destroy(); widgetInstance.trigger("widgetdestroyed"); } } } } /** * Calls destroy on widget (or widgets) connected with given HTMLElement * Removes child widgets as well. * @method destroyAllWidgets * @param {HTMLElement|string} element * @param {boolean} [childOnly=false] destroy only widgets on children elements * @static * @member ns.engine */ function destroyAllWidgets(element, childOnly) { var childWidgets, i; if (typeof element === TYPE_STRING) { element = document.getElementById(element); } if (!childOnly) { // If type is not defined all widgets should be removed // this is for backward compatibility destroyGroupWidgets(element); } //Destroy child widgets, if something left. childWidgets = slice.call(element.querySelectorAll("[" + DATA_BOUND + "]")); for (i = childWidgets.length - 1; i >= 0; i -= 1) { if (childWidgets[i]) { destroyAllWidgets(childWidgets[i], false); } } removeAllBindings(element); } /** * Load widgets from data-* definition * @method processHollowWidget * @param {HTMLElement} element base element of widget * @param {Object} definition widget definition * @param {Object} [options] options for create widget * @return {HTMLElement} base element of widget * @private * @static * @member ns.engine */ function processHollowWidget(element, definition, options) { var name = (element && element.getAttribute(DATA_NAME)) || (definition && definition.name); definition = definition || (name && widgetDefinitions[name]) || { "name": name }; return processWidget(element, definition, options); } /** * Compare function for nodes on build queue * @param {Object} nodeA * @param {Object} nodeB * @return {number} * @private * @static */ function compareByDepth(nodeA, nodeB) { /*jshint -W016 */ var mask = Node.DOCUMENT_POSITION_CONTAINS | Node.DOCUMENT_POSITION_PRECEDING; if (nodeA.element === nodeB.element) { return 0; } if (nodeA.element.compareDocumentPosition(nodeB.element) & mask) { return 1; } /*jshint +W016 */ return -1; } /** * Processes one build queue item. Runs processHollowWidget * underneath * @method processBuildQueueItem * @param {Object|HTMLElement} queueItem * @private * @static */ function processBuildQueueItem(queueItem) { // HTMLElement doesn't have .element property // widgetDefinitions will return undefined when called widgetDefinitions[undefined] processHollowWidget(queueItem.element || queueItem, widgetDefinitions[queueItem.widgetName]); } /** * Build widgets on all children of context element * @method createWidgets * @static * @param {HTMLElement} context base html for create children * @member ns.engine */ function createWidgets(context) { // find widget which are built var builtWidgetElements = slice.call(context.querySelectorAll(querySelectorWidgets)), normal, buildQueue = [], selectorKeys = Object.keys(widgetDefinitions), excludeSelector, i, j, len = selectorKeys.length, definition, widgetName, definitionSelectors; // process built widgets builtWidgetElements.forEach(processBuildQueueItem); // process widgets didn't build for (i = 0; i < len; ++i) { widgetName = selectorKeys[i]; if (widgetName.indexOf(".") === -1) { definition = widgetDefinitions[widgetName]; definitionSelectors = definition.selectors; if (definitionSelectors.length) { excludeSelector = excludeBuiltAndBound(widgetName); normal = slice.call(context.querySelectorAll(definitionSelectors.join(excludeSelector + ",") + excludeSelector)); j = normal.length; while (--j >= 0) { buildQueue.push({ element: normal[j], widgetName: widgetName }); } } } } // Sort queue by depth, on every DOM branch outer most element go first buildQueue.sort(compareByDepth); // Build all widgets from queue buildQueue.forEach(processBuildQueueItem); eventUtils.trigger(document, "built"); eventUtils.trigger(document, eventType.BOUND); } /** * Handler for event create * @method createEventHandler * @param {Event} event * @static * @member ns.engine */ function createEventHandler(event) { createWidgets(event.target); } function setViewport() { /** * Sets viewport tag if not exists */ var documentHead = document.head, metaTagListLength, metaTagList, metaTag, i; metaTagList = documentHead.querySelectorAll("[name=\"viewport\"]"); metaTagListLength = metaTagList.length; if (metaTagListLength > 0) { // Leave the last viewport tag --metaTagListLength; // Remove duplicated tags for (i = 0; i < metaTagListLength; ++i) { // Remove meta tag from DOM documentHead.removeChild(metaTagList[i]); } } else { // Create new HTML Element metaTag = document.createElement("meta"); // Set required attributes metaTag.setAttribute("name", "viewport"); metaTag.setAttribute("content", "width=device-width, user-scalable=no"); // Force that viewport tag will be first child of head if (documentHead.firstChild) { documentHead.insertBefore(metaTag, documentHead.firstChild); } else { documentHead.appendChild(metaTag); } } } /** * Build first page * @method build * @static * @member ns.engine */ function build() { eventUtils.trigger(document, eventType.READY); setViewport(); } /** * Method to remove all listeners bound in run * @method stop * @static * @member ns.engine */ function stop() { } /** * Method to remove all listeners bound in run * @method destroy * @static * @member ns.engine */ function destroy() { stop(); eventUtils.fastOff(document, "create", createEventHandler); destroyAllWidgets(document.body, true); eventUtils.trigger(document, eventType.DESTROY); } /** * Add to object value at index equal to type of arg. * @method getType * @param {Object} result * @param {*} arg * @return {Object} * @static * @private * @member ns.engine */ function getType(result, arg) { var type = arg instanceof HTMLElement ? "HTMLElement" : typeof arg; result[type] = arg; return result; } /** * Convert args array to object with keys being types and arguments mapped by values * @method getArgumentsTypes * @param {Arguments[]} args * @return {Object} * @static * @private * @member ns.engine */ function getArgumentsTypes(args) { return arrayUtils.reduce(args, getType, {}); } ns.widgetDefinitions = {}; engine = { justBuild: location.hash === "#build", /** * object with names of engine attributes * @property {Object} dataTau * @property {string} [dataTau.built="data-tau-built"] attribute inform that widget id build * @property {string} [dataTau.name="data-tau-name"] attribute contains widget name * @property {string} [dataTau.bound="data-tau-bound"] attribute inform that widget id bound * @property {string} [dataTau.separator=","] separation string for widget names * @static * @member ns.engine */ dataTau: { built: DATA_BUILT, name: DATA_NAME, bound: DATA_BOUND, separator: NAMES_SEPARATOR }, destroyWidget: destroyWidget, destroyAllWidgets: destroyAllWidgets, createWidgets: createWidgets, /** * Method to get all definitions of widgets * @method getDefinitions * @return {Object} * @static * @member ns.engine */ getDefinitions: function () { return widgetDefinitions; }, /** * Returns definition of widget * @method getWidgetDefinition * @param {string} name * @static * @member ns.engine * @return {Object} */ getWidgetDefinition: function (name) { return widgetDefinitions[name]; }, defineWidget: defineWidget, getBinding: getBinding, getAllBindings: getAllBindings, setBinding: setBinding, removeBinding: removeBinding, removeAllBindings: removeAllBindings, /** * Clear bindings of widgets * @method _clearBindings * @static * @member ns.engine */ _clearBindings: function () { //clear and set references to the same object widgetBindingMap = {}; }, build: build, /** * Run engine * @method run * @static * @member ns.engine */ run: function () { stop(); eventUtils.fastOn(document, "create", createEventHandler); eventUtils.trigger(document, eventType.INIT, {tau: ns}); switch (document.readyState) { case "interactive": case "complete": build(); break; default: eventUtils.one(document, "DOMContentLoaded", build.bind(engine)); break; } }, /** * Build instance of widget and binding events * Returns error when empty element is passed * @method instanceWidget * @param {HTMLElement|string} [element] * @param {string} name * @param {Object} [options] * @return {?Object} * @static * @member ns.engine */ instanceWidget: function (element, name, options) { var binding, definition, argumentsTypes = getArgumentsTypes(arguments); // Map arguments with specific types to correct variables // Only name is required argument element = argumentsTypes.HTMLElement; name = argumentsTypes.string; options = argumentsTypes.object; // If element exists try to find existing binding if (element) { binding = getBinding(element, name); } // If didn't found binding build new widget if (!binding && widgetDefinitions[name]) { definition = widgetDefinitions[name]; element = processHollowWidget(element, definition, options); binding = getBinding(element, name); } else if (binding) { // if widget was built early we should set options delivered to constructor binding.option(options); } return binding; }, stop: stop, destroy: destroy, /** * Method to change build mode * @method setJustBuild * @param {boolean} newJustBuild * @static * @member ns.engine */ setJustBuild: function (newJustBuild) { // Set location hash to have a consistent behavior if (newJustBuild) { location.hash = "build"; } else { location.hash = ""; } justBuild = newJustBuild; }, /** * Method to get build mode * @method getJustBuild * @return {boolean} * @static * @member ns.engine */ getJustBuild: function () { return justBuild; }, _createEventHandler: createEventHandler }; engine.eventType = eventType; ns.engine = engine; }(window, window.document)); /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /*global window, define, ns */ /** * #Utility DOM * Utility object with function to DOM manipulation, CSS properties support * and DOM attributes support. * * # How to replace jQuery methods by ns methods * ## append vs appendNodes * * #### HTML code before manipulation * * @example *
*
Hello
*
And
*
Goodbye
*
* * #### jQuery manipulation * * @example * $( "#second" ).append( "Test" ); * #### ns manipulation * * @example * var context = document.getElementById("second"), * element = document.createElement("span"); * element.innerHTML = "Test"; * ns.util.DOM.appendNodes(context, element); * * #### HTML code after manipulation * * @example *
*
Hello
*
And * Test *
*
Goodbye
*
* * ## replaceWith vs replaceWithNodes * * #### HTML code before manipulation * * @example *
*
Hello
*
And
*
Goodbye
*
* * #### jQuery manipulation * * @example * $('#second').replaceWith("Test"); * * #### ns manipulation * * @example * var context = document.getElementById("second"), * element = document.createElement("span"); * element.innerHTML = "Test"; * ns.util.DOM.replaceWithNodes(context, element); * * #### HTML code after manipulation * * @example *
*
Hello
* Test *
Goodbye
*
* * ## before vs insertNodesBefore * * #### HTML code before manipulation * * @example *
*
Hello
*
And
*
Goodbye
*
* * #### jQuery manipulation * * @example * $( "#second" ).before( "Test" ); * * #### ns manipulation * * @example * var context = document.getElementById("second"), * element = document.createElement("span"); * element.innerHTML = "Test"; * ns.util.DOM.insertNodesBefore(context, element); * * #### HTML code after manipulation * * @example *
*
Hello
* Test *
And
*
Goodbye
*
* * ## wrapInner vs wrapInHTML * * #### HTML code before manipulation * * @example *
*
Hello
*
And
*
Goodbye
*
* * #### jQuery manipulation * * @example * $( "#second" ).wrapInner( "" ); * * #### ns manipulation * * @example * var element = document.getElementById("second"); * ns.util.DOM.wrapInHTML(element, ""); * * #### HTML code after manipulation * * @example *
*
Hello
*
* And *
*
Goodbye
*
* * @class ns.util.DOM * @author Jadwiga Sosnowska * @author Krzysztof Antoszek * @author Maciej Moczulski * @author Piotr Karny */ (function () { "use strict"; ns.util.DOM = ns.util.DOM || {}; }()); /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /*global window, ns, define */ /* * @author Jadwiga Sosnowska * @author Krzysztof Antoszek * @author Maciej Moczulski * @author Piotr Karny */ (function () { "use strict"; var selectors = ns.util.selectors, DOM = ns.util.DOM, NAMESPACE = "namespace"; /** * Returns given attribute from element or the closest parent, * which matches the selector. * @method inheritAttr * @member ns.util.DOM * @param {HTMLElement} element * @param {string} attr * @param {string} selector * @return {?string} * @static */ DOM.inheritAttr = function (element, attr, selector) { var value = element.getAttribute(attr), parent; if (!value) { parent = selectors.getClosestBySelector(element, selector); if (parent) { return parent.getAttribute(attr); } } return value; }; /** * Returns Number from properties described in html tag * @method getNumberFromAttribute * @member ns.util.DOM * @param {HTMLElement} element * @param {string} attribute * @param {string=} [type] auto type casting * @param {number} [defaultValue] default returned value * @static * @return {number} */ DOM.getNumberFromAttribute = function (element, attribute, type, defaultValue) { var value = element.getAttribute(attribute), result = defaultValue; if (!isNaN(value)) { if (type === "float") { value = parseFloat(value); if (!isNaN(value)) { result = value; } } else { value = parseInt(value, 10); if (!isNaN(value)) { result = value; } } } return result; }; function getDataName(name, skipData) { var _namespace = ns.getConfig(NAMESPACE), prefix = ""; if (!skipData) { prefix = "data-"; } return prefix + (_namespace ? _namespace + "-" : "") + name; } /** * Special function to set attribute and property in the same time * @method setAttribute * @param {HTMLElement} element * @param {string} name * @param {Mixed} value * @member ns.util.DOM * @static */ function setAttribute(element, name, value) { element[name] = value; element.setAttribute(name, value); } /** * This function sets value of attribute data-{namespace}-{name} for element. * If the namespace is empty, the attribute data-{name} is used. * @method setNSData * @param {HTMLElement} element Base element * @param {string} name Name of attribute * @param {string|number|boolean} value New value * @member ns.util.DOM * @static */ DOM.setNSData = function (element, name, value) { element.setAttribute(getDataName(name), value); }; /** * This function returns value of attribute data-{namespace}-{name} for element. * If the namespace is empty, the attribute data-{name} is used. * Method may return boolean in case of 'true' or 'false' strings as attribute value. * @method getNSData * @param {HTMLElement} element Base element * @param {string} name Name of attribute * @param {boolean} skipData * @member ns.util.DOM * @return {?string|boolean} * @static */ DOM.getNSData = function (element, name, skipData) { var value = element.getAttribute(getDataName(name, skipData)); if (value === "true") { return true; } if (value === "false") { return false; } return value; }; /** * This function returns true if attribute data-{namespace}-{name} for element is set * or false in another case. If the namespace is empty, attribute data-{name} is used. * @method hasNSData * @param {HTMLElement} element Base element * @param {string} name Name of attribute * @member ns.util.DOM * @return {boolean} * @static */ DOM.hasNSData = function (element, name) { return element.hasAttribute(getDataName(name)); }; /** * Get or set value on data attribute. * @method nsData * @param {HTMLElement} element * @param {string} name * @param {?Mixed} [value] * @static * @member ns.util.DOM */ DOM.nsData = function (element, name, value) { if (value === undefined) { return DOM.getNSData(element, name); } else { return DOM.setNSData(element, name, value); } }; /** * This function removes attribute data-{namespace}-{name} from element. * If the namespace is empty, attribute data-{name} is used. * @method removeNSData * @param {HTMLElement} element Base element * @param {string} name Name of attribute * @member ns.util.DOM * @static */ DOM.removeNSData = function (element, name) { element.removeAttribute(getDataName(name)); }; /** * Returns object with all data-* attributes of element * @method getData * @param {HTMLElement} element Base element * @member ns.util.DOM * @return {Object} * @static */ DOM.getData = function (element) { var dataPrefix = "data-", data = {}, attributes = element.attributes, attribute, nodeName, value, i, length = attributes.length, lowerCaseValue; for (i = 0; i < length; i++) { attribute = attributes.item(i); nodeName = attribute.nodeName; if (nodeName.indexOf(dataPrefix) > -1) { value = attribute.value; lowerCaseValue = value.toLowerCase(); if (lowerCaseValue === "true") { value = true; } else if (lowerCaseValue === "false") { value = false; } data[nodeName.replace(dataPrefix, "")] = value; } } return data; }; /** * Special function to remove attribute and property in the same time * @method removeAttribute * @param {HTMLElement} element * @param {string} name * @member ns.util.DOM * @static */ DOM.removeAttribute = function (element, name) { element.removeAttribute(name); element[name] = false; }; DOM.setAttribute = setAttribute; /** * Special function to set attributes and properties in the same time * @method setAttribute * @param {HTMLElement} element * @param {Object} values * @member ns.util.DOM * @static */ DOM.setAttributes = function (element, values) { var i, names = Object.keys(values), name, len; for (i = 0, len = names.length; i < len; i++) { name = names[i]; setAttribute(element, name, values[name]); } }; }()); /*global window, ns, define, RegExp */ /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * #Path Utility * Object helps work with paths. * @class ns.util.path * @static * @author Tomasz Lukawski * @author Maciej Urbanski * @author Piotr Karny */ (function (window, document, ns) { "use strict"; var utilsObject = ns.util.object, /** * Local alias for ns.util.selectors * @property {Object} utilsSelectors Alias for {@link ns.util.selectors} * @member ns.util.path * @static * @private */ utilsSelectors = ns.util.selectors, /** * Local alias for ns.util.DOM * @property {Object} utilsDOM Alias for {@link ns.util.DOM} * @member ns.util.path * @static * @private */ utilsDOM = ns.util.DOM, /** * Cache for document base element * @member ns.util.path * @property {HTMLBaseElement} base * @static * @private */ base, /** * location object * @property {Object} location * @static * @private * @member ns.util.path */ location = {}, path = { /** * href part for mark state * @property {string} [uiStateKey="&ui-state"] * @static * @member ns.util.path */ uiStateKey: "&ui-state", // This scary looking regular expression parses an absolute URL or its relative // variants (protocol, site, document, query, and hash), into the various // components (protocol, host, path, query, fragment, etc that make up the // URL as well as some other commonly used sub-parts. When used with RegExp.exec() // or String.match, it parses the URL into a results array that looks like this: // // [0]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread# // msg-content?param1=true¶m2=123 // [1]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread // [2]: http://jblas:password@mycompany.com:8080/mail/inbox // [3]: http://jblas:password@mycompany.com:8080 // [4]: http: // [5]: // // [6]: jblas:password@mycompany.com:8080 // [7]: jblas:password // [8]: jblas // [9]: password // [10]: mycompany.com:8080 // [11]: mycompany.com // [12]: 8080 // [13]: /mail/inbox // [14]: /mail/ // [15]: inbox // [16]: ?msg=1234&type=unread // [17]: #msg-content?param1=true¶m2=123 // [18]: #msg-content // [19]: ?param1=true¶m2=123 // /** * @property {RegExp} urlParseRE Regular expression for parse URL * @member ns.util.path * @static */ urlParseRE: /^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)((#[^\?]*)(\?.*)?)?/, /** * Abstraction to address xss (Issue #4787) by removing the authority in * browsers that auto decode it. All references to location.href should be * replaced with a call to this method so that it can be dealt with properly here * @method getLocation * @param {string|Object} [url] * @return {string} * @member ns.util.path */ getLocation: function (url) { var uri = this.parseUrl(url || window.location.href), hash = uri.hash, search = uri.hashSearch; // mimic the browser with an empty string when the hash and hashSearch are empty hash = hash === "#" && !search ? "" : hash; location = uri; // Make sure to parse the url or the location object for the hash because using // location.hash is automatically decoded in firefox, the rest of the url should be from the // object (location unless we're testing) to avoid the inclusion of the authority return uri.protocol + "//" + uri.host + uri.pathname + uri.search + hash + search; }, /** * Return the original document url * @method getDocumentUrl * @member ns.util.path * @param {boolean} [asParsedObject=false] * @return {string|Object} * @static */ getDocumentUrl: function (asParsedObject) { return asParsedObject ? utilsObject.copy(path.documentUrl) : path.documentUrl.href; }, /** * Parse a location into a structure * @method parseLocation * @return {Object} * @member ns.util.path */ parseLocation: function () { return this.parseUrl(this.getLocation()); }, /** * Parse a URL into a structure that allows easy access to * all of the URL components by name. * If we're passed an object, we'll assume that it is * a parsed url object and just return it back to the caller. * @method parseUrl * @member ns.util.path * @param {string|Object} url * @return {Object} uri record * @return {string} return.href * @return {string} return.hrefNoHash * @return {string} return.hrefNoSearch * @return {string} return.domain * @return {string} return.protocol * @return {string} return.doubleSlash * @return {string} return.authority * @return {string} return.username * @return {string} return.password * @return {string} return.host * @return {string} return.hostname * @return {string} return.port * @return {string} return.pathname * @return {string} return.directory * @return {string} return.filename * @return {string} return.search * @return {string} return.hash * @return {string} return.hashSearch * @static */ parseUrl: function (url) { var matches; if (typeof url === "object") { return url; } matches = path.urlParseRE.exec(url || "") || []; // Create an object that allows the caller to access the sub-matches // by name. Note that IE returns an empty string instead of undefined, // like all other browsers do, so we normalize everything so its consistent // no matter what browser we're running on. return { href: matches[0] || "", hrefNoHash: matches[1] || "", hrefNoSearch: matches[2] || "", domain: matches[3] || "", protocol: matches[4] || "", doubleSlash: matches[5] || "", authority: matches[6] || "", username: matches[8] || "", password: matches[9] || "", host: matches[10] || "", hostname: matches[11] || "", port: matches[12] || "", pathname: matches[13] || "", directory: matches[14] || "", filename: matches[15] || "", search: matches[16] || "", hash: matches[18] || "", hashSearch: matches[19] || "" }; }, /** * Turn relPath into an absolute path. absPath is * an optional absolute path which describes what * relPath is relative to. * @method makePathAbsolute * @member ns.util.path * @param {string} relPath * @param {string} [absPath=""] * @return {string} * @static */ makePathAbsolute: function (relPath, absPath) { var absStack, relStack, directory, i; if (relPath && relPath.charAt(0) === "/") { return relPath; } relPath = relPath || ""; absPath = absPath ? absPath.replace(/^\/|(\/[^\/]*|[^\/]+)$/g, "") : ""; absStack = absPath ? absPath.split("/") : []; relStack = relPath.split("/"); for (i = 0; i < relStack.length; i++) { directory = relStack[i]; switch (directory) { case ".": break; case "..": if (absStack.length) { absStack.pop(); } break; default: absStack.push(directory); break; } } return "/" + absStack.join("/"); }, /** * Returns true if both urls have the same domain. * @method isSameDomain * @member ns.util.path * @param {string|Object} absUrl1 * @param {string|Object} absUrl2 * @return {boolean} * @static */ isSameDomain: function (absUrl1, absUrl2) { return path.parseUrl(absUrl1).domain === path.parseUrl(absUrl2).domain; }, /** * Returns true for any relative variant. * @method isRelativeUrl * @member ns.util.path * @param {string|Object} url * @return {boolean} * @static */ isRelativeUrl: function (url) { // All relative Url variants have one thing in common, no protocol. return path.parseUrl(url).protocol === ""; }, /** * Returns true for an absolute url. * @method isAbsoluteUrl * @member ns.util.path * @param {string} url * @return {boolean} * @static */ isAbsoluteUrl: function (url) { return path.parseUrl(url).protocol !== ""; }, /** * Turn the specified relative URL into an absolute one. This function * can handle all relative variants (protocol, site, document, query, fragment). * @method makeUrlAbsolute * @member ns.util.path * @param {string} relUrl * @param {string} absUrl * @return {string} * @static */ makeUrlAbsolute: function (relUrl, absUrl) { var relObj, absObj, protocol, doubleSlash, authority, hasPath, pathname, search, hash; if (!path.isRelativeUrl(relUrl)) { return relUrl; } relObj = path.parseUrl(relUrl); absObj = path.parseUrl(absUrl); protocol = relObj.protocol || absObj.protocol; doubleSlash = relObj.protocol ? relObj.doubleSlash : (relObj.doubleSlash || absObj.doubleSlash); authority = relObj.authority || absObj.authority; hasPath = relObj.pathname !== ""; pathname = path.makePathAbsolute(relObj.pathname || absObj.filename, absObj.pathname); search = relObj.search || (!hasPath && absObj.search) || ""; hash = relObj.hash; return protocol + doubleSlash + authority + pathname + search + hash; }, /** * Add search (aka query) params to the specified url. * If page is embedded page, search query will be added after * hash tag. It's allowed to add query content for both external * pages and embedded pages. * Examples: * http://domain/path/index.html#embedded?search=test * http://domain/path/external.html?s=query#embedded?s=test * @method addSearchParams * @member ns.util.path * @param {string|Object} url * @param {Object|string} params * @return {string} */ addSearchParams: function (url, params) { var urlObject = path.parseUrl(url), paramsString = (typeof params === "object") ? this.getAsURIParameters(params) : params, searchChar, urlObjectHash = urlObject.hash; if (path.isEmbedded(url) && paramsString.length > 0) { searchChar = urlObject.hashSearch || "?"; return urlObject.hrefNoHash + (urlObjectHash || "") + searchChar + (searchChar.charAt(searchChar.length - 1) === "?" ? "" : "&") + paramsString; } searchChar = urlObject.search || "?"; return urlObject.hrefNoSearch + searchChar + (searchChar.charAt(searchChar.length - 1) === "?" ? "" : "&") + paramsString + (urlObjectHash || ""); }, /** * Add search params to the specified url with hash * @method addHashSearchParams * @member ns.util.path * @param {string|Object} url * @param {Object|string} params * @return {string} */ addHashSearchParams: function (url, params) { var urlObject = path.parseUrl(url), paramsString = (typeof params === "object") ? path.getAsURIParameters(params) : params, hash = urlObject.hash, searchChar = hash ? (hash.indexOf("?") < 0 ? hash + "?" : hash + "&") : "#?"; return urlObject.hrefNoHash + searchChar + (searchChar.charAt(searchChar.length - 1) === "?" ? "" : "&") + paramsString; }, /** * Convert absolute Url to data Url * - for embedded pages strips parameters * - for the same domain as document base remove domain * otherwise returns decoded absolute Url * @method convertUrlToDataUrl * @member ns.util.path * @param {string} absUrl * @param {boolean} dialogHashKey * @param {Object} documentBase uri structure * @return {string} * @static */ convertUrlToDataUrl: function (absUrl, dialogHashKey, documentBase) { var urlObject = path.parseUrl(absUrl); if (path.isEmbeddedPage(urlObject, !!dialogHashKey)) { // Keep hash and search data for embedded page return path.getFilePath(urlObject.hash + urlObject.hashSearch, dialogHashKey); } documentBase = documentBase || path.documentBase; if (path.isSameDomain(urlObject, documentBase)) { return urlObject.hrefNoHash.replace(documentBase.domain, ""); } return window.decodeURIComponent(absUrl); }, /** * Get path from current hash, or from a file path * @method get * @member ns.util.path * @param {string} newPath * @return {string} */ get: function (newPath) { if (newPath === undefined) { newPath = this.parseLocation().hash; } return this.stripHash(newPath).replace(/[^\/]*\.[^\/*]+$/, ""); }, /** * Test if a given url (string) is a path * NOTE might be exceptionally naive * @method isPath * @member ns.util.path * @param {string} url * @return {boolean} * @static */ isPath: function (url) { return (/\//).test(url); }, /** * Return a url path with the window's location protocol/hostname/pathname removed * @method clean * @member ns.util.path * @param {string} url * @param {Object} documentBase uri structure * @return {string} * @static */ clean: function (url, documentBase) { return url.replace(documentBase.domain, ""); }, /** * Just return the url without an initial # * @method stripHash * @member ns.util.path * @param {string} url * @return {string} * @static */ stripHash: function (url) { return url.replace(/^#/, ""); }, /** * Return the url without an query params * @method stripQueryParams * @member ns.util.path * @param {string} url * @return {string} * @static */ stripQueryParams: function (url) { return url.replace(/\?.*$/, ""); }, /** * Validation proper hash * @method isHashValid * @member ns.util.path * @param {string} hash * @static */ isHashValid: function (hash) { return (/^#[^#]+$/).test(hash); }, /** * Check whether a url is referencing the same domain, or an external domain or different * protocol could be mailto, etc * @method isExternal * @member ns.util.path * @param {string|Object} url * @param {Object} documentUrl uri object * @return {boolean} * @static */ isExternal: function (url, documentUrl) { var urlObject = path.parseUrl(url); return urlObject.protocol && urlObject.domain !== documentUrl.domain ? true : false; }, /** * Check if the url has protocol * @method hasProtocol * @member ns.util.path * @param {string} url * @return {boolean} * @static */ hasProtocol: function (url) { return (/^(:?\w+:)/).test(url); }, /** * Check if the url refers to embedded content * @method isEmbedded * @member ns.util.path * @param {string} url * @return {boolean} * @static */ isEmbedded: function (url) { var urlObject = path.parseUrl(url); if (urlObject.protocol !== "") { return (!path.isPath(urlObject.hash) && !!urlObject.hash && (urlObject.hrefNoHash === path.parseLocation().hrefNoHash)); } return (/\?.*#|^#/).test(urlObject.href); }, /** * Get the url as it would look squashed on to the current resolution url * @method squash * @member ns.util.path * @param {string} url * @param {string} [resolutionUrl=undefined] * @return {string} */ squash: function (url, resolutionUrl) { var href, cleanedUrl, search, stateIndex, isPath = this.isPath(url), uri = this.parseUrl(url), preservedHash = uri.hash, uiState = ""; // produce a url against which we can resole the provided path resolutionUrl = resolutionUrl || (path.isPath(url) ? path.getLocation() : path.getDocumentUrl()); // If the url is anything but a simple string, remove any preceding hash // eg #foo/bar -> foo/bar // #foo -> #foo cleanedUrl = isPath ? path.stripHash(url) : url; // If the url is a full url with a hash check if the parsed hash is a path // if it is, strip the #, and use it otherwise continue without change cleanedUrl = path.isPath(uri.hash) ? path.stripHash(uri.hash) : cleanedUrl; // Split the UI State keys off the href stateIndex = cleanedUrl.indexOf(this.uiStateKey); // store the ui state keys for use if (stateIndex > -1) { uiState = cleanedUrl.slice(stateIndex); cleanedUrl = cleanedUrl.slice(0, stateIndex); } // make the cleanedUrl absolute relative to the resolution url href = path.makeUrlAbsolute(cleanedUrl, resolutionUrl); // grab the search from the resolved url since parsing from // the passed url may not yield the correct result search = this.parseUrl(href).search; // @TODO all this crap is terrible, clean it up if (isPath) { // reject the hash if it's a path or it's just a dialog key if (path.isPath(preservedHash) || preservedHash.replace("#", "").indexOf(this.uiStateKey) === 0) { preservedHash = ""; } // Append the UI State keys where it exists and it's been removed // from the url if (uiState && preservedHash.indexOf(this.uiStateKey) === -1) { preservedHash += uiState; } // make sure that pound is on the front of the hash if (preservedHash.indexOf("#") === -1 && preservedHash !== "") { preservedHash = "#" + preservedHash; } // reconstruct each of the pieces with the new search string and hash href = path.parseUrl(href); href = href.protocol + "//" + href.host + href.pathname + search + preservedHash; } else { href += href.indexOf("#") > -1 ? uiState : "#" + uiState; } return href; }, /** * Check if the hash is preservable * @method isPreservableHash * @member ns.util.path * @param {string} hash * @return {boolean} */ isPreservableHash: function (hash) { return hash.replace("#", "").indexOf(this.uiStateKey) === 0; }, /** * Escape weird characters in the hash if it is to be used as a selector * @method hashToSelector * @member ns.util.path * @param {string} hash * @return {string} * @static */ hashToSelector: function (hash) { var hasHash = (hash.substring(0, 1) === "#"); if (hasHash) { hash = hash.substring(1); } return (hasHash ? "#" : "") + hash.replace(new RegExp("([!\"#$%&'()*+,./:;<=>?@[\\]^`{|}~])", "g"), "\\$1"); }, /** * Check if the specified url refers to the first page in the main application document. * @method isFirstPageUrl * @member ns.util.path * @param {string} url * @param {HTMLElement} firstPageElement first page element * @param {string} documentBase uri structure * @param {boolean} documentBaseDiffers * @param {Object} documentUrl uri structure * @return {boolean} * @static */ isFirstPageUrl: function (url, firstPageElement, documentBase, documentBaseDiffers, documentUrl) { var urlStructure, samePath, firstPageId, hash; documentBase = documentBase === undefined ? path.documentBase : documentBase; documentBaseDiffers = documentBaseDiffers === undefined ? path.documentBaseDiffers : documentBaseDiffers; documentUrl = documentUrl === undefined ? path.documentUrl : documentUrl; // We only deal with absolute paths. urlStructure = path.parseUrl(path.makeUrlAbsolute(url, documentBase)); // Does the url have the same path as the document? samePath = urlStructure.hrefNoHash === documentUrl.hrefNoHash || (documentBaseDiffers && urlStructure.hrefNoHash === documentBase.hrefNoHash); // Get the id of the first page element if it has one. firstPageId = firstPageElement && firstPageElement.id || false; hash = urlStructure.hash; // The url refers to the first page if the path matches the document and // it either has no hash value, or the hash is exactly equal to the id of the // first page element. return samePath && (!hash || hash === "#" || (firstPageId && hash.replace(/^#/, "") === firstPageId)); }, /** * Some embedded browsers, like the web view in Phone Gap, allow cross-domain XHR * requests if the document doing the request was loaded via the file:// protocol. * This is usually to allow the application to "phone home" and fetch app specific * data. We normally let the browser handle external/cross-domain urls, but if the * allowCrossDomainPages option is true, we will allow cross-domain http/https * requests to go through our page loading logic. * @method isPermittedCrossDomainRequest * @member ns.util.path * @param {Object} docUrl * @param {string} reqUrl * @return {boolean} * @static */ isPermittedCrossDomainRequest: function (docUrl, reqUrl) { return ns.getConfig("allowCrossDomainPages", false) && docUrl.protocol === "file:" && reqUrl.search(/^https?:/) !== -1; }, /** * Convert a object data to URI parameters * @method getAsURIParameters * @member ns.util.path * @param {Object} data * @return {string} * @static */ getAsURIParameters: function (data) { var url = "", key; for (key in data) { if (data.hasOwnProperty(key)) { url += encodeURIComponent(key) + "=" + encodeURIComponent(data[key]) + "&"; } } return url.substring(0, url.length - 1); }, /** * Document Url * @member ns.util.path * @property {string|null} documentUrl */ documentUrl: null, /** * The document base differs * @member ns.util.path * @property {boolean} documentBaseDiffers */ documentBaseDiffers: false, /** * Set location hash to path * @method set * @member ns.util.path * @param {string} path * @static */ set: function (path) { location.hash = path; }, /** * Return the substring of a file path before the sub-page key, * for making a server request * @method getFilePath * @member ns.util.path * @param {string} path * @param {string} dialogHashKey * @return {string} * @static */ getFilePath: function (path, dialogHashKey) { var splitKey = "&" + ns.getConfig("subPageUrlKey", ""); return path && path.split(splitKey)[0].split(dialogHashKey)[0]; }, /** * Remove the preceding hash, any query params, and dialog notations * @method cleanHash * @member ns.util.path * @param {string} hash * @param {string} dialogHashKey * @return {string} * @static */ cleanHash: function (hash, dialogHashKey) { return path.stripHash(hash.replace(/\?.*$/, "").replace(dialogHashKey, "")); }, /** * Check if url refers to the embedded page * @method isEmbeddedPage * @member ns.util.path * @param {string} url * @param {boolean} allowEmbeddedOnlyBaseDoc * @return {boolean} * @static */ isEmbeddedPage: function (url, allowEmbeddedOnlyBaseDoc) { var urlObject = path.parseUrl(url); //if the path is absolute, then we need to compare the url against //both the documentUrl and the documentBase. The main reason for this //is that links embedded within external documents will refer to the //application document, whereas links embedded within the application //document will be resolved against the document base. if (urlObject.protocol !== "") { return (urlObject.hash && (allowEmbeddedOnlyBaseDoc ? urlObject.hrefNoHash === path.documentUrl.hrefNoHash : urlObject.hrefNoHash === path.parseLocation().hrefNoHash)); } return (/^#/).test(urlObject.href); } }; path.documentUrl = path.parseLocation(); base = document.querySelector("base"); /** * The document base URL for the purposes of resolving relative URLs, * and the name of the default browsing context for the purposes of * following hyperlinks * @member ns.util.path * @property {Object} documentBase uri structure * @static */ path.documentBase = base ? path.parseUrl(path.makeUrlAbsolute(base.getAttribute("href"), path.documentUrl.href)) : path.documentUrl; path.documentBaseDiffers = (path.documentUrl.hrefNoHash !== path.documentBase.hrefNoHash); /** * Get document base * @method getDocumentBase * @member ns.util.path * @param {boolean} [asParsedObject=false] * @return {string|Object} * @static */ path.getDocumentBase = function (asParsedObject) { return asParsedObject ? utilsObject.copy(path.documentBase) : path.documentBase.href; }; /** * Find the closest page and extract out its url * @method getClosestBaseUrl * @member ns.util.path * @param {HTMLElement} element * @param {string} selector * @return {string} * @static */ path.getClosestBaseUrl = function (element, selector) { // Find the closest page and extract out its url. var url = utilsDOM.getNSData(utilsSelectors.getClosestBySelector(element, selector), "url"), baseUrl = path.documentBase.hrefNoHash; if (!ns.getConfig("dynamicBaseEnabled", true) || !url || !path.isPath(url)) { url = baseUrl; } return path.makeUrlAbsolute(url, baseUrl); }; ns.util.path = path; }(window, window.document, ns)); /** * */ (function (window) { "use strict"; var isarray = Array.isArray; pathToRegexp.parse = parse pathToRegexp.compile = compile pathToRegexp.tokensToFunction = tokensToFunction pathToRegexp.tokensToRegExp = tokensToRegExp /** * Start original file * Licence MIT * https://github.com/pillarjs/path-to-regexp */ /** * The main path matching regexp utility. * * @type {RegExp} */ var PATH_REGEXP = new RegExp([ // Match escaped characters that would otherwise appear in future matches. // This allows the user to escape special characters that won't transform. '(\\\\.)', // Match Express-style parameters and un-named parameters with a prefix // and optional suffixes. Matches appear as: // // "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?"] // "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined] '([\\/.])?(?:\\:(\\w+)(?:\\(((?:\\\\.|[^)])*)\\))?|\\(((?:\\\\.|[^)])*)\\))([+*?])?' ].join('|'), 'g') /** * Parse a string for the raw tokens. * * @param {String} str * @return {Array} */ function parse (str) { var tokens = [] var key = 0 var index = 0 var path = '' var res while ((res = PATH_REGEXP.exec(str)) != null) { var m = res[0] var escaped = res[1] var offset = res.index path += str.slice(index, offset) index = offset + m.length // Ignore already escaped sequences. if (escaped) { path += escaped[1] continue } // Push the current path onto the tokens. if (path) { tokens.push(path) path = '' } var prefix = res[2] var name = res[3] var capture = res[4] var group = res[5] var suffix = res[6] var repeat = suffix === '+' || suffix === '*' var optional = suffix === '?' || suffix === '*' var delimiter = prefix || '/' tokens.push({ name: name || key++, prefix: prefix || '', delimiter: delimiter, optional: optional, repeat: repeat, pattern: escapeGroup(capture || group || '[^' + delimiter + ']+?') }) } // Match any characters still remaining. if (index < str.length) { path += str.substr(index) } // If the path exists, push it onto the end. if (path) { tokens.push(path) } return tokens } /** * Compile a string to a template function for the path. * * @param {String} str * @return {Function} */ function compile (str) { return tokensToFunction(parse(str)) } /** * Expose a method for transforming tokens into the path function. */ function tokensToFunction (tokens) { // Compile all the tokens into regexps. var matches = new Array(tokens.length) // Compile all the patterns before compilation. for (var i = 0; i < tokens.length; i++) { if (typeof tokens[i] === 'object') { matches[i] = new RegExp('^' + tokens[i].pattern + '$') } } return function (obj) { var path = '' obj = obj || {} for (var i = 0; i < tokens.length; i++) { var key = tokens[i] if (typeof key === 'string') { path += key continue } var value = obj[key.name] if (value == null) { if (key.optional) { continue } else { throw new TypeError('Expected "' + key.name + '" to be defined') } } if (isarray(value)) { if (!key.repeat) { throw new TypeError('Expected "' + key.name + '" to not repeat') } if (value.length === 0) { if (key.optional) { continue } else { throw new TypeError('Expected "' + key.name + '" to not be empty') } } for (var j = 0; j < value.length; j++) { if (!matches[i].test(value[j])) { throw new TypeError('Expected all "' + key.name + '" to match "' + key.pattern + '"') } path += (j === 0 ? key.prefix : key.delimiter) + encodeURIComponent(value[j]) } continue } if (!matches[i].test(value)) { throw new TypeError('Expected "' + key.name + '" to match "' + key.pattern + '"') } path += key.prefix + encodeURIComponent(value) } return path } } /** * Escape a regular expression string. * * @param {String} str * @return {String} */ function escapeString (str) { return str.replace(/([.+*?=^!:${}()[\]|\/])/g, '\\$1') } /** * Escape the capturing group by escaping special characters and meaning. * * @param {String} group * @return {String} */ function escapeGroup (group) { return group.replace(/([=!:$\/()])/g, '\\$1') } /** * Attach the keys as a property of the regexp. * * @param {RegExp} re * @param {Array} keys * @return {RegExp} */ function attachKeys (re, keys) { re.keys = keys return re } /** * Get the flags for a regexp from the options. * * @param {Object} options * @return {String} */ function flags (options) { return options.sensitive ? '' : 'i' } /** * Pull out keys from a regexp. * * @param {RegExp} path * @param {Array} keys * @return {RegExp} */ function regexpToRegexp (path, keys) { // Use a negative lookahead to match only capturing groups. var groups = path.source.match(/\((?!\?)/g) if (groups) { for (var i = 0; i < groups.length; i++) { keys.push({ name: i, prefix: null, delimiter: null, optional: false, repeat: false, pattern: null }) } } return attachKeys(path, keys) } /** * Transform an array into a regexp. * * @param {Array} path * @param {Array} keys * @param {Object} options * @return {RegExp} */ function arrayToRegexp (path, keys, options) { var parts = [] for (var i = 0; i < path.length; i++) { parts.push(pathToRegexp(path[i], keys, options).source) } var regexp = new RegExp('(?:' + parts.join('|') + ')', flags(options)) return attachKeys(regexp, keys) } /** * Create a path regexp from string input. * * @param {String} path * @param {Array} keys * @param {Object} options * @return {RegExp} */ function stringToRegexp (path, keys, options) { var tokens = parse(path) var re = tokensToRegExp(tokens, options) // Attach keys back to the regexp. for (var i = 0; i < tokens.length; i++) { if (typeof tokens[i] !== 'string') { keys.push(tokens[i]) } } return attachKeys(re, keys) } /** * Expose a function for taking tokens and returning a RegExp. * * @param {Array} tokens * @param {Array} keys * @param {Object} options * @return {RegExp} */ function tokensToRegExp (tokens, options) { options = options || {} var strict = options.strict var end = options.end !== false var route = '' var lastToken = tokens[tokens.length - 1] var endsWithSlash = typeof lastToken === 'string' && /\/$/.test(lastToken) // Iterate over the tokens and create our regexp string. for (var i = 0; i < tokens.length; i++) { var token = tokens[i] if (typeof token === 'string') { route += escapeString(token) } else { var prefix = escapeString(token.prefix) var capture = token.pattern if (token.repeat) { capture += '(?:' + prefix + capture + ')*' } if (token.optional) { if (prefix) { capture = '(?:' + prefix + '(' + capture + '))?' } else { capture = '(' + capture + ')?' } } else { capture = prefix + '(' + capture + ')' } route += capture } } // In non-strict mode we allow a slash at the end of match. If the path to // match already ends with a slash, we remove it for consistency. The slash // is valid at the end of a path match, not in the middle. This is important // in non-ending mode, where "/test/" shouldn't match "/test//route". if (!strict) { route = (endsWithSlash ? route.slice(0, -2) : route) + '(?:\\/(?=$))?' } if (end) { route += '$' } else { // In non-ending mode, we need the capturing groups to match as much as // possible by using a positive lookahead to the end or next path segment. route += strict && endsWithSlash ? '' : '(?=\\/|$)' } return new RegExp('^' + route, flags(options)) } /** * Normalize the given path string, returning a regular expression. * * An empty array can be passed in for the keys, which will hold the * placeholder key descriptions. For example, using `/user/:id`, `keys` will * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`. * * @param {(String|RegExp|Array)} path * @param {Array} [keys] * @param {Object} [ * * options] * @return {RegExp} */ function pathToRegexp (path, keys, options) { keys = keys || [] if (!isarray(keys)) { options = keys keys = [] } else if (!options) { options = {} } if (path instanceof RegExp) { return regexpToRegexp(path, keys, options) } if (isarray(path)) { return arrayToRegexp(path, keys, options) } return stringToRegexp(path, keys, options) } /** * End original file */ window.pathToRegexp = pathToRegexp; }(window)); /*global define, ns */ /* * Copyright (c) 2010 - 2014 Samsung Electronics Co., Ltd. * License : MIT License V2 */ /** * #Path to Regexp Utility * Convert string to regexp and can match path string to one defined regex path * * Syntax of paths is the same as in Express for nodejs * * Library based on https://github.com/pillarjs/path-to-regexp * @class ns.util.pathToRegexp * @author Maciej Urbanski * */ (function (window) { "use strict"; ns.util.pathToRegexp = window.pathToRegexp; }(window)); /*global define, ns */ /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * #Router * Namespace for routers * @author Maciej Urbanski * @author Krzysztof Antoszek * @class ns.router */ (function () { "use strict"; ns.router = ns.router || {}; }()); /*global define, ns */ /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * #Route Namespace * Object contains rules for router. * * @class ns.router.route */ /* * @author Maciej Urbanski */ (function () { "use strict"; ns.router.route = ns.router.route || {}; }()); /*global window, ns, define, ns */ /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ /** * #History * Object controls history changes. * * @class ns.history * @author Maciej Urbanski */ (function (window) { "use strict"; var historyVolatileMode, object = ns.util.object, historyUid = 0, historyActiveIndex = 0, windowHistory = window.history, history = { /** * Property contains active state in history. * @property {Object} activeState * @static * @member ns.history */ activeState: null, /** * This method replaces or pushes state to history. * @method replace * @param {Object} state The state object * @param {string} stateTitle The title of state * @param {string} url The new history entry's URL * @static * @member ns.history */ replace: function (state, stateTitle, url) { var newState = object.merge({}, state, { uid: historyVolatileMode ? historyActiveIndex : ++historyUid, stateUrl: url, stateTitle: stateTitle }); windowHistory[historyVolatileMode ? "replaceState" : "pushState"](newState, stateTitle, url); history.setActive(newState); }, /** * This method moves backward through history. * @method back * @static * @member ns.history */ back: function () { windowHistory.back(); }, /** * This method sets active state. * @method setActive * @param {Object} state Activated state * @static * @member ns.history */ setActive: function (state) { if (state) { history.activeState = state; historyActiveIndex = state.uid; if (state.volatileRecord) { history.enableVolatileMode(); return; } } history.disableVolatileMode(); }, /** * This method returns "back" if state is in history or "forward" if it is new state. * @method getDirection * @param {Object} state Checked state * @return {"back"|"forward"} * @static * @member ns.history */ getDirection: function (state) { if (state) { return state.uid <= historyActiveIndex ? "back" : "forward"; } return "back"; }, /** * This method sets volatile mode to true. * @method enableVolatileMode * @static * @member ns.history */ enableVolatileMode: function () { historyVolatileMode = true; }, /** * This method sets volatile mode to false. * @method disableVolatileMode * @static * @member ns.history */ disableVolatileMode: function () { historyVolatileMode = false; } }; ns.history = history; }(window)); /*global CustomEvent, define, window, ns */ /*jslint plusplus: true, nomen: true, bitwise: true */ /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * #Virtual Mouse Events * Reimplementation of jQuery Mobile virtual mouse events. * * ##Purpose * It will let for users to register callbacks to the standard events like bellow, * without knowing if device support touch or mouse events * @class ns.event.vmouse */ /** * Triggered after mouse-down or touch-started. * @event vmousedown * @member ns.event.vmouse */ /** * Triggered when mouse-click or touch-end when touch-move didn't occur * @event vclick * @member ns.event.vmouse */ /** * Triggered when mouse-up or touch-end * @event vmouseup * @member ns.event.vmouse */ /** * Triggered when mouse-move or touch-move * @event vmousemove * @member ns.event.vmouse */ /** * Triggered when mouse-over or touch-start if went over coordinates * @event vmouseover * @member ns.event.vmouse */ /** * Triggered when mouse-out or touch-end * @event vmouseout * @member ns.event.vmouse */ /** * Triggered when mouse-cancel or touch-cancel and when scroll occur during touchmove * @event vmousecancel * @member ns.event.vmouse */ (function (window, document, ns) { "use strict"; /** * Object with default options * @property {Object} vmouse * @member ns.event.vmouse * @static * @private **/ var vmouse, /** * @property {Object} eventProps Contains the properties which are copied from the original * event to custom v-events * @member ns.event.vmouse * @static * @private **/ eventProps, /** * Indicates if the browser support touch events * @property {boolean} touchSupport * @member ns.event.vmouse * @static **/ touchSupport = window.hasOwnProperty("ontouchstart"), /** * @property {boolean} didScroll The flag tell us if the scroll event was triggered * @member ns.event.vmouse * @static * @private **/ didScroll, /** @property {HTMLElement} lastOver holds reference to last element that touch was over * @member ns.event.vmouse * @private */ lastOver = null, /** * @property {number} [startX=0] Initial data for touchstart event * @member ns.event.vmouse * @static * @private **/ startX = 0, /** * @property {number} [startY=0] Initial data for touchstart event * @member ns.event.vmouse * @private * @static **/ startY = 0, touchEventProps = ["clientX", "clientY", "pageX", "pageY", "screenX", "screenY"], KEY_CODES = { enter: 13 }; /** * Extends objects with other objects * @method copyProps * @param {Object} from Sets the original event * @param {Object} to Sets the new event * @param {Object} properties Sets the special properties for position * @param {Object} propertiesNames Describe parameters which will be copied from Original to * event * @private * @static * @member ns.event.vmouse */ function copyProps(from, to, properties, propertiesNames) { var i, length, descriptor, property; for (i = 0, length = propertiesNames.length; i < length; ++i) { property = propertiesNames[i]; if (isNaN(properties[property]) === false || isNaN(from[property]) === false) { descriptor = Object.getOwnPropertyDescriptor(to, property); if (property !== "detail" && (!descriptor || descriptor.writable)) { to[property] = properties[property] || from[property]; } } } } /** * Create custom event * @method createEvent * @param {string} newType gives a name for the new Type of event * @param {Event} original Event which trigger the new event * @param {Object} properties Sets the special properties for position * @return {Event} * @private * @static * @member ns.event.vmouse */ function createEvent(newType, original, properties) { var evt = new CustomEvent(newType, { "bubbles": original.bubbles, "cancelable": original.cancelable, "detail": original.detail }), originalType = original.type, changeTouches, touch, j = 0, len, prop; copyProps(original, evt, properties, eventProps); evt._originalEvent = original; if (originalType.indexOf("touch") !== -1) { originalType = original.touches; changeTouches = original.changedTouches; if (originalType && originalType.length) { touch = originalType[0]; } else { touch = (changeTouches && changeTouches.length) ? changeTouches[0] : null; } if (touch) { for (len = touchEventProps.length; j < len; j++) { prop = touchEventProps[j]; evt[prop] = touch[prop]; } } } return evt; } /** * Dispatch Events * @method fireEvent * @param {string} eventName event name * @param {Event} evt original event * @param {Object} [properties] Sets the special properties for position * @return {boolean} * @private * @static * @member ns.event.vmouse */ function fireEvent(eventName, evt, properties) { return evt.target.dispatchEvent(createEvent(eventName, evt, properties || {})); } eventProps = [ "currentTarget", "detail", "button", "buttons", "clientX", "clientY", "offsetX", "offsetY", "pageX", "pageY", "screenX", "screenY", "toElement", "which" ]; vmouse = { /** * Sets the distance of pixels after which the scroll event will be successful * @property {number} [eventDistanceThreshold=10] * @member ns.event.vmouse * @static */ eventDistanceThreshold: 10, touchSupport: touchSupport }; /** * Handle click down * @method handleDown * @param {Event} evt * @private * @static * @member ns.event.vmouse */ function handleDown(evt) { fireEvent("vmousedown", evt); } /** * Prepare position of event for keyboard events. * @method preparePositionForClick * @param {Event} event * @return {?Object} options * @private * @static * @member ns.event.vmouse */ function preparePositionForClick(event) { var x = event.clientX, y = event.clientY; // event comes from keyboard if (!x && !y) { return preparePositionForEvent(event); } } /** * Handle click * @method handleClick * @param {Event} evt * @private * @static * @member ns.event.vmouse */ function handleClick(evt) { fireEvent("vclick", evt, preparePositionForClick(evt)); } /** * Handle click up * @method handleUp * @param {Event} evt * @private * @static * @member ns.event.vmouse */ function handleUp(evt) { fireEvent("vmouseup", evt); } /** * Handle click move * @method handleMove * @param {Event} evt * @private * @static * @member ns.event.vmouse */ function handleMove(evt) { fireEvent("vmousemove", evt); } /** * Handle click over * @method handleOver * @param {Event} evt * @private * @static * @member ns.event.vmouse */ function handleOver(evt) { fireEvent("vmouseover", evt); } /** * Handle click out * @method handleOut * @param {Event} evt * @private * @static * @member ns.event.vmouse */ function handleOut(evt) { fireEvent("vmouseout", evt); } /** * Handle touch start * @method handleTouchStart * @param {Event} evt * @private * @static * @member ns.event.vmouse */ function handleTouchStart(evt) { var touches = evt.touches, firstTouch; //if touches are registered and we have only one touch if (touches && touches.length === 1) { didScroll = false; firstTouch = touches[0]; startX = firstTouch.pageX; startY = firstTouch.pageY; // Check if we have touched something on our page // @TODO refactor for multi touch /* over = document.elementFromPoint(startX, startY); if (over) { lastOver = over; fireEvent("vmouseover", evt); } */ fireEvent("vmousedown", evt); } } /** * Handle touch end * @method handleTouchEnd * @param {Event} evt * @private * @static * @member ns.event.vmouse */ function handleTouchEnd(evt) { var touches = evt.touches; if (touches && touches.length === 0) { fireEvent("vmouseup", evt); fireEvent("vmouseout", evt); // Reset flag for last over element lastOver = null; } } /** * Handle touch move * @method handleTouchMove * @param {Event} evt * @private * @static * @member ns.event.vmouse */ function handleTouchMove(evt) { var over, firstTouch = evt.touches && evt.touches[0], didCancel = didScroll, //sets the threshold, based on which we consider if it was the touch-move event moveThreshold = vmouse.eventDistanceThreshold; /** * Ignore the touch which has identifier other than 0. * Only first touch has control others are ignored. * Patch for webkit behavior where touchmove event * is triggered between touchend events * if there is multi touch. */ if ((firstTouch === undefined) || firstTouch.identifier > 0) { //evt.preventDefault(); // cant preventDefault passive events!!! evt.stopPropagation(); return; } didScroll = didScroll || //check in both axes X,Y if the touch-move event occur (Math.abs(firstTouch.pageX - startX) > moveThreshold || Math.abs(firstTouch.pageY - startY) > moveThreshold); // detect over event // for compatibility with mouseover because "touchenter" fires only once // @TODO Handle many touches over = document.elementFromPoint(firstTouch.pageX, firstTouch.pageY); if (over && lastOver !== over) { lastOver = over; fireEvent("vmouseover", evt); } //if didScroll occur and wasn't canceled then trigger touchend otherwise just touchmove if (didScroll && !didCancel) { fireEvent("vmousecancel", evt); lastOver = null; } fireEvent("vmousemove", evt); } /** * Handle Scroll * @method handleScroll * @param {Event} evt * @private * @static * @member ns.event.vmouse */ function handleScroll(evt) { if (!didScroll) { fireEvent("vmousecancel", evt); } didScroll = true; } /** * Handle touch cancel * @method handleTouchCancel * @param {Event} evt * @private * @static * @member ns.event.vmouse */ function handleTouchCancel(evt) { fireEvent("vmousecancel", evt); lastOver = null; } /** * Prepare position of event for keyboard events. * @method preparePositionForEvent * @param {Event} event * @return {Object} properties * @private * @static * @member ns.event.vmouse */ function preparePositionForEvent(event) { var targetRect = event.target && event.target.getBoundingClientRect(), properties = {}; if (targetRect) { properties = { "clientX": targetRect.left + targetRect.width / 2, "clientY": targetRect.top + targetRect.height / 2, "which": 1 }; } return properties; } /** * Handle key up * @method handleKeyUp * @param {Event} event * @private * @static * @member ns.event.vmouse */ function handleKeyUp(event) { var properties; if (event.keyCode === KEY_CODES.enter) { properties = preparePositionForEvent(event); fireEvent("vmouseup", event, properties); fireEvent("vclick", event, properties); } } /** * Handle key down * @method handleKeyDown * @param {Event} event * @private * @static * @member ns.event.vmouse */ function handleKeyDown(event) { if (event.keyCode === KEY_CODES.enter) { fireEvent("vmousedown", event, preparePositionForEvent(event)); } } /** * Binds events common to mouse and touch to support virtual mouse. * @method bindCommonEvents * @static * @member ns.event.vmouse */ vmouse.bindCommonEvents = function () { document.addEventListener("keyup", handleKeyUp, true); document.addEventListener("keydown", handleKeyDown, true); document.addEventListener("scroll", handleScroll, true); document.addEventListener("click", handleClick, true); }; // @TODO delete touchSupport flag and attach touch and mouse listeners, // @TODO check if v-events are not duplicated if so then called only once /** * Binds touch events to support virtual mouse. * @method bindTouch * @static * @member ns.event.vmouse */ vmouse.bindTouch = function () { document.addEventListener("touchstart", handleTouchStart, true); document.addEventListener("touchend", handleTouchEnd, true); document.addEventListener("touchmove", handleTouchMove, true); document.addEventListener("touchcancel", handleTouchCancel, true); }; /** * Binds mouse events to support virtual mouse. * @method bindMouse * @static * @member ns.event.vmouse */ vmouse.bindMouse = function () { document.addEventListener("mousedown", handleDown, true); document.addEventListener("mouseup", handleUp, true); document.addEventListener("mousemove", handleMove, true); document.addEventListener("mouseover", handleOver, true); document.addEventListener("mouseout", handleOut, true); }; /** * Unbinds touch events to support virtual mouse. * @method unbindTouch * @static * @member ns.event.vmouse */ vmouse.unbindTouch = function () { document.removeEventListener("touchstart", handleTouchStart, true); document.removeEventListener("touchend", handleTouchEnd, true); document.removeEventListener("touchmove", handleTouchMove, true); document.removeEventListener("touchcancel", handleTouchCancel, true); document.removeEventListener("click", handleClick, true); }; /** * Unbinds mouse events to support virtual mouse. * @method unbindMouse * @static * @member ns.event.vmouse */ vmouse.unbindMouse = function () { document.removeEventListener("mousedown", handleDown, true); document.removeEventListener("mouseup", handleUp, true); document.removeEventListener("mousemove", handleMove, true); document.removeEventListener("mouseover", handleOver, true); document.removeEventListener("mouseout", handleOut, true); document.removeEventListener("keyup", handleKeyUp, true); document.removeEventListener("keydown", handleKeyDown, true); document.removeEventListener("scroll", handleScroll, true); document.removeEventListener("click", handleClick, true); }; ns.event.vmouse = vmouse; if (touchSupport) { vmouse.bindTouch(); } else { vmouse.bindMouse(); } vmouse.bindCommonEvents(); }(window, window.document, ns)); /*global window, define, ns */ /*jslint browser: true, nomen: true */ /** * # History manager * * Control events connected with history change and trigger events to controller * or router. * * @class ns.history.manager * @since 2.4 * @author Krzysztof Antoszek * @author Maciej Urbanski * @author Tomasz Lukawski */ /** * Event historystatechange * @event historystatechange * @class ns.history.manager */ /** * Event historyhashchange * @event historyhashchange * @class ns.history.manager */ /** * Event historyenabled * @event historyenabled * @class ns.history.manager */ /** * Event historydisabled * @event historydisabled * @class ns.history.manager */ (function (window, document) { "use strict"; var manager = Object.create(null), // we don't need the Object proto WINDOW_EVENT_POPSTATE = "popstate", WINDOW_EVENT_HASHCHANGE = "hashchange", DOC_EVENT_VCLICK = "vclick", LINK_SELECTOR = "a,tau-button", util = ns.util, history = ns.history, eventUtils = ns.event, selectorUtils = util.selectors, objectUtils = util.object, pathUtils = util.path, DOM = util.DOM, EVENT_STATECHANGE = "historystatechange", EVENT_HASHCHANGE = "historyhashchange", EVENT_ENABLED = "historyenabled", EVENT_DISABLED = "historydisabled", /** * Engine event types * @property {Object} events * @property {string} events.STATECHANGE="historystatechange" event name on history manager change state * @property {string} events.HASHCHANGE="historyhashchange" event name on history manager change hash * @property {string} events.ENABLED="historyenabled" event name on enable history manager * @property {string} events.DISABLED="historydisabled" event name on disable history manager * @static * @readonly * @member ns.history.manager */ events = { STATECHANGE: EVENT_STATECHANGE, HASHCHANGE: EVENT_HASHCHANGE, ENABLED: EVENT_ENABLED, DISABLED: EVENT_DISABLED }; manager.events = events; /** * Trigger event "historystatechange" on document * @param {Object} options * @return {boolean} */ function triggerStateChange(options) { return eventUtils.trigger(document, EVENT_STATECHANGE, options, true, true); } /** * Callback for link click * @param {Event} event * @return {boolean} */ function onLinkAction(event) { var target = event.target, link = selectorUtils.getClosestBySelector(target, LINK_SELECTOR), href, useDefaultUrlHandling, options, // this should be empty object but some utils that work on it rel; // require hasOwnProperty :( if (link && event.which === 1) { href = link.getAttribute("href"); rel = link.getAttribute("rel"); useDefaultUrlHandling = rel === "external" || link.hasAttribute("target"); if (!useDefaultUrlHandling) { options = DOM.getData(link); options.event = event; if (rel && !options.rel) { options.rel = rel; } else { rel = options.rel; } if (href && !options.href) { options.href = href; } if (rel === "popup" && link && !options.link) { options.link = link; } history.disableVolatileMode(); if (!triggerStateChange(options)) { // mark as handled // but not on back if (!rel || (rel !== "back")) { eventUtils.preventDefault(event); return false; } } } } return true; } /** * Callback on popstate event. * @param {Event} event */ function onPopState(event) { var state = event.state, lastState = history.activeState, options = {}, reverse, resultOfTigger = true, skipTriggerStateChange = false; if (manager.locked) { history.disableVolatileMode(); if (lastState) { history.replace(lastState, lastState.stateTitle, lastState.stateUrl); } } else if (state) { reverse = history.getDirection(state) === "back"; options = objectUtils.merge(options, state, { reverse: reverse, transition: reverse ? ((lastState && lastState.transition) || "none") : state.transition, fromHashChange: true }); if (lastState) { resultOfTigger = eventUtils.trigger(document, EVENT_HASHCHANGE, objectUtils.merge(options, {url: pathUtils.getLocation(), stateUrl: lastState.stateUrl}), true, true); // if EVENT HASHCHANGE has been triggered successfuly then skip trigger HistoryStateChange skipTriggerStateChange = resultOfTigger; } state.url = pathUtils.getLocation(); history.setActive(state); if (!skipTriggerStateChange) { options.event = event; triggerStateChange(options); } } } /** * Callback on "hashchange" event * @param {Event} event */ function onHashChange(event) { var newURL = event.newURL; if (newURL && history.activeState.url !== newURL) { triggerStateChange({href: newURL, fromHashChange: true, event: event}); } } /** * Inform that manager is enabled or not. * @property {boolean} [enabled=true] * @static * @since 2.4 * @member ns.history.manager */ manager.enabled = true; /** * Informs that manager is enabled or not. * * If manager is locked then not trigger events historystatechange. * @property {boolean} [locked=false] * @static * @since 2.4 * @member ns.history.manager */ manager.locked = false; /** * Locks history manager. * * Sets locked property to true. * * @example * tau.history.manager.lock(); * * @method lock * @static * @since 2.4 * @member ns.history.manager */ manager.lock = function () { this.locked = true; }; /** * Unlocks history manager. * * Sets locked property to false. * * @example * tau.history.manager.unlock(); * * @method unlock * @static * @since 2.4 * @member ns.history.manager */ manager.unlock = function () { this.locked = false; }; /** * Enables history manager. * * This method adds all event listeners connected with history manager. * * Event listeners: * * - popstate on window * - hashchange on window * - vclick on document * * After set event listeners method sets property enabled to true. * * @example * tau.history.manager.enable(); * // add event's listeners * // after click on link or hash change history manager will handle events * * @method enable * @static * @since 2.4 * @member ns.history.manager */ manager.enable = function () { document.addEventListener(DOC_EVENT_VCLICK, onLinkAction, false); window.addEventListener(WINDOW_EVENT_POPSTATE, onPopState, false); window.addEventListener(WINDOW_EVENT_HASHCHANGE, onHashChange, false); history.enableVolatileMode(); this.enabled = true; eventUtils.trigger(document, EVENT_ENABLED, this); }; /** * Disables history manager. * * This method removes all event listeners connected with history manager. * * After set event listeners method sets property enabled to true. * * @example * tau.history.manager.disable(); * // remove event's listeners * // after click on link or hash change history manager will not handle events * * @method disable * @static * @since 2.4 * @member ns.history.manager */ manager.disable = function () { document.removeEventListener(DOC_EVENT_VCLICK, onLinkAction, false); window.removeEventListener(WINDOW_EVENT_POPSTATE, onPopState, false); window.removeEventListener(WINDOW_EVENT_HASHCHANGE, onHashChange, false); history.disableVolatileMode(); this.enabled = false; eventUtils.trigger(document, EVENT_DISABLED, this); }; ns.history.manager = manager; }(window, window.document)); /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /*global define, ns */ /** * #String Utility * Utility helps work with strings. * @class ns.util.string */ (function () { "use strict"; var DASH_TO_UPPER_CASE_REGEXP = /-([a-z])/gi, UPPER_TO_DASH_CASE_REGEXP = /([A-Z])/g, arrayUtil = ns.util.array; /** * Callback method for regexp used in dashesToCamelCase method * @method toUpperCaseFn * @param {string} match * @param {string} value * @return {string} * @member ns.util.string * @static * @private */ function toUpperCaseFn(match, value) { return value.toLocaleUpperCase(); } /** * Callback method for regexp used in camelCaseToDashes method * @method toUpperCaseFn * @param {string} match * @param {string} value * @return {string} * @member ns.util.string * @static * @private */ function toLowerCaseFn(match, value) { return "-" + value.toLowerCase(); } /** * Changes dashes string to camel case string * @method firstToUpperCase * @param {string} str * @return {string} * @member ns.util.string * @static */ function dashesToCamelCase(str) { return str.replace(DASH_TO_UPPER_CASE_REGEXP, toUpperCaseFn); } /** * Changes camel case string to dashes string * @method camelCaseToDashes * @param {string} str * @return {string} * @member ns.util.string * @static */ function camelCaseToDashes(str) { return str.replace(UPPER_TO_DASH_CASE_REGEXP, toLowerCaseFn); } /** * Changes the first char in string to uppercase * @method firstToUpperCase * @param {string} str * @return {string} * @member ns.util.string * @static */ function firstToUpperCase(str) { return str.charAt(0).toLocaleUpperCase() + str.substring(1); } /** * Map different types to number if is possible. * @param {string|*} x * @return {*} */ function mapToNumber(x) { var parsed; if (x && (x + "").indexOf("%") === -1) { parsed = parseInt(x, 10); if (isNaN(parsed)) { parsed = null; } return parsed; } return x; } /** * Parses comma separated string to array * @method parseProperty * @param {string} property * @return {Array} containing number or null * @member ns.util.string * @static */ function parseProperty(property) { var arrayProperty; if (typeof property === "string") { arrayProperty = property.split(","); } else { arrayProperty = property || []; } return arrayUtil.map(arrayProperty, mapToNumber); } ns.util.string = { dashesToCamelCase: dashesToCamelCase, camelCaseToDashes: camelCaseToDashes, firstToUpperCase: firstToUpperCase, parseProperty: parseProperty }; }()); /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /*global window, ns, define */ /* * @author Jadwiga Sosnowska * @author Krzysztof Antoszek * @author Maciej Moczulski * @author Piotr Karny */ (function (window, ns) { "use strict"; var DOM = ns.util.DOM, stringUtil = ns.util.string, appStyleSheet; /** * Returns css property for element * @method getCSSProperty * @param {HTMLElement} element * @param {string} property * @param {string|number|null} [def=null] default returned value * @param {"integer"|"float"|null} [type=null] auto type casting * @return {string|number|null} * @member ns.util.DOM * @static */ function getCSSProperty(element, property, def, type) { var style = window.getComputedStyle(element), value, result = def; if (style) { value = style.getPropertyValue(property); if (value) { switch (type) { case "integer": value = parseInt(value, 10); if (!isNaN(value)) { result = value; } break; case "float": value = parseFloat(value); if (!isNaN(value)) { result = value; } break; default: result = value; break; } } } return result; } /** * Convert string to float or integer * @param {string} value * @return {number} */ function convertToNumber(value) { if ((value + "").indexOf(".") > -1) { return parseFloat(value); } return parseInt(value, 10); } /** * Extracts css properties from computed css for an element. * The properties values are applied to the specified * properties list (dictionary) * @method extractCSSProperties * @param {HTMLElement} element * @param {Object} properties * @param {?string} [pseudoSelector=null] * @param {boolean} [noConversion=false] * @member ns.util.DOM * @static */ function extractCSSProperties(element, properties, pseudoSelector, noConversion) { var style = window.getComputedStyle(element, pseudoSelector), property, value, newValue; for (property in properties) { if (properties.hasOwnProperty(property)) { value = style.getPropertyValue(property); newValue = convertToNumber(value); if (!isNaN(newValue) || !noConversion) { value = newValue; } properties[property] = value; } } } function getOffset(element, props, pseudoSelector, force, offsetProperty) { var originalDisplay, originalVisibility, originalPosition, offsetValue, style = element.style; if (style.display !== "none") { extractCSSProperties(element, props, pseudoSelector, true); offsetValue = element[offsetProperty]; } else if (force) { originalDisplay = style.display; originalVisibility = style.visibility; originalPosition = style.position; style.display = "block"; style.visibility = "hidden"; style.position = "relative"; extractCSSProperties(element, props, pseudoSelector, true); offsetValue = element[offsetProperty]; style.display = originalDisplay; style.visibility = originalVisibility; style.position = originalPosition; } return offsetValue; } /** * Returns elements height from computed style * @method getElementHeight * @param {HTMLElement} element * if null then the "inner" value is assigned * @param {"outer"|null} [type=null] * @param {boolean} [includeOffset=false] * @param {boolean} [includeMargin=false] * @param {?string} [pseudoSelector=null] * @param {boolean} [force=false] check even if element is hidden * @return {number} * @member ns.util.DOM * @static */ function getElementHeight(element, type, includeOffset, includeMargin, pseudoSelector, force) { var height = 0, outer = (type && type === "outer") || false, offsetHeight, property, props = { "height": 0, "margin-top": 0, "margin-bottom": 0, "padding-top": 0, "padding-bottom": 0, "border-top-width": 0, "border-bottom-width": 0, "box-sizing": "" }; if (element) { offsetHeight = getOffset(element, props, pseudoSelector, force, "offsetHeight"); for (property in props) { if (props.hasOwnProperty(property) && property !== "box-sizing") { props[property] = convertToNumber(props[property]); } } height += props["height"]; if (props["box-sizing"] !== "border-box") { height += props["padding-top"] + props["padding-bottom"]; } if (includeOffset) { height = offsetHeight; } else if (outer && props["box-sizing"] !== "border-box") { height += props["border-top-width"] + props["border-bottom-width"]; } if (includeMargin) { height += Math.max(0, props["margin-top"]) + Math.max(0, props["margin-bottom"]); } } return height; } /** * Returns elements width from computed style * @method getElementWidth * @param {HTMLElement} element * if null then the "inner" value is assigned * @param {"outer"|null} [type=null] * @param {boolean} [includeOffset=false] * @param {boolean} [includeMargin=false] * @param {?string} [pseudoSelector=null] * @param {boolean} [force=false] check even if element is hidden * @return {number} * @member ns.util.DOM * @static */ function getElementWidth(element, type, includeOffset, includeMargin, pseudoSelector, force) { var width = 0, value, offsetWidth, property, outer = (type && type === "outer") || false, props = { "width": 0, "margin-left": 0, "margin-right": 0, "padding-left": 0, "padding-right": 0, "border-left-width": 0, "border-right-width": 0, "box-sizing": "" }; if (element) { offsetWidth = getOffset(element, props, pseudoSelector, force, "offsetWidth"); for (property in props) { if (props.hasOwnProperty(property) && property !== "box-sizing") { value = parseFloat(props[property]); props[property] = value; } } width += props["width"]; if (props["box-sizing"] !== "border-box") { width += props["padding-left"] + props["padding-right"]; } if (includeOffset) { width = offsetWidth; } else if (outer && props["box-sizing"] !== "border-box") { width += props["border-left-width"] + props["border-right-width"]; } if (includeMargin) { width += Math.max(0, props["margin-left"]) + Math.max(0, props["margin-right"]); } } return width; } /** * Returns offset of element * @method getElementOffset * @param {HTMLElement} element * @return {Object} * @member ns.util.DOM * @static */ function getElementOffset(element) { var left = 0, top = 0, loopElement = element; do { top += loopElement.offsetTop; left += loopElement.offsetLeft; loopElement = loopElement.offsetParent; } while (loopElement !== null); return { top: top, left: left }; } /** * Check if element occupies place at view * @method isOccupiedPlace * @param {HTMLElement} element * @return {boolean} * @member ns.util.DOM * @static */ function isOccupiedPlace(element) { return !(element.offsetWidth <= 0 && element.offsetHeight <= 0); } /** * Set values for element with prefixes for browsers * @method setPrefixedStyle * @param {HTMLElement | CSSStyleRule} elementOrRule * @param {string} property * @param {string|Object|null} value * @member ns.util.DOM * @static */ function setPrefixedStyle(elementOrRule, property, value) { var style = elementOrRule.style, propertyForPrefix = property, values = (typeof value !== "object") ? { webkit: value, moz: value, o: value, ms: value, normal: value } : value; style.setProperty(property, values.normal); style.setProperty("-webkit-" + propertyForPrefix, values.webkit); style.setProperty("-moz-" + propertyForPrefix, values.moz); style.setProperty("-o-" + propertyForPrefix, values.o); style.setProperty("-ms-" + propertyForPrefix, values.ms); } /** * Get value from element with prefixes for browsers * @method getCSSProperty * @param {string} value * @return {Object} * @member ns.util.DOM * @static */ function getPrefixedValue(value) { return { webkit: "-webkit-" + value, moz: "-moz-" + value, o: "-ms-" + value, ms: "-o-" + value, normal: value }; } /** * Returns style value for css property with browsers prefixes * @method getPrefixedStyle * @param {HTMLStyle} styles * @param {string} property * @return {Object} * @member ns.util.DOM * @static */ function getPrefixedStyleValue(styles, property) { var prefixedProperties = getPrefixedValue(property), value, key; for (key in prefixedProperties) { if (prefixedProperties.hasOwnProperty(key)) { value = styles[prefixedProperties[key]]; if (value && value !== "none") { return value; } } } return value; } /** * Returns size (width, height) as CSS string * @method toCSSSize * @param {string|Array} size has to be comma separated string (eg. "10,100") or array with 2 * elements * @return {string} if not enough arguments the method returns empty string * @member ns.util.DOM * @static */ function toCSSSize(size) { var cssSize = "", arraySize = stringUtil.parseProperty(size); if (arraySize && arraySize.length === 2) { cssSize = "width: " + arraySize[0] + "px; " + "height: " + arraySize[1] + "px;"; } return cssSize; } /** * Set CSS styles for pseudo class selector. * @method setStylesForPseudoClass * @param {string} selector selector of elements * @param {string} pseudoClassName CSS pseudo class name to set, for example after, before * @param {Object} cssValues object with styles to set * @return {number?} return index of inserted rule * @member ns.util.DOM * @static */ function setStylesForPseudoClass(selector, pseudoClassName, cssValues) { var cssValuesArray = [], headElement, styleElement, name; // create style element on first use if (!appStyleSheet) { headElement = document.head || document.getElementsByTagName("head")[0]; styleElement = document.createElement("style"); styleElement.type = "text/css"; headElement.appendChild(styleElement); appStyleSheet = styleElement.sheet; } for (name in cssValues) { if (cssValues.hasOwnProperty(name)) { cssValuesArray.push(name + ": " + cssValues[name]); } } if (cssValuesArray.length) { return appStyleSheet.addRule(selector + "::" + pseudoClassName, cssValuesArray.join("; ")); } return null; } /** * Remove CSS rule from sheet. * @method removeCSSRule * @param {number} ruleIndex Index of rule to remove * @static */ function removeCSSRule(ruleIndex) { // create style element on first use if (appStyleSheet) { appStyleSheet.deleteRule(ruleIndex); } } // assign methods to namespace DOM.getCSSProperty = getCSSProperty; DOM.extractCSSProperties = extractCSSProperties; DOM.getElementHeight = getElementHeight; DOM.getElementWidth = getElementWidth; DOM.getElementOffset = getElementOffset; DOM.isOccupiedPlace = isOccupiedPlace; DOM.setPrefixedStyle = setPrefixedStyle; DOM.getPrefixedValue = getPrefixedValue; DOM.getPrefixedStyleValue = getPrefixedStyleValue; DOM.toCSSSize = toCSSSize; DOM.setStylesForPseudoClass = setStylesForPseudoClass; DOM.removeCSSRule = removeCSSRule; }(window, ns)); /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /*global window, ns, define */ /** * #Set Utility * * Own implementation of ECMAScript Set. * * @class ns.util.Set */ (function (window, ns) { "use strict"; var Set = function () { this._data = []; }; Set.prototype = { /** * Add one or many arguments to set * @method add * @member ns.util.Set */ add: function () { var data = this._data; this._data = data.concat.apply(data, [].slice.call(arguments)) .filter(function (item, pos, array) { return array.indexOf(item) === pos; }); }, /** * Remove all items from set * @method clear * @member ns.util.Set */ clear: function () { this._data = []; }, /** * delete one item from set * @method delete * @param {*} item * @member ns.util.Set */ delete: function (item) { var data = this._data, index = data.indexOf(item); if (index > -1) { data.splice(index, 1); } }, /** * Check that item exists in set * @method has * @param {Object} item * @member ns.util.Set * @return {boolean} */ has: function (item) { return this._data.indexOf(item) > -1; }, /** * Iterate on each set elements * @method forEach * @param {Function} cb * @member ns.util.Set */ forEach: function (cb) { this._data.forEach(cb); } }; // for tests ns.util._Set = Set; ns.util.Set = window.Set || Set; }(window, ns)); /*global window, ns, define, ns */ /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Copyright (c) 2010 - 2014 Samsung Electronics Co., Ltd. * License : MIT License V2 */ /** * #Namespace For Widgets * Namespace For Widgets * @author Krzysztof Antoszek * @class ns.widget */ (function (document) { "use strict"; var engine = ns.engine, eventType = engine.eventType, widget = { /** * Get bound widget for element * @method getInstance * @static * @param {HTMLElement|string} element * @param {string} type widget name * @return {?Object} * @member ns.widget */ getInstance: engine.getBinding, /** * Returns Get all bound widget for element or id gives as parameter * @method getAllInstances * @param {HTMLElement|string} element * @return {?Object} * @static * @member ns.widget */ getAllInstances: engine.getAllBindings }; function mapWidgetDefinition(name, element, options) { var widgetParams = { name: name, element: element, options: options } return widgetParams; } function widgetConstructor(name, element, options) { var widgetParams = mapWidgetDefinition(name, element, options); return engine.instanceWidget(widgetParams.element, widgetParams.name, widgetParams.options); } /** * Register simple widget constructor in namespace * @param {Event} event */ function defineWidget(event) { var definition = event.detail, name = definition.name; ns.widget[name] = widgetConstructor.bind(null, name); } /** * Remove event listeners on framework destroy */ function destroy() { document.removeEventListener(eventType.WIDGET_DEFINED, defineWidget, true); document.removeEventListener(eventType.DESTROY, destroy, false); } document.addEventListener(eventType.WIDGET_DEFINED, defineWidget, true); document.addEventListener(eventType.DESTROY, destroy, false); /** @namespace ns.widget */ ns.widget = widget; }(window.document)); /*global window, ns, define */ /*jslint nomen: true */ /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * #BaseWidget * Prototype class of widget * * ## How to invoke creation of widget from JavaScript * * To build and initialize widget in JavaScript you have to use method * {@link ns.engine#instanceWidget}. First argument for method is HTMLElement, which specifies the * element of widget. Second parameter is name of widget to create. * * If you load jQuery before initializing tau library, you can use standard jQuery UI Widget * notation. * * ### Examples * #### Build widget from JavaScript * * @example * var element = document.getElementById("id"), * ns.engine.instanceWidget(element, "Button"); * * #### Build widget from jQuery * * @example * var element = $("#id").button(); * * ## How to create new widget * * @example * (function (ns) { * "use strict"; * * var BaseWidget = ns.widget.BaseWidget, // create alias to main objects * ... * arrayOfElements, // example of private property, common for all instances of widget * Button = function () { // create local object with widget * ... * }, * prototype = new BaseWidget(); // add ns.widget.BaseWidget as prototype to widget's * object, for better minification this should be assign to local variable and next * variable should be assign to prototype of object. * * function closestEnabledButton(element) { // example of private method * ... * } * ... * * prototype.options = { //add default options to be read from data- attributes * theme: "s", * ... * }; * * prototype._build = function (template, element) { * // method called when the widget is being built, should contain all HTML * // manipulation actions * ... * return element; * }; * * prototype._init = function (element) { * // method called during initialization of widget, should contain all actions * // necessary fastOn application start * ... * return element; * }; * * prototype._bindEvents = function (element) { * // method to bind all events, should contain all event bindings * ... * }; * * prototype._enable = function (element) { * // method called during invocation of enable() method * ... * }; * * prototype._disable = function (element) { * // method called during invocation of disable() method * ... * }; * * prototype.refresh = function (element) { * // example of public method * ... * }; * * prototype._refresh = function () { * // example of protected method * ... * }; * * Button.prototype = prototype; * * engine.defineWidget( // define widget * "Button", //name of widget * "[data-role='button'],button,[type='button'],[type='submit'],[type='reset']", * //widget's selector * [ // public methods, here should be list all public method * "enable", * "disable", * "refresh" * ], * Button, // widget's object * "mobile" // widget's namespace * ); * ns.widget.Button = Button; * * }(ns)); * @author Jadwiga Sosnowska * @author Krzysztof Antoszek * @author Tomasz Lukawski * @author Przemyslaw Ciezkowski * @author Maciej Urbanski * @author Piotr Karny * @author MichaÅ‚ Szepielak * @class ns.widget.BaseWidget */ (function (document, ns) { "use strict"; var slice = [].slice, /** * Alias to ns.engine * @property {ns.engine} engine * @member ns.widget.BaseWidget * @private * @static */ engine = ns.engine, engineDataTau = engine.dataTau, util = ns.util, /** * Alias to {@link ns.event} * @property {Object} eventUtils * @member ns.widget.BaseWidget * @private * @static */ eventUtils = ns.event, /** * Alias to {@link ns.util.DOM} * @property {Object} domUtils * @private * @static */ domUtils = util.DOM, utilString = util.string, /** * Alias to {@link ns.util.object} * @property {Object} objectUtils * @private * @static */ objectUtils = util.object, Set = util.Set, BaseWidget = function () { this.flowState = "created"; return this; }, getNSData = domUtils.getNSData, prototype = {}, /** * Property with string represent function type * (for better minification) * @property {string} [TYPE_FUNCTION="function"] * @private * @static * @readonly */ TYPE_FUNCTION = "function", disableClass = "ui-state-disabled", ariaDisabled = "aria-disabled", __callbacks; BaseWidget.classes = { disable: disableClass }; prototype._configureDefinition = function (definition) { var self = this, definitionName, definitionNamespace; if (definition) { definitionName = definition.name; definitionNamespace = definition.namespace; /** * Name of the widget * @property {string} name * @member ns.widget.BaseWidget */ self.name = definitionName; /** * Name of the widget (in lower case) * @property {string} widgetName * @member ns.widget.BaseWidget */ self.widgetName = definitionName; /** * Namespace of widget events * @property {string} widgetEventPrefix * @member ns.widget.BaseWidget */ self.widgetEventPrefix = definitionName.toLowerCase(); /** * Namespace of the widget * @property {string} namespace * @member ns.widget.BaseWidget */ self.namespace = definitionNamespace; /** * Full name of the widget * @property {string} widgetFullName * @member ns.widget.BaseWidget */ self.widgetFullName = ((definitionNamespace ? definitionNamespace + "-" : "") + definitionName).toLowerCase(); /** * Id of widget instance * @property {string} id * @member ns.widget.BaseWidget */ self.id = ns.getUniqueId(); /** * Widget's selector * @property {string} selector * @member ns.widget.BaseWidget */ self.selector = definition.selector; } }; /** * Protected method configuring the widget * @method _configure * @member ns.widget.BaseWidget * @protected * @template * @ignore */ /** * Configures widget object from definition. * * It calls such methods as #\_getCreateOptions and #\_configure. * @method configure * @param {Object} definition * @param {string} definition.name Name of the widget * @param {string} definition.selector Selector of the widget * @param {HTMLElement} element Element of widget * @param {Object} options Configure options * @member ns.widget.BaseWidget * @return {ns.widget.BaseWidget} * @ignore */ prototype.configure = function (definition, element, options) { var self = this; /** * Object with options for widget * @property {Object} [options={}] * @member ns.widget.BaseWidget */ self.flowState = "configuring"; self.options = self.options || {}; /** * Base element of widget * @property {?HTMLElement} [element=null] * @member ns.widget.BaseWidget */ self.element = self.element || null; self._configureDefinition(definition); if (typeof self._configure === TYPE_FUNCTION) { self._configure(element); } self.isCustomElement = !!element.createdCallback; self._getCreateOptions(element); objectUtils.fastMerge(self.options, options); self.flowState = "configured"; }; /** * Reads data-* attributes and save to options object. * @method _getCreateOptions * @param {HTMLElement} element Base element of the widget * @return {Object} * @member ns.widget.BaseWidget * @protected */ prototype._getCreateOptions = function (element) { var self = this, options = self.options, tag = element.localName.toLowerCase(); if (options) { Object.keys(options).forEach(function (option) { var attributeName = utilString.camelCaseToDashes(option), baseValue = getNSData(element, attributeName, true), prefixedValue = getNSData(element, attributeName); if (prefixedValue !== null) { options[option] = prefixedValue; } else { if (typeof options[option] === "boolean") { self._readBooleanOptionFromElement(element, option); } } if (option === "type" && tag === "input") { // don't set conflicting props return; } if (baseValue !== null) { options[option] = baseValue; } }); } return options; }; /** * Protected method building the widget * @method _build * @param {HTMLElement} element * @return {HTMLElement} widget's element * @member ns.widget.BaseWidget * @protected * @template */ /** * Builds widget. * * It calls method #\_build. * * Before starting building process, the event beforecreate with * proper prefix defined in variable widgetEventPrefix is triggered. * @method build * @param {HTMLElement} element Element of widget before building process * @return {HTMLElement} Element of widget after building process * @member ns.widget.BaseWidget * @ignore */ prototype.build = function (element) { var self = this, id, node, dataBuilt = element.getAttribute(engineDataTau.built), dataName = element.getAttribute(engineDataTau.name); eventUtils.trigger(element, self.widgetEventPrefix + "beforecreate"); self.flowState = "building"; id = element.id; if (id) { self.id = id; } else { element.id = self.id; } if (typeof self._build === TYPE_FUNCTION) { node = self._build(element); } else { node = element; } self._setBooleanOptions(element); // Append current widget name to data-tau-built and data-tau-name attributes dataBuilt = !dataBuilt ? self.name : dataBuilt + engineDataTau.separator + self.name; dataName = !dataName ? self.name : dataName + engineDataTau.separator + self.name; element.setAttribute(engineDataTau.built, dataBuilt); element.setAttribute(engineDataTau.name, dataName); self.flowState = "built"; return node; }; /** * Protected method initializing the widget * @method _init * @param {HTMLElement} element * @member ns.widget.BaseWidget * @template * @protected */ /** * Initializes widget. * * It calls method #\_init. * @method init * @param {HTMLElement} element Element of widget before initialization * @member ns.widget.BaseWidget * @return {ns.widget.BaseWidget} * @ignore */ prototype.init = function (element) { var self = this; self.id = element.id; self.flowState = "initiating"; if (typeof self._init === TYPE_FUNCTION) { self._init(element); } if (element.getAttribute("disabled") || self.options.disabled === true) { self.disable(); } else { self.enable(); } self.flowState = "initiated"; return self; }; /** * Returns base element widget * @member ns.widget.BaseWidget * @return {HTMLElement|null} * @instance */ prototype.getContainer = function () { var self = this; if (typeof self._getContainer === TYPE_FUNCTION) { return self._getContainer(); } return self.element; }; /** * Bind widget events attached in init mode * @method _bindEvents * @param {HTMLElement} element Base element of widget * @member ns.widget.BaseWidget * @template * @protected */ /** * Binds widget events. * * It calls such methods as #\_buildBindEvents and #\_bindEvents. * At the end of binding process, the event "create" with proper * prefix defined in variable widgetEventPrefix is triggered. * @method bindEvents * @param {HTMLElement} element Base element of the widget * @param {boolean} onlyBuild Inform about the type of bindings: build/init * @member ns.widget.BaseWidget * @return {ns.widget.BaseWidget} * @ignore */ prototype.bindEvents = function (element, onlyBuild) { var self = this, dataBound = element.getAttribute(engineDataTau.bound); if (!onlyBuild) { dataBound = !dataBound ? self.name : dataBound + engineDataTau.separator + self.name; element.setAttribute(engineDataTau.bound, dataBound); } if (typeof self._buildBindEvents === TYPE_FUNCTION) { self._buildBindEvents(element); } if (!onlyBuild && typeof self._bindEvents === TYPE_FUNCTION) { self._bindEvents(element); } self.trigger(self.widgetEventPrefix + "create", self); return self; }; /** * Event triggered when method focus is called * @event taufocus * @member ns.widget.BaseWidget */ /** * Focus widget's element. * * This function calls function focus on element and if it is known * the direction of event, the proper css classes are added/removed. * @method focus * @param {Object} options The options of event. * @param {HTMLElement} options.previousElement Element to blur * @param {HTMLElement} options.element Element to focus * @member ns.widget.BaseWidget */ prototype.focus = function (options) { var self = this, element = self.element, blurElement, blurWidget; options = options || {}; blurElement = options.previousElement; // we try to blur element, which has focus previously if (blurElement) { blurWidget = engine.getBinding(blurElement); // call blur function on widget if (blurWidget) { options = objectUtils.merge({}, options, {element: blurElement}); blurWidget.blur(options); } else { // or on element, if widget does not exist blurElement.blur(); } } options = objectUtils.merge({}, options, {element: element}); // set focus on element eventUtils.trigger(document, "taufocus", options); element.focus(); return true; }; /** * Event triggered then method blur is called. * @event taublur * @member ns.widget.BaseWidget */ /** * Blur widget's element. * * This function calls function blur on element and if it is known * the direction of event, the proper css classes are added/removed. * @method blur * @param {Object} options The options of event. * @param {HTMLElement} options.element Element to blur * @member ns.widget.BaseWidget */ prototype.blur = function (options) { var self = this, element = self.element; options = objectUtils.merge({}, options, {element: element}); // blur element eventUtils.trigger(document, "taublur", options); element.blur(); return true; }; /** * Protected method destroying the widget * @method _destroy * @template * @protected * @member ns.widget.BaseWidget */ /** * Destroys widget. * * It calls method #\_destroy. * * At the end of destroying process, the event "destroy" with proper * prefix defined in variable widgetEventPrefix is triggered and * the binding set in engine is removed. * @method destroy * @param {HTMLElement} element Base element of the widget * @member ns.widget.BaseWidget */ prototype.destroy = function (element) { var self = this; element = element || self.element; // the widget is in during destroy process self.flowState = "destroying"; if (typeof self._destroy === TYPE_FUNCTION) { self._destroy(element); } if (self.element) { self.trigger(self.widgetEventPrefix + "destroy"); } if (element) { engine.removeBinding(element, self.name); } // the widget was destroyed self.flowState = "destroyed"; }; /** * Protected method disabling the widget * @method _disable * @protected * @member ns.widget.BaseWidget * @template */ /** * Disables widget. * * It calls method #\_disable. * @method disable * @member ns.widget.BaseWidget * @return {ns.widget.BaseWidget} */ prototype.disable = function () { var self = this, args = slice.call(arguments), element = self.element; element.classList.add(disableClass); element.setAttribute(ariaDisabled, true); if (typeof self._disable === TYPE_FUNCTION) { args.unshift(element); self._disable.apply(self, args); } return this; }; /** * Check if widget is disabled. * @method isDisabled * @member ns.widget.BaseWidget * @return {boolean} Returns true if widget is disabled */ prototype.isDisabled = function () { var self = this; return self.element.getAttribute("disabled") || self.options.disabled === true; }; /** * Protected method enabling the widget * @method _enable * @protected * @member ns.widget.BaseWidget * @template */ /** * Enables widget. * * It calls method #\_enable. * @method enable * @member ns.widget.BaseWidget * @return {ns.widget.BaseWidget} */ prototype.enable = function () { var self = this, args = slice.call(arguments), element = self.element; element.classList.remove(disableClass); element.setAttribute(ariaDisabled, false); if (typeof self._enable === TYPE_FUNCTION) { args.unshift(element); self._enable.apply(self, args); } return this; }; /** * Protected method causing the widget to refresh * @method _refresh * @protected * @member ns.widget.BaseWidget * @template */ /** * Refreshes widget. * * It calls method #\_refresh. * @method refresh * @member ns.widget.BaseWidget * @return {ns.widget.BaseWidget} */ prototype.refresh = function () { var self = this; if (typeof self._refresh === TYPE_FUNCTION) { self._refresh.apply(self, arguments); } return self; }; /** * Reads class based on name conversion option value, for all options which have boolean value * we can read option value by check that exists classname connected with option name. To * correct use this method is required define in widget property _classesPrefix. * * For example for option middle in Button widget we will check existing of class * ui-btn-middle. * * @method _readBooleanOptionFromElement * @param {HTMLElement} element Main element of widget * @param {string} name Name of option which should be used * @return {boolean} * @member ns.widget.BaseWidget * @protected */ prototype._readBooleanOptionFromElement = function (element, name) { var classesPrefix = this._classesPrefix, className; if (classesPrefix) { className = classesPrefix + utilString.camelCaseToDashes(name); this.options[name] = element.classList.contains(className); } }; /** * Sets or removes class based on name conversion option, for all options which have boolean * value we can just set classname which is converted from camel case to dash style. * To correct use this method is required define in widget property _classesPrefix. * * For example for option middle in Button widget we will set or remove class ui-btn-middle. * * @method _setBooleanOption * @param {HTMLElement} element Main element of widget * @param {string} name Name of option which should be used * @param {boolean} value New value of option to set * @member ns.widget.BaseWidget * @protected * @return {false} always return false to block refreshing */ prototype._setBooleanOption = function (element, name, value) { var classesPrefix = this._classesPrefix, className; if (classesPrefix) { className = classesPrefix + utilString.camelCaseToDashes(name); element.classList.toggle(className, value); } // we don't need refresh, always can return false return false; }; /** * For each options which has boolean value set or remove connected class. * * @method _setBooleanOptions * @param {HTMLElement} element Base element of the widget * @return {Object} * @member ns.widget.BaseWidget * @protected */ prototype._setBooleanOptions = function (element) { var self = this, classesPrefix = self._classesPrefix, options = self.options; if (classesPrefix && options !== undefined) { Object.keys(options).forEach(function (option) { if (typeof options[option] === "boolean") { options[option] = self._setBooleanOption(element, option, options[option]); } }); } return options; }; prototype._processOptionObject = function (firstArgument) { var self = this, key, partResult, refresh = false; for (key in firstArgument) { if (firstArgument.hasOwnProperty(key)) { partResult = self._oneOption(key, firstArgument[key]); if (key !== undefined && firstArgument[key] !== undefined) { refresh = refresh || partResult; } } } return refresh; }; /** * Gets or sets options of the widget. * * This method can work in many context. * * If first argument is type of object them, method set values for options given in object. * Keys of object are names of options and values from object are values to set. * * If you give only one string argument then method return value for given option. * * If you give two arguments and first argument will be a string then second argument will be * intemperate as value to set. * * @method option * @param {string|Object} [name] name of option * @param {*} [value] value to set * @member ns.widget.BaseWidget * @return {*} return value of option or null if method is called in setter context */ prototype.option = function (name, value) { var self = this, firstArgument = name, secondArgument = value, result = null, refresh = false; if (typeof firstArgument === "string") { result = self._oneOption(firstArgument, secondArgument); if (secondArgument !== undefined) { refresh = result; result = null; } } else if (typeof firstArgument === "object") { refresh = self._processOptionObject(firstArgument); } if (refresh) { self.refresh(); } return result; }; /** * Gets or sets one option of the widget. * * @method _oneOption * @param {string} field * @param {*} value * @member ns.widget.BaseWidget * @return {*} * @protected */ prototype._oneOption = function (field, value) { var self = this, methodName, refresh = false; if (value === undefined) { methodName = "_get" + (field[0].toUpperCase() + field.slice(1)); if (typeof self[methodName] === TYPE_FUNCTION) { return self[methodName](); } return self.options[field]; } methodName = "_set" + (field[0].toUpperCase() + field.slice(1)); if (typeof self[methodName] === TYPE_FUNCTION) { refresh = self[methodName](self.element, value); } else if (typeof value === "boolean") { refresh = self._setBooleanOption(self.element, field, value); } else { self.options[field] = value; if (self.element) { self.element.setAttribute("data-" + (field.replace(/[A-Z]/g, function (c) { return "-" + c.toLowerCase(); })), value); refresh = true; } } return refresh; }; /** * Returns true if widget has bounded events. * * This methods enables to check if the widget has bounded * events through the {@link ns.widget.BaseWidget#bindEvents} method. * @method isBound * @param {string} [type] Type of widget * @member ns.widget.BaseWidget * @ignore * @return {boolean} true if events are bounded */ prototype.isBound = function (type) { var element = this.element; type = type || this.name; return element && element.hasAttribute(engineDataTau.bound) && element.getAttribute(engineDataTau.bound).indexOf(type) > -1; }; /** * Returns true if widget is built. * * This methods enables to check if the widget was built * through the {@link ns.widget.BaseWidget#build} method. * @method isBuilt * @param {string} [type] Type of widget * @member ns.widget.BaseWidget * @ignore * @return {boolean} true if the widget was built */ prototype.isBuilt = function (type) { var element = this.element; type = type || this.name; return element && element.hasAttribute(engineDataTau.built) && element.getAttribute(engineDataTau.built).indexOf(type) > -1; }; /** * Protected method getting the value of widget * @method _getValue * @return {*} * @member ns.widget.BaseWidget * @template * @protected */ /** * Protected method setting the value of widget * @method _setValue * @param {*} value * @return {*} * @member ns.widget.BaseWidget * @template * @protected */ /** * Gets or sets value of the widget. * * @method value * @param {*} [value] New value of widget * @member ns.widget.BaseWidget * @return {*} */ prototype.value = function (value) { var self = this; if (value !== undefined) { if (typeof self._setValue === TYPE_FUNCTION) { return self._setValue(value); } return self; } if (typeof self._getValue === TYPE_FUNCTION) { return self._getValue(); } return self; }; /** * Triggers an event on widget's element. * * @method trigger * @param {string} eventName The name of event to trigger * @param {?*} [data] additional Object to be carried with the event * @param {boolean} [bubbles=true] Indicating whether the event * bubbles up through the DOM or not * @param {boolean} [cancelable=true] Indicating whether * the event is cancelable * @member ns.widget.BaseWidget * @return {boolean} False, if any callback invoked preventDefault on event object */ prototype.trigger = function (eventName, data, bubbles, cancelable) { return eventUtils.trigger(this.element, eventName, data, bubbles, cancelable); }; /** * Adds event listener to widget's element. * @method on * @param {string} eventName The name of event * @param {Function} listener Function called after event will be trigger * @param {boolean} [useCapture=false] useCapture Parameter of addEventListener * @member ns.widget.BaseWidget */ prototype.on = function (eventName, listener, useCapture) { eventUtils.on(this.element, eventName, listener, useCapture); }; /** * Removes event listener from widget's element. * @method off * @param {string} eventName The name of event * @param {Function} listener Function call after event will be trigger * @param {boolean} [useCapture=false] useCapture Parameter of addEventListener * @member ns.widget.BaseWidget */ prototype.off = function (eventName, listener, useCapture) { eventUtils.off(this.element, eventName, listener, useCapture); }; prototype._framesFlow = function () { var self = this, args = slice.call(arguments), func = args.shift(); if (typeof func === "function") { func(); } if (func !== undefined) { util.requestAnimationFrame(function frameFlowCallback() { self._framesFlow.apply(self, args); }); } }; function callbacksFilter(item) { return !item.toRemove; } function callbacksForEach(item) { if (item.object[item.property] === item.value) { util.requestAnimationFrame(item.callback.bind(item.object)); item.toRemove = true; } } function _controlWaitFor() { __callbacks.forEach(callbacksForEach); __callbacks = __callbacks.filter(callbacksFilter); if (__callbacks.length) { util.requestAnimationFrame(_controlWaitFor); } } prototype._waitFor = function (property, value, callback) { var self = this; if (self[property] === value) { callback.call(self); } else { __callbacks = __callbacks || []; __callbacks.push({ object: self, property: property, value: value, callback: callback }); } _controlWaitFor(); }; function readDOMElementStateClassList(element, stateObject) { var classList = stateObject.classList; if (classList !== undefined) { if (classList instanceof Set) { classList.clear(); } else { classList = new Set(); stateObject.classList = classList; } if (element.classList.length) { classList.add.apply(classList, slice.call(element.classList)); } } } function readDOMElementState(element, stateObject) { readDOMElementStateClassList(element, stateObject); if (stateObject.offsetWidth !== undefined) { stateObject.offsetWidth = element.offsetWidth; } if (stateObject.style !== undefined) { domUtils.extractCSSProperties(element, stateObject.style, null, true); } if (stateObject.children !== undefined) { stateObject.children.forEach(function (child, index) { readDOMElementState(element.children[index], child); }); } } function render(stateObject, element, isChild) { var recalculate = false; if (stateObject.classList !== undefined) { slice.call(element.classList).forEach(function renderRemoveClassList(className) { if (!stateObject.classList.has(className)) { element.classList.remove(className); recalculate = true; } }); stateObject.classList.forEach(function renderAddClassList(className) { if (!element.classList.contains(className)) { element.classList.add(className); recalculate = true; } }); } if (stateObject.style !== undefined) { Object.keys(stateObject.style).forEach(function renderUpdateStyle(styleName) { element.style[styleName] = stateObject.style[styleName]; }); } if (stateObject.children !== undefined) { stateObject.children.forEach(function renderChildren(child, index) { render(child, element.children[index], true); }); } if (recalculate && !isChild) { util.requestAnimationFrame(readDOMElementState.bind(null, element, stateObject)); } } prototype._render = function (now) { var self = this, stateDOM = self._stateDOM, element = self.element; if (now) { render(stateDOM, element); } else { util.requestAnimationFrame(render.bind(null, stateDOM, element)); } }; prototype._initDOMstate = function () { readDOMElementState(this.element, this._stateDOM); }; prototype._togglePrefixedClass = function (stateDOM, prefix, name) { var requireRefresh = false, prefixedClassName = prefix + name; stateDOM.classList.forEach(function (className) { if (className.indexOf(prefix) === 0 && prefixedClassName !== className) { stateDOM.classList.delete(className); requireRefresh = true; } }); if (!stateDOM.classList.has(prefixedClassName)) { stateDOM.classList.add(prefixedClassName); requireRefresh = true; } return requireRefresh; }; BaseWidget.prototype = prototype; // definition ns.widget.BaseWidget = BaseWidget; }(window.document, ns)); /*global window, ns, define, ns */ /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * #Namespace For Core Widgets * @author Krzysztof Antoszek * @class ns.widget.core */ (function (document, ns) { "use strict"; ns.widget.core = ns.widget.core || {}; }(window.document, ns)); /*global window, ns, define */ /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /*jslint nomen: true */ /** * # Page Widget * Page is main element of application's structure. * * ## Default selectors * In the Tizen Web UI framework the application page structure is based on a header, content and footer elements: * * - **The header** is placed at the top, and displays the page title and optionally buttons. * - **The content** is the section below the header, showing the main content of the page. * - **The footer** is a bottom part of page which can display for example buttons * * The following table describes the specific information for each section. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
SectionClassMandatoryDescription
Pageui-pageYesDefines the element as a page. * * The page widget is used to manage a single item in a page-based architecture. * * A page is composed of header (optional), content (mandatory), and footer (optional) elements.
ui-page-activeNoIf an application has a static start page, insert the ui-page-active class in the page element to * speed up the application launch. The start page with the ui-page-active class can be displayed before * the framework is fully loaded. * * If this class is not used, the framework inserts the class automatically to the first page of the * application. * * However, this has a slowing effect on the application launch, because the page is displayed only after * *the* framework is fully loaded.
Headerui-headerNoDefines the element as a header.
Contentui-contentYesDefines the element as content.
Footerui-footerNoDefines the element as a footer. * * The footer section is mostly used to include option buttons.
* * All elements with class=ui-page will be become page widgets * * @example * *
*
*
*
*
* * *
*
*

Call menu

* *
*
Content message
*
* *
*
* * ## Manual constructor * For manual creation of page widget you can use constructor of widget from **tau** namespace: * * @example * var pageElement = document.getElementById("page"), * page = tau.widget.Page(buttonElement); * * Constructor has one require parameter **element** which are base **HTMLElement** to create widget. * We recommend get * this element by method *document.getElementById*. * * ## Multi-page Layout * * You can implement a template containing multiple page containers in the application index.html file. * * In the multi-page layout, the main page is defined with the ui-page-active class. * If no page has the ui-page-active * class, the framework automatically sets up the first page in the source order * as the main page. You can improve the * launch performance by explicitly defining the main page to be displayed first. * If the application has to wait for * the framework to set up the main page, the page is displayed with some delay * only after the framework is fully * loaded. * * You can link to internal pages by referring to the ID of the page. For example, to link to the page with an ID * of * two, the link element needs the href="#two" attribute in the code, as in the following example. * * @example * *
*
*
*
*
* * *
*
*
*
*
* * To find the currently active page, use the ui-page-active class. * * ## Changing Pages * ### Go to page in JavaScript * To change page use method *tau.changePage* * * @example * tau.changePage("page-two"); * * ### Back in JavaScript * To back to previous page use method *tau.back* * * @example * tau.back(); * * ## Transitions * * When changing the active page, you can use a page transition. * * Tizen Web UI Framework does not apply transitions by default. To set a custom transition effect, * you must add the * data-transition attribute to a link: * * @example * I\'ll slide up * * To set a default custom transition effect for all pages, use the pageTransition property: * * @example * tau.defaults.pageTransition = "slideup"; * * ### Transitions list * * - **none** no transition. * - **slideup** Makes the content of the next page slide up, appearing to conceal the content of the previous page. * * ## Handling Page Events * * With page widget we have connected many of events. * * To handle page events, use the following code: * * @example *
*
*
*
* * * * To bind an event callback on the Back key, use the following code: * * Full list of available events is in [events list section](#events-list). * * To bind an event callback on the Back key, use the following code: * * @example * * * * ## Methods * * To call method on widget you can use tau API: * * @example * var pageElement = document.getElementById("page"), * page = tau.widget.Page(buttonElement); * * page.methodName(methodArgument1, methodArgument2, ...); * * @class ns.widget.core.Page * @extends ns.widget.BaseWidget * @component-selector .ui-page * @component-type container-component * @component-constraint 'popup', 'drawer', 'header', 'bottom-button' * @component-attachable false * @author Maciej Urbanski * @author Piotr Karny * @author Damian Osipiuk */ (function (document, ns) { "use strict"; /** * Alias for {@link ns.widget.BaseWidget} * @property {Object} BaseWidget * @member ns.widget.core.Page * @private * @static */ var BaseWidget = ns.widget.BaseWidget, /** * Alias for {@link ns.util} * @property {Object} util * @member ns.widget.core.Page * @private * @static */ util = ns.util, utilsDOM = util.DOM, /** * Alias for {@link ns.util.selectors} * @property {Object} utilSelectors * @member ns.widget.core.Page * @private * @static */ utilSelectors = util.selectors, /** * Alias for {@link ns.engine} * @property {Object} engine * @member ns.widget.core.Page * @private * @static */ engine = ns.engine, Page = function () { var self = this; /** * Callback on resize * @property {?Function} _contentFillAfterResizeCallback * @private * @member ns.widget.core.Page */ self._contentFillAfterResizeCallback = null; self._initialContentStyle = {}; /** * Options for widget. * It is empty object, because widget Page does not have any options. * @property {Object} options * @member ns.widget.core.Page */ self.options = {}; self._contentStyleAttributes = ["height", "width", "minHeight", "marginTop", "marginBottom"]; self._ui = {}; }, /** * Dictionary for page related event types * @property {Object} EventType * @member ns.widget.core.Page * @static */ EventType = { /** * Triggered on the page we are transitioning to, * after the transition animation has completed. * @event pageshow * @member ns.widget.core.Page */ SHOW: "pageshow", /** * Triggered on the page we are transitioning away from, * after the transition animation has completed. * @event pagehide * @member ns.widget.core.Page */ HIDE: "pagehide", /** * Triggered when the page has been created in the DOM * (for example, through Ajax) but before all widgets * have had an opportunity to enhance the contained markup. * @event pagecreate * @member ns.widget.core.Page */ CREATE: "pagecreate", /** * Triggered when the page is being initialized, * before most plugin auto-initialization occurs. * @event pagebeforecreate * @member ns.widget.core.Page */ BEFORE_CREATE: "pagebeforecreate", /** * Triggered on the page we are transitioning to, * before the actual transition animation is kicked off. * @event pagebeforeshow * @member ns.widget.core.Page */ BEFORE_SHOW: "pagebeforeshow", /** * Triggered on the page we are transitioning away from, * before the actual transition animation is kicked off. * @event pagebeforehide * @member ns.widget.core.Page */ BEFORE_HIDE: "pagebeforehide" }, /** * Dictionary for page related css class names * @property {Object} classes * @member ns.widget.core.Page * @static * @readonly */ classes = { uiPage: "ui-page", /** * Indicates active page * @style ui-page-active * @member ns.widget.core.Page */ uiPageActive: "ui-page-active", uiSection: "ui-section", uiHeader: "ui-header", uiFooter: "ui-footer", uiContent: "ui-content", uiTitle: "ui-title", uiPageScroll: "ui-scroll-on", uiScroller: "ui-scroller" }, HEADER_SELECTOR = "header,[data-role='header'],." + classes.uiHeader, FOOTER_SELECTOR = "footer,[data-role='footer'],." + classes.uiFooter, //ui-indexscrollbar is needed as widget ads html markup at the //same level as content, other wise page content is build on //indexscrollbar element CONTENT_SELECTOR = "[data-role='content'],." + classes.uiContent, prototype = new BaseWidget(); Page.classes = classes; Page.events = EventType; /** * Configure default options for widget * @method _configure * @protected * @member ns.widget.core.Page */ prototype._configure = function () { var options = this.options; /** * Object with default options * @property {Object} options * @property {boolean|string|null} [options.header=false] Sets content of header. * @property {boolean|string|null} [options.footer=false] Sets content of footer. * @property {boolean} [options.autoBuildWidgets=false] Automatically build widgets inside page. * @property {string} [options.content=null] Sets content of popup. * @member ns.widget.core.Page * @static */ options.header = null; options.footer = null; options.content = null; options.enablePageScroll = ns.getConfig("enablePageScroll"); options.autoBuildWidgets = ns.getConfig("autoBuildOnPageChange"); this.options = options; }; /** * Setup size of element to 100% of screen * @method _contentFill * @protected * @member ns.widget.core.Page */ prototype._contentFill = function () { var self = this, element = self.element, screenWidth = window.innerWidth, screenHeight = window.innerHeight, elementStyle = element.style, ui = self._ui, content = ui.content, contentStyle, header = ui.header, top = 0, bottom = 0, footer = ui.footer; elementStyle.width = screenWidth + "px"; elementStyle.height = screenHeight + "px"; if (content && !element.classList.contains("ui-page-flex")) { contentStyle = content.style; if (header) { top = utilsDOM.getElementHeight(header); } if (footer) { bottom = utilsDOM.getElementHeight(footer); contentStyle.marginBottom = bottom + "px"; contentStyle.paddingBottom = (-bottom) + "px"; } if (!self.options.enablePageScroll) { contentStyle.height = (screenHeight - top - bottom) + "px"; } } }; prototype._storeContentStyle = function () { var self = this, initialContentStyle = self._initialContentStyle, contentStyleAttributes = self._contentStyleAttributes, content = self.element.querySelector("." + classes.uiContent), contentStyle = content ? content.style : {}; contentStyleAttributes.forEach(function (name) { initialContentStyle[name] = contentStyle[name]; }); }; /** * Restore saved styles for content. * Called on refresh or hide. * @protected */ prototype._restoreContentStyle = function () { var self = this, initialContentStyle = self._initialContentStyle, contentStyleAttributes = self._contentStyleAttributes, content = self.element.querySelector("." + classes.uiContent), contentStyle = content ? content.style : {}; contentStyleAttributes.forEach(function (name) { contentStyle[name] = initialContentStyle[name]; }); }; /** * Setter for footer option * @method _setFooter * @param {HTMLElement} element * @param {string} value * @protected * @member ns.widget.core.Page */ prototype._setFooter = function (element, value) { var self = this, ui = self._ui, footer = ui.footer; // footer element if footer does not exist and value is true or string if (!footer && value) { footer = document.createElement("footer"); element.appendChild(footer); ui.footer = footer; } if (footer) { // remove child if footer does not exist and value is set to false if (value === false) { element.removeChild(footer); ui.footer = null; } else { // if options is set to true, to string or not is set // add class footer.classList.add(classes.uiFooter); // if is string fill content by string value if (typeof value === "string") { ui.footer.textContent = value; } } // and remember options self.options.footer = value; } }; /** * Setter for header option * @method _setHeader * @param {HTMLElement} element * @param {string} value * @protected * @member ns.widget.core.Page */ prototype._setHeader = function (element, value) { var self = this, ui = self._ui, header = ui.header; // header element if header does not exist and value is true or string if (!header && value) { header = document.createElement("header"); element.appendChild(header); ui.header = header; } if (header) { // remove child if header does not exist and value is set to false if (value === false) { element.removeChild(header); ui.header = null; } else { // if options is set to true, to string or not is set // add class header.classList.add(classes.uiHeader); // if is string fill content by string value if (typeof value === "string") { ui.header.textContent = value; } } // and remember options self.options.header = value; } }; /** * Setter for content option * @method _setContent * @param {HTMLElement} element * @param {string} value * @protected * @member ns.widget.core.Page */ prototype._setContent = function (element, value) { var self = this, ui = self._ui, content = ui.content, child = element.firstChild, next; if (!content && value) { content = document.createElement("div"); while (child) { next = child.nextSibling; if (child !== ui.footer && child !== ui.header) { content.appendChild(child); } child = next; } element.insertBefore(content, ui.footer); ui.content = content; } if (content) { // remove child if content exist and value is set to false if (value === false) { element.removeChild(content); ui.content = null; } else { // if options is set to true, to string or not is set // add class content.classList.add(classes.uiContent); // if is string fill content by string value if (typeof value === "string") { content.textContent = value; } } // and remember options self.options.content = value; } }; /** * Method creates empty page header. It also checks for additional * content to be added in header. * @method _buildHeader * @param {HTMLElement} element * @protected * @member ns.widget.core.Page */ prototype._buildHeader = function (element) { var self = this; self._ui.header = utilSelectors.getChildrenBySelector(element, HEADER_SELECTOR)[0] || null; if (self.options.header === undefined) { self.options.header = !!self._ui.header; } self._setHeader(element, self.options.header); }; /** * Method creates empty page footer. * @method _buildFooter * @param {HTMLElement} element * @protected * @member ns.widget.core.Page */ prototype._buildFooter = function (element) { var self = this; self._ui.footer = utilSelectors.getChildrenBySelector(element, FOOTER_SELECTOR)[0] || null; if (self.options.footer === undefined) { self.options.footer = !!self._ui.footer; } self._setFooter(element, self.options.footer); }; /** * Method creates empty page content. * @method _buildContent * @param {HTMLElement} element * @protected * @member ns.widget.core.Page */ prototype._buildContent = function (element) { var self = this; self._ui.content = utilSelectors.getChildrenBySelector(element, CONTENT_SELECTOR)[0] || null; if (self.options.content === undefined) { self.options.content = !!self._ui.content; } self._setContent(element, self.options.content); }; /** * Set ARIA attributes on page structure * @method _setAria * @protected * @member ns.widget.core.Page */ prototype._setAria = function () { var self = this, ui = self._ui, content = ui.content, header = ui.header, footer = ui.footer, title = ui.title; if (content) { content.setAttribute("role", "main"); } if (header) { header.setAttribute("role", "header"); } if (footer) { footer.setAttribute("role", "footer"); } if (title) { title.setAttribute("role", "heading"); title.setAttribute("aria-level", 1); title.setAttribute("aria-label", "title"); } }; /** * Find title of page * @param {HTMLElement} element * @method _setTitle * @protected * @member ns.widget.core.Page */ prototype._setTitle = function (element) { var self = this, dataPageTitle = utilsDOM.getNSData(element, "title"), header = self._ui.header, pageTitle = dataPageTitle, titleElement; if (header) { titleElement = utilSelectors.getChildrenBySelector(header, "h1, h2, h3, h4, h5, h6")[0]; if (titleElement) { titleElement.classList.add(classes.uiTitle); } if (!pageTitle && titleElement) { pageTitle = titleElement.innerText; self._ui.title = titleElement; } if (!dataPageTitle && pageTitle) { utilsDOM.setNSData(element, "title", pageTitle); } } }; /** * Build page * @method _build * @param {HTMLElement} element * @return {HTMLElement} * @protected * @member ns.widget.core.Page */ prototype._build = function (element) { var self = this; element.classList.add(classes.uiPage); self._buildHeader(element); self._buildFooter(element); self._buildContent(element); self._setTitle(element); self._setAria(); //it means that we are in wearable profile and we want to make a scrollview on page element (not content) if (self.options.enablePageScroll === true && !element.querySelector("." + classes.uiScroller)) { engine.instanceWidget(element, "Scrollview"); } return element; }; /** * This method sets page active or inactive. * * @example *
* * * @method setActive * @param {boolean} [value=true] If true, then page will be active. Otherwise, page will be inactive. * @member ns.widget.core.Page */ prototype.setActive = function (value) { var elementClassList = this.element.classList; if (value || value === undefined) { this.focus(); elementClassList.add(classes.uiPageActive); } else { this.blur(); elementClassList.remove(classes.uiPageActive); } }; /** * Return current status of page. * @method isActive * @member ns.widget.core.Page * @instance */ prototype.isActive = function () { return this.element.classList.contains(classes.uiPageActive); }; /** * Sets the focus to page * @method focus * @member ns.widget.core.Page */ prototype.focus = function () { var element = this.element, focusable = element.querySelector("[autofocus]") || element; focusable.focus(); }; /** * Removes focus from page and all descendants * @method blur * @member ns.widget.core.Page */ prototype.blur = function () { var element = this.element, focusable = document.activeElement || element; focusable.blur(); }; /** * Bind events to widget * @method _bindEvents * @protected * @member ns.widget.core.Page */ prototype._bindEvents = function () { var self = this; self._contentFillAfterResizeCallback = self._contentFill.bind(self); window.addEventListener("resize", self._contentFillAfterResizeCallback, false); }; /** * Refresh widget structure * @method _refresh * @protected * @member ns.widget.core.Page */ prototype._refresh = function () { this._restoreContentStyle(); this._contentFill(); }; /** * Layouting page structure * @method layout * @internal * @member ns.widget.core.Page */ prototype.layout = function () { this._storeContentStyle(); this._contentFill(); }; /** * This method triggers BEFORE_SHOW event. * @method onBeforeShow * @internal * @member ns.widget.core.Page */ prototype.onBeforeShow = function () { this.trigger(EventType.BEFORE_SHOW); }; /** * This method triggers SHOW event. * @method onShow * @internal * @member ns.widget.core.Page */ prototype.onShow = function () { this.trigger(EventType.SHOW); }; /** * This method triggers BEFORE_HIDE event. * @method onBeforeHide * @internal * @member ns.widget.core.Page */ prototype.onBeforeHide = function () { this.trigger(EventType.BEFORE_HIDE); }; /** * This method triggers HIDE event. * @method onHide * @internal * @member ns.widget.core.Page */ prototype.onHide = function () { this._restoreContentStyle(); this.trigger(EventType.HIDE); }; /** * Destroy widget * @method _destroy * @param {HTMLElement} element * @protected * @member ns.widget.core.Page */ prototype._destroy = function (element) { var self = this; element = element || self.element; window.removeEventListener("resize", self._contentFillAfterResizeCallback, false); // destroy widgets on children engine.destroyAllWidgets(element, true); self._contentFillAfterResizeCallback = null; }; /** * Return scroller * @method getScroller * @member ns.widget.core.Page */ prototype.getScroller = function () { var element = this.element, scroller = element.querySelector("." + classes.uiScroller); return scroller || element.querySelector("." + classes.uiContent) || element; }; Page.prototype = prototype; Page.createEmptyElement = function () { var div = document.createElement("div"); div.classList.add(classes.uiPage); return div; }; engine.defineWidget( "Page", "[data-role=page],.ui-page", [ "focus", "blur", "setActive" ], Page, // for register in jQuery Mobile space "mobile" ); ns.widget.core.Page = Page; }(window.document, ns)); /** * #Footer * * ## Creating footer * * @example template *
* * @class ns.widget.core.Footer * @component-selector .ui-page > footer, .ui-page .ui-footer * @component-type layout-component * @extends ns.widget.BaseWidget * */ /** * #Header * * @example template *

Header

* * @class ns.widget.core.Header * @component-selector .ui-page > header, .ui-page > .ui-header * @component-type layout-component * @extends ns.widget.BaseWidget */ /** * Button for menu with icon in header * @style ui-more * @selector .ui-btn * @member ns.widget.core.Header * @wearable * @since 2.3.1 */ /** * Icon style for menu button * @style ui-icon-detail * @selector .ui-btn.ui-more * @member ns.widget.core.Header * @wearable * @since 2.3.1 */ /** * Icon style for menu button * @style ui-icon-overflow * @selector .ui-btn.ui-more * @member ns.widget.core.Header * @wearable * @since 2.3.1 */ /** * Icon style for menu button * @style ui-icon-selectall * @selector .ui-btn.ui-more * @member ns.widget.core.Header * @wearable * @since 2.3.1 */ /** * #Content * * @example template *
* * @class ns.widget.core.Content * @component-selector .ui-content * @component-type container-component * @component-constraint 'bottom-button', 'checkbox', 'listview', 'processing', 'closet-tau-circle-progress', 'radio', 'toggleswitch', 'text', 'closet-image', 'sectionchanger' * @extends ns.widget.BaseWidget */ /** * Defines the buttons inside column width as 100% of the screen * @style ui-grid-col-1 * @selector div:not(.ui-grid-col-1):not(.ui-grid-col-2):not(.ui-grid-col-3):not(.ui-grid-row) * @member ns.widget.core.Content * @wearable * @since 2.3.1 */ /** * Defines the buttons inside column width as 50% of the screen * @style ui-grid-col-2 * @selector div:not(.ui-grid-col-1):not(.ui-grid-col-2):not(.ui-grid-col-3):not(.ui-grid-row) * @member ns.widget.core.Content * @wearable * @since 2.3.1 */ /** * Defines the buttons inside column width as 50% of the screen * @style ui-grid-col-3 * @selector div:not(.ui-grid-col-1):not(.ui-grid-col-2):not(.ui-grid-col-3):not(.ui-grid-row) * @member ns.widget.core.Content * @wearable * @since 2.3.1 */ /** * Arranges the buttons inside in a row * @style ui-grid-row * @selector div:not(.ui-grid-col-1):not(.ui-grid-col-2):not(.ui-grid-col-3):not(.ui-grid-row) * @member ns.widget.core.Content * @wearable * @since 2.3.1 */ ; /*global window, ns, define */ /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /*jslint nomen: true, plusplus: true */ /** * # PageContainer Widget * PageContainer is a widget, which is supposed to have multiple child pages but display only one at a time. * * @class ns.widget.core.PageContainer * @extends ns.widget.BaseWidget * @author Maciej Urbanski * @author Piotr Karny * @author Krzysztof Głodowski */ (function (document, ns) { "use strict"; var BaseWidget = ns.widget.BaseWidget, util = ns.util, DOM = util.DOM, engine = ns.engine, classes = { pageContainer: "ui-page-container", uiViewportTransitioning: "ui-viewport-transitioning", out: "out", in: "in", reverse: "reverse", uiPreIn: "ui-pre-in", uiBuild: "ui-page-build" }, PageContainer = function () { /** * Active page. * @property {ns.widget.core.Page} [activePage] * @member ns.widget.core.PageContainer */ this.activePage = null; this.inTransition = false; }, EventType = { /** * Triggered before the changePage() request * has started loading the page into the DOM. * @event pagebeforechange * @member ns.widget.core.PageContainer */ PAGE_BEFORE_CHANGE: "pagebeforechange", /** * Triggered after the changePage() request * has finished loading the page into the DOM and * all page transition animations have completed. * @event pagechange * @member ns.widget.core.PageContainer */ PAGE_CHANGE: "pagechange", PAGE_REMOVE: "pageremove" }, animationend = "animationend", webkitAnimationEnd = "webkitAnimationEnd", mozAnimationEnd = "mozAnimationEnd", msAnimationEnd = "msAnimationEnd", oAnimationEnd = "oAnimationEnd", animationEndNames = [ animationend, webkitAnimationEnd, mozAnimationEnd, msAnimationEnd, oAnimationEnd ], prototype = new BaseWidget(); //When resolved deferred function is responsible for triggering events related to page change as well as //destroying unused widgets from last page and/or removing last page function deferredFunction(fromPageWidget, toPageWidget, self, options) { if (fromPageWidget) { fromPageWidget.onHide(); if (options.reverse) { fromPageWidget.destroy(); } self._removeExternalPage(fromPageWidget, options); } toPageWidget.onShow(); self.trigger(EventType.PAGE_CHANGE); } /** * Dictionary for PageContainer related event types. * @property {Object} events * @property {string} [events.PAGE_CHANGE="pagechange"] * @member ns.router.route.popup * @static */ PageContainer.events = EventType; /** * Dictionary for PageContainer related css class names * @property {Object} classes * @member ns.widget.core.Page * @static * @readonly */ PageContainer.classes = classes; /** * Build widget structure * @method _build * @param {HTMLElement} element * @return {HTMLElement} * @member ns.widget.core.PageContainer * @protected */ prototype._build = function (element) { element.classList.add(classes.pageContainer); return element; }; /** * This method changes active page to specified element. * @method change * @param {HTMLElement} toPageElement The element to set * @param {Object} [options] Additional options for the transition * @param {string} [options.transition=none] Specifies the type of transition * @param {boolean} [options.reverse=false] Specifies the direction of transition * @member ns.widget.core.PageContainer */ prototype.change = function (toPageElement, options) { var self = this, fromPageWidget = self.getActivePage(), toPageWidget, calculatedOptions = options || {}; // store options to detect that option was changed before process finish self._options = calculatedOptions; calculatedOptions.widget = calculatedOptions.widget || "Page"; // The change should be made only if no active page exists // or active page is changed to another one. if (!fromPageWidget || (fromPageWidget.element !== toPageElement)) { if (toPageElement.parentNode !== self.element) { toPageElement = self._include(toPageElement); } self.trigger(EventType.PAGE_BEFORE_CHANGE); toPageElement.classList.add(classes.uiBuild); toPageWidget = engine.instanceWidget(toPageElement, calculatedOptions.widget); // set sizes of page for correct display toPageWidget.layout(); if (toPageWidget.option("autoBuildWidgets")) { engine.createWidgets(toPageElement); } if (fromPageWidget) { fromPageWidget.onBeforeHide(); } toPageWidget.onBeforeShow(); toPageElement.classList.remove(classes.uiBuild); // if options is different that this mean that another change page was called and we need stop // previous change page if (calculatedOptions === self._options) { calculatedOptions.deferred = { resolve: deferredFunction }; self._transition(toPageWidget, fromPageWidget, calculatedOptions); } } }; /** * This method performs transition between the old and a new page. * @method _transition * @param {ns.widget.core.Page} toPageWidget The new page * @param {ns.widget.core.Page} fromPageWidget The page to be replaced * @param {Object} [options] Additional options for the transition * @param {string} [options.transition=none] The type of transition * @param {boolean} [options.reverse=false] Specifies transition direction * @param {Object} [options.deferred] Deferred object * @member ns.widget.core.PageContainer * @protected */ prototype._transition = function (toPageWidget, fromPageWidget, options) { var self = this, element = self.element, elementClassList = element.classList, transition = !fromPageWidget || !options.transition ? "none" : options.transition, deferred = options.deferred, clearClasses = [classes.in, classes.out, classes.uiPreIn, transition], oldDeferredResolve, oneEvent; if (options.reverse) { clearClasses.push(classes.reverse); } self.inTransition = true; elementClassList.add(classes.uiViewportTransitioning); oldDeferredResolve = deferred.resolve; deferred.resolve = function () { var fromPageWidgetClassList = fromPageWidget && fromPageWidget.element.classList, toPageWidgetClassList = toPageWidget.element.classList; self._setActivePage(toPageWidget); self._clearTransitionClasses(clearClasses, fromPageWidgetClassList, toPageWidgetClassList); oldDeferredResolve(fromPageWidget, toPageWidget, self, options); }; if (transition !== "none") { oneEvent = function () { toPageWidget.off( animationEndNames, oneEvent, false ); deferred.resolve(); }; toPageWidget.on( animationEndNames, oneEvent, false ); self._appendTransitionClasses(fromPageWidget, toPageWidget, transition, options.reverse); } else { window.setTimeout(deferred.resolve, 0); } }; /** * This method adds proper transition classes to specified page widgets. * @param {ns.widget.core.Page} fromPageWidget Page widget from which transition will occur * @param {ns.widget.core.Page} toPageWidget Destination page widget for transition * @param {string} transition Specifies the type of transition * @param {boolean} reverse Specifies the direction of transition * @member ns.widget.core.PageContainer * @protected */ prototype._appendTransitionClasses = function (fromPageWidget, toPageWidget, transition, reverse) { var classList; if (fromPageWidget) { classList = fromPageWidget.element.classList; classList.add(transition, classes.out); if (reverse) { classList.add(classes.reverse); } } classList = toPageWidget.element.classList; classList.add(transition, classes.in, classes.uiPreIn); if (reverse) { classList.add(classes.reverse); } }; /** * This method removes transition classes from classLists of page widget elements. * @param {Object} clearClasses An array containing classes to be removed * @param {Object} fromPageWidgetClassList classList object from source page element * @param {Object} toPageWidgetClassList classList object from destination page element * @member ns.widget.core.PageContainer * @protected */ prototype._clearTransitionClasses = function (clearClasses, fromPageWidgetClassList, toPageWidgetClassList) { var self = this, element = self.element, elementClassList = element.classList; elementClassList.remove(classes.uiViewportTransitioning); self.inTransition = false; clearClasses.forEach(function (className) { toPageWidgetClassList.remove(className); }); if (fromPageWidgetClassList) { clearClasses.forEach(function (className) { fromPageWidgetClassList.remove(className); }); } }; /** * This method adds an element as a page. * @method _include * @param {HTMLElement} page an element to add * @return {HTMLElement} * @member ns.widget.core.PageContainer * @protected */ prototype._include = function (page) { var element = this.element; if (!page.parentNode || page.ownerDocument !== document) { page = util.importEvaluateAndAppendElement(page, element); } return page; }; /** * This method sets currently active page. * @method _setActivePage * @param {ns.widget.core.Page} page a widget to set as the active page * @member ns.widget.core.PageContainer * @protected */ prototype._setActivePage = function (page) { var self = this; if (self.activePage) { self.activePage.setActive(false); } self.activePage = page; page.setActive(true); }; /** * This method returns active page widget. * @method getActivePage * @member ns.widget.core.PageContainer * @return {ns.widget.core.Page} Currently active page */ prototype.getActivePage = function () { return this.activePage; }; /** * This method removes page element from the given widget and destroys it. * @method _removeExternalPage * @param {ns.widget.core.Page} fromPageWidget the widget to destroy * @param {Object} [options] transition options * @param {boolean} [options.reverse=false] specifies transition direction * @member ns.widget.core.PageContainer * @protected */ prototype._removeExternalPage = function (fromPageWidget, options) { var fromPageElement = fromPageWidget.element; if (options && options.reverse && DOM.hasNSData(fromPageElement, "external") && fromPageElement.parentNode) { fromPageElement.parentNode.removeChild(fromPageElement); this.trigger(EventType.PAGE_REMOVE); } }; PageContainer.prototype = prototype; // definition ns.widget.core.PageContainer = PageContainer; engine.defineWidget( "pagecontainer", "", ["change", "getActivePage"], PageContainer, "core" ); }(window.document, ns)); /*global define, ns */ /*jslint nomen: true, plusplus: true, bitwise: false */ /* * Copyright (c) 2010 - 2014 Samsung Electronics Co., Ltd. * License : MIT License V2 */ /** * #Template Manager * * Object menage template's engines and renderer HTMLElement by template engine. * * @class ns.template * @since 2.4 * @author Maciej Urbanski * @author Jadwiga Sosnowska * @author Krzysztof Antoszek */ (function () { "use strict"; var utilPath = ns.util.path, template, templateFunctions = {}, globalOptions = { "pathPrefix": "", "default": "" }; /** * Function to get global option * * @example * tau.template.get("pathPrefix"); * // -> "/prefix/to/all/paths" * * @method get * @param {string} name param name which will be return * @return {*} return value of option * @since 2.4 * @member ns.template */ function get(name) { return globalOptions[name]; } /** * Function to set global option * * @example * tau.template.set("pathPrefix", "/views"); * * @method set * @param {string} name param name which will be set * @param {*} value value to set * @since 2.4 * @member ns.template */ function set(name, value) { globalOptions[name] = value; } /** * Register new template function * * Template function should have 4 arguments: * * - globalOptions - global options of template engine * - path - path or id of template content * - data - data for template render * - callback - callback call on finish * * and should call callback on finish with arguments: * * - status - object describing status of render * - element - base HTMLElement of template results (on error can be null) * * after registration you can use engine in render function. * * @example * tau.template.register("inline", function(globalOptions, path, data, callback) { * callback({ * success: true * }, * document.createElement("div") * ); * }); * * @method register * @param {string} name Engine name * @param {Function} templateFunction function to renderer template * @since 2.4 * @member ns.template */ function register(name, templateFunction) { templateFunctions[name] = templateFunction; } /** * Unregister template function * * @method unregister * @param {string} name Engine name * @since 2.4 * @member ns.template */ function unregister(name) { templateFunctions[name] = null; } /** * Return engine with given name * * @method engine * @param {string} name Engine name * @since 2.4 * @member ns.template */ function engine(name) { return templateFunctions[name]; } /** * Create absolute path for given path. * If parameter withProfile is true, the returned path will have name of profile * separated by dots before the last dot. * @method getAbsUrl * @param {string} path * @param {boolean} withProfile Create path with profile's name * @return {string} changed path * @since 2.4 */ function getAbsUrl(path, withProfile) { var profile = ns.info.profile, lastDot = path.lastIndexOf("."); if (utilPath.isAbsoluteUrl(path)) { return path; } if (withProfile) { path = path.substring(0, lastDot) + "." + profile + path.substring(lastDot); } return utilPath.makeUrlAbsolute((globalOptions.pathPrefix || "") + path, utilPath.getLocation()); } /** * Return HTMLElement for given path * * When engine name is not given then get default name from global options. If this is not set then get first registered engine. * * Result of this method is handed to callback. First parameter of callback is object with status. Second is HTMLElement generated by engine. * * Status object contains properties: * * - _boolean_ success - inform about success or error * - _string_ description contains details on error * * @example * tau.template.render("external/path/to/file.html", {additionalParameter: true}, function(status, element) { * if (status.success) { * document.body.appendChild(element); * } else { * console.error(status.description); * }, "html"); * * @method render * @param {string} path Path to file ot other id for template system * @param {Object} data additional data for template system * @param {Function} callback function which will be called on finish * @param {string} [engineName] engine name * @since 2.4 * @member ns.template */ function render(path, data, callback, engineName) { var templateFunction = templateFunctions[engineName || get("default") || ""], targetCallback = function (status, element) { // add current patch status.absUrl = targetPath; callback(status, element); }, templateCallback = function (status, element) { if (status.success) { // path was found and callback can be called targetCallback(status, element); } else { // try one more time with path without profile targetPath = getAbsUrl(path, false); templateFunction(globalOptions, targetPath, data || {}, targetCallback); } }, targetPath; // if template engine name and default name is not given then we // take first registered engine if (!templateFunction) { templateFunction = templateFunctions[Object.keys(templateFunctions).pop()]; } // if template system exists then we go to him if (templateFunction) { targetPath = getAbsUrl(path, ns.getConfig("findProfileFile", false)); templateFunction(globalOptions, targetPath, data || {}, templateCallback); } else { // else we return error callback({ success: false, description: "Can't get engine system" }, null); } } template = { get: get, set: set, register: register, unregister: unregister, engine: engine, render: render }; ns.template = template; }()); /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* global define, HTMLElement, ns */ /** * #Router * * Main class to navigate between pages, popups and other widgets which has own rules in all profiles. * * Class communicates with PageContainer which deactivate and activate changed pages. * * Router listening on events triggered by history manager. * * ## Getting instance * * To receive instance of router you should use method _getInstance_ * * @example * var router = ns.router.Router.getInstance(); * * By default TAU create instance of router and getInstance method return this instance. * * ##Connected widgets * * Router cooperate with widgets: * * - Page * - Popup * - Drawer * - Dialog (mobile) * - CircularIndexScrollBar (wearable - circle) * * Opening or closing these widgets are possible by create link with correct rel. * * ##Global options used in router * * - *pageContainer* = document.body - default container element * - *pageContainerBody* = false - use body instead pageContainer option * - *autoInitializePage* = true - automatically initialize first page * - *addPageIfNotExist* = true - automatically add page if doesn't exist * - *loader* = false - enable loader on change page * - *disableRouter* = false - disable auto initialize of router * * @class ns.router.Router * @author Maciej Urbanski * @author Piotr Karny * @author Tomasz Lukawski * @author Hyunkook, Cho * @author Piotr Czajka * @author Junhyeon Lee * @author Michał Szepielak * @author Jadwiga Sosnowska * @author Heeju Joo */ (function (window, document) { "use strict"; /** * Local alias for ns.util * @property {Object} util Alias for {@link ns.util} * @member ns.router.Router * @static * @private */ var util = ns.util, /** * Local alias for ns.event * @property {Object} eventUtils Alias for {@link ns.event} * @member ns.router.Router * @static * @private */ eventUtils = ns.event, /** * Alias for {@link ns.util.DOM} * @property {Object} DOM * @member ns.router.Router * @static * @private */ DOM = util.DOM, /** * Local alias for ns.util.path * @property {Object} path Alias for {@link ns.util.path} * @member ns.router.Router * @static * @private */ path = util.path, /** * Local alias for ns.util.selectors * @property {Object} selectors Alias for {@link ns.util.selectors} * @member ns.router.Router * @static * @private */ selectors = util.selectors, /** * Local alias for ns.util.object * @property {Object} object Alias for {@link ns.util.object} * @member ns.router.Router * @static * @private */ object = util.object, /** * Local alias for ns.engine * @property {Object} engine Alias for {@link ns.engine} * @member ns.router.Router * @static * @private */ engine = ns.engine, /** * Local alias for ns.router * @property {Object} router Alias for namespace ns.router * @member ns.router.Router * @static * @private */ router = ns.router, /** * Local alias for ns.history * @property {Object} history Alias for {@link ns.history} * @member ns.router.Router * @static * @private */ history = ns.history, historyManager = history.manager, /** * Local alias for ns.history.manager.events * @property {Object} historyManagerEvents Alias for (@link ns.history.manager.events} * @member ns.router.Router * @static * @private */ historyManagerEvents = historyManager.events, /** * Local alias for ns.router.route * @property {Object} route Alias for namespace ns.router.route * @member ns.router.Router * @static * @private */ route = router.route, /** * Local alias for document body element * @property {HTMLElement} body * @member ns.router.Router * @static * @private */ body = document.body, /** * Alias to Array.slice method * @method slice * @member ns.router.Router * @private * @static */ slice = [].slice, /** * Local instance of the Router * @property {Object} routerInstance * @member ns.router.Router * @static * @private */ _isLock = false, ORDER_NUMBER = { 1: "page", 10: "panel", 100: "popup", 101: "dialog", 1000: "drawer", 2000: "circularindexscrollbar" }, eventType = { BEFORE_ROUTER_INIT: "beforerouterinit", ROUTER_INIT: "routerinit" }, HASH_REGEXP = /[#|\s]/g, Page = ns.widget.core.Page, routerInstance, template = ns.template, Router = function () { var self = this; /** * Instance of widget PageContainer which controls page changing. * @property {?ns.widget.core.PageContainer} [container=null] * @member ns.router.Router */ self.container = null; /** * Settings for last call of method open * @property {Object} [settings={}] * @member ns.router.Router */ self.settings = {}; /** * Handler for event "statechange" * @property {Function} [_onStateChangeHandler=null] * @member ns.router.Router * @protected * @since 2.4 */ self._onStateChangeHandler = null; /** * Handler for event "hashchange" * @property {Function} [_onHashChangeHandler=null] * @member ns.router.Router * @protected * @since 2.4 */ self._onHashChangeHandler = null; /** * Handler for event "controllercontent" * @property {Function} [_onControllerContent=null] * @member ns.router.Router * @protected * @since 2.4 */ self._onControllerContent = null; /** * Router locking flag * @property {boolean} locked=false * @member ns.router.Router * @since 2.4 */ self.locked = false; }; /** * Default values for router * @property {Object} defaults * @property {boolean} [defaults.fromHashChange=false] Sets if will be changed after hashchange. * @property {boolean} [defaults.reverse=false] Sets the direction of change. * @property {boolean} [defaults.volatileRecord=false] Sets if the current history entry will be modified or a new one will be created. * @member ns.router.Router */ Router.prototype.defaults = { fromHashChange: false, reverse: false, volatileRecord: false }; /** * Find the closest link for element * @method findClosestLink * @param {HTMLElement} element * @return {HTMLElement} * @private * @static * @member ns.router.Router */ function findClosestLink(element) { while (element) { if (element.nodeType === Node.ELEMENT_NODE && element.nodeName && element.nodeName === "A") { break; } element = element.parentNode; } return element; } /** * Handle event link click * @method linkClickHandler * @param {ns.router.Router} router * @param {Event} event * @private * @static * @member ns.router.Router */ function linkClickHandler(router, event) { var link = findClosestLink(event.target), href, useDefaultUrlHandling, options; if (link && event.which === 1) { href = link.getAttribute("href"); useDefaultUrlHandling = (link.getAttribute("rel") === "external") || link.hasAttribute("target"); if (!useDefaultUrlHandling) { options = DOM.getData(link); router.open(href, options, event); eventUtils.preventDefault(event); } } } Router.prototype.linkClick = function (event) { linkClickHandler(this, event); }; function openUrlFromState(instanceRouter, state) { var rules = router.route, prevState = history.activeState, reverse = state && history.getDirection(state) === "back", maxOrderNumber, orderNumberArray = [], ruleKey, options, url = path.getLocation(), isContinue = true, transition, rule; transition = reverse ? ((prevState && prevState.transition) || "none") : state.transition; options = object.merge({}, state, { reverse: reverse, transition: transition, fromHashChange: true }); // find rule with max order number for (ruleKey in rules) { if (rules.hasOwnProperty(ruleKey) && rules[ruleKey].active) { orderNumberArray.push(rules[ruleKey].orderNumber); } } maxOrderNumber = Math.max.apply(null, orderNumberArray); rule = rules[ORDER_NUMBER[maxOrderNumber]]; if (rule && rule.onHashChange(url, options, prevState)) { if (maxOrderNumber === 10) { // rule is panel return; } isContinue = false; } history.setActive(state); if (isContinue) { instanceRouter.open(state.url, options); } } /** * Detect rel attribute from HTMLElement. * * This method tries to match element to each rule filter and return first rule name which match. * * If don't match any rule then return null. * * @example * var router = tau.router.Router.getInstance(); * router.detectRel(document.getElementById("pageId")); * // if HTML element will be match to selector of page then return rule for page * * @param {HTMLElement} to element to check * @member ns.router.Router * @return {?string} */ Router.prototype.detectRel = function (to) { var rule, i; for (i in route) { if (route.hasOwnProperty(i)) { rule = route[i]; if (selectors.matchesSelector(to, rule.filter)) { return i; } } } return null; }; /** * Open given page with deferred * @method _openDeferred * @param {HTMLElement} to HTMLElement of page * @param {Object} [options] * @param {"page"|"popup"|"external"} [options.rel = "page"] Represents kind of link as "page" * or "popup" * or "external" for linking to another domain. * @param {string} [options.transition = "none"] Sets the animation used during change of * page. * @param {boolean} [options.reverse = false] Sets the direction of change. * @param {boolean} [options.fromHashChange = false] Sets if will be changed after hashchange. * @param {boolean} [options.volatileRecord = false] Sets if the current history entry will * be modified or * a new one will be created. * @param {boolean} [options.dataUrl] Sets if page has url attribute. * @param {?string} [options.container = null] It is used in RoutePopup as selector for * container. * @param {Event} event * @member ns.router.Router * @protected */ Router.prototype._openDeferred = function (to, options, event) { var self = this, rule = route[options.rel], deferred = { resolve: function (_options, content) { rule.open(content, _options, event); }, reject: function (_options) { eventUtils.trigger(self.container.element, "changefailed", _options); } }; if (typeof to === "string") { if (to.replace(HASH_REGEXP, "")) { self._loadUrl(to, options, rule, deferred); } } else { // execute deferred object immediately if (to && selectors.matchesSelector(to, rule.filter)) { deferred.resolve(options, to); } else { deferred.reject(options); } } }; /** * Change page to page given in parameter "to". * * @example * var router = tau.router.Router.getInstance(); * router.open("pageId"); * // open page with given id * router.open("page.html"); * // open page from html file * router.open("popupId"); * // open popup with given id * * @method open * @param {string|HTMLElement} to Id of page or file url or HTMLElement of page * @param {Object} [options] * @param {"page"|"popup"|"external"} [options.rel="page"] Represents kind of link as "page" or "popup" or "external" for linking to another domain. * @param {string} [options.transition="none"] Sets the animation used during change of page. * @param {boolean} [options.reverse=false] Sets the direction of change. * @param {boolean} [options.fromHashChange=false] Sets if will be changed after hashchange. * @param {boolean} [options.volatileRecord=false] Sets if the current history entry will be modified or a new one will be created. * @param {boolean} [options.dataUrl] Sets if page has url attribute. * @param {?string} [options.container=null] It is used in RoutePopup as selector for container. * @param {Event} [event] Event object * @member ns.router.Router */ Router.prototype.open = function (to, options, event) { var self = this, rel, rule; if (!_isLock) { to = getHTMLElement(to); rel = (options && options.rel) || (to instanceof HTMLElement && self.detectRel(to)); rel = rel || "page"; rule = route[rel]; if (rel === "back") { history.back(); } else if (rule) { options = object.merge( { rel: rel }, self.defaults, rule.option(), options ); self._openDeferred(to, options, event); } else { throw new Error("Not defined router rule [" + rel + "]"); } } }; /** * Init routes defined in router * @method _initRoutes * @member ns.router.Router */ Router.prototype._initRoutes = function () { var ruleKey, rules = router.route; for (ruleKey in rules) { if (rules.hasOwnProperty(ruleKey) && rules[ruleKey].init) { rules[ruleKey].init(); } } }; function removeActivePageClass(containerElement) { var PageClasses = Page.classes, uiPageActiveSelector = "." + PageClasses.uiPageActive, activePages = slice.call(containerElement.querySelectorAll(uiPageActiveSelector)); activePages.forEach(function (page) { page.classList.remove(uiPageActiveSelector); }); } Router.prototype._autoInitializePage = function (containerElement, pages, pageSelector) { var self = this, page, location = window.location, uiPageActiveClass = Page.classes.uiPageActive, firstPage = containerElement.querySelector("." + uiPageActiveClass); if (!firstPage) { firstPage = pages[0]; } if (firstPage) { removeActivePageClass(containerElement); } if (location.hash) { //simple check to determine if we should show firstPage or other page = document.getElementById(location.hash.replace("#", "")); if (page && selectors.matchesSelector(page, pageSelector)) { firstPage = page; } } if (!firstPage && ns.getConfig("addPageIfNotExist", true)) { firstPage = Page.createEmptyElement(); while (containerElement.firstChild) { firstPage.appendChild(containerElement.firstChild); } containerElement.appendChild(firstPage); } if (self.justBuild) { if (firstPage) { self.register( engine.instanceWidget(containerElement, "pagecontainer"), firstPage ); } } return firstPage; }; /** * Method initializes page container and builds the first page if flag autoInitializePage is * set. * @method init * @param {boolean} justBuild * @member ns.router.Router */ Router.prototype.init = function (justBuild) { var containerElement, firstPage, pages, pageDefinition = ns.engine.getWidgetDefinition("Page"), pageSelector = pageDefinition.selector, self = this; eventUtils.trigger(document, eventType.BEFORE_ROUTER_INIT, self, false); body = document.body; self.justBuild = justBuild; containerElement = ns.getConfig("pageContainer") || body; pages = slice.call(containerElement.querySelectorAll(pageSelector)); if (!ns.getConfig("pageContainerBody", false)) { containerElement = pages.length ? pages[0].parentNode : containerElement; } if (ns.getConfig("autoInitializePage", true)) { firstPage = self._autoInitializePage(containerElement, pages, pageSelector); if (justBuild) { return; } } historyManager.enable(); // init router's routes self._initRoutes(); self.register( engine.instanceWidget(containerElement, "pagecontainer"), firstPage ); eventUtils.trigger(document, eventType.ROUTER_INIT, self, false); }; /** * Method removes all events listeners set by router. * * Also remove singleton instance of router; * * @example * var router = tau.router.Router.getInstance(); * router.destroy(); * var router2 = tau.router.Router.getInstance(); * // router !== router2 * * @method destroy * @member ns.router.Router */ Router.prototype.destroy = function () { var self = this; historyManager.disable(); window.removeEventListener("popstate", self.popStateHandler, false); if (body) { body.removeEventListener("pagebeforechange", self.pagebeforechangeHandler, false); body.removeEventListener("vclick", self.linkClickHandler, false); } }; /** * Method sets instance of PageContainer widget * * @example * var router = tau.router.Router.getInstance(); * router.setContainer(new ns.widget.PageContainer()); * * @method setContainer * @param {ns.widget.core.PageContainer} container * @member ns.router.Router */ Router.prototype.setContainer = function (container) { this.container = container; }; /** * Method returns instance of PageContainer widget * * @example * var router = tau.router.Router.getInstance(); * containerWidget = router.getContainer(); * * @method getContainer * @return {ns.widget.core.PageContainer} * @member ns.router.Router */ Router.prototype.getContainer = function () { return this.container; }; /** * Method returns ths first page HTMLElement * @method getFirstPage * @return {HTMLElement} * @member ns.router.Router */ Router.prototype.getFirstPage = function () { return this.getRoute("page").getFirstElement(); }; /** * Callback for event "historyhashchange" which is triggered by history manager after hash is changed * @param {ns.router.Router} router * @param {Event} event */ function onHistoryHashChange(router, event) { openUrlFromState(router, event.detail); } /** * Callback for event "historystatechange" which is triggered by history manager after hash is changed * @param {ns.router.Router} router * @param {Event} event */ function onHistoryStateChange(router, event) { var options = event.detail, // url = options.reverse ? options.url : (options.href || options.url); delete options.event; router.open(url, options); // prevent current event eventUtils.preventDefault(event); eventUtils.stopImmediatePropagation(event); } /** * Convert HTML string to HTMLElement * @param {string|HTMLElement} content * @param {string} title * @return {?HTMLElement} */ function convertToNode(content, title) { var contentNode = null, externalDocument = document.implementation.createHTMLDocument(title), externalBody = externalDocument.body; if (content instanceof HTMLElement) { // if content is HTMLElement just set to contentNode contentNode = content; } else { // otherwise convert string to HTMLElement try { externalBody.insertAdjacentHTML("beforeend", content); contentNode = externalBody.firstChild; } catch (e) { ns.error("Failed to inject element", e); } } return contentNode; } /** * Set data-url on HTMLElement if not exists * @param {HTMLElement} contentNode * @param {string} url */ function setURLonElement(contentNode, url) { if (url) { if (contentNode instanceof HTMLElement && !DOM.hasNSData(contentNode, "url")) { // if url is missing we need set data-url attribute for good finding by method open in router url = url.replace(/^#/, ""); DOM.setNSData(contentNode, "url", url); } } } /** * Callback for event "controller-content-available" which is triggered by controller after application handle hash change * @param {ns.router.Router} router * @param {Event} event */ function onControllerContent(router, event) { var data = event.detail, content = data.content, options = data.options, contentNode, url = (options.href || options.url); // if controller give content if (content) { // convert to node if content is string contentNode = convertToNode(content, options.title); // set data-url on node setURLonElement(contentNode, url); // calling open method router.open(contentNode, options); //prevent event eventUtils.preventDefault(event); } } /** * Method registers page container and the first page. * * @example * var router = tau.router.Router.getInstance(); * router.register(new ns.widget.PageContainer(), document.getElementById("firstPage")); * * @method register * @param {ns.widget.core.PageContainer} container * @param {HTMLElement} firstPage * @member ns.router.Router */ Router.prototype.register = function (container, firstPage) { var self = this, routePopup = this.getRoute("popup"); // sets instance of PageContainer widget self.container = container; // sets first page HTMLElement self.getRoute("page").setFirstElement(firstPage); eventUtils.trigger(document, "themeinit", self); // sets events handlers if (!self._onHashChangeHandler) { self._onHashChangeHandler = onHistoryHashChange.bind(null, self); window.addEventListener(historyManagerEvents.HASHCHANGE, self._onHashChangeHandler, false); } if (!self._onStateChangeHandler) { self._onStateChangeHandler = onHistoryStateChange.bind(null, self); window.addEventListener(historyManagerEvents.STATECHANGE, self._onStateChangeHandler, false); } if (!self._onControllerContent) { self._onControllerContent = onControllerContent.bind(null, self); window.addEventListener("controller-content-available", self._onControllerContent, false); } // if loader config is set then create loader widget if (ns.getConfig("loader", false)) { container.element.appendChild(self.getLoader().element); } // set history support history.enableVolatileMode(); // if first page exist open this page without transition if (firstPage) { self.open(firstPage, {transition: "none"}); } if (routePopup) { routePopup.setActive(null); } }; /** * Convert string id to HTMLElement or return HTMLElement if is given * @param {string|HTMLElement} idOrElement * @return {HTMLElement|string} */ function getHTMLElement(idOrElement) { var stringId, toElement; // if given argument is string then if (typeof idOrElement === "string") { if (idOrElement[0] === "#") { // trim first char if it is # stringId = idOrElement.substr(1); } else { stringId = idOrElement; } // find element by id toElement = document.getElementById(stringId); if (toElement) { // is exists element by id then return it idOrElement = toElement; } // otherwise return string } return idOrElement; } /** * Method close route element, eg page or popup. * * @example * var router = tau.router.Router.getInstance(); * router.close("popupId", {transition: "none"}); * * @method close * @param {string|HTMLElement} to Id of page or file url or HTMLElement of page * @param {Object} [options] * @param {"page"|"popup"|"external"} [options.rel="page"] Represents kind of link as "page" or "popup" or "external" for linking to another domain * @member ns.router.Router */ Router.prototype.close = function (to, options) { var rel = "back", closingWidget = getHTMLElement(to), rule; if (options && options.rel) { rel = options.rel; } else if (closingWidget) { rel = this.detectRel(closingWidget); } rule = route[rel]; // if router is not locked if (!this.locked) { // if rel is back then call back method if (rel === "back") { history.back(); } else { // otherwise if rule exists if (rule) { // call close on rule rule.close(closingWidget, options); } else { throw new Error("Not defined router rule [" + rel + "]"); } } } }; /** * Method back to previous state. * * @example * var router = tau.router.Router.getInstance(); * router.back(); * * @method close * @member ns.router.Router */ Router.prototype.back = function () { // if router is not locked if (!this.locked) { history.back(); } }; /** * Method opens popup. * * @example * var router = tau.router.Router.getInstance(); * router.openPopup("popupId", {transition: "none"}); * * @method openPopup * @param {HTMLElement|string} to Id or HTMLElement of popup. * @param {Object} [options] * @param {string} [options.transition="none"] Sets the animation used during change of page. * @param {boolean} [options.reverse=false] Sets the direction of change. * @param {boolean} [options.fromHashChange=false] Sets if will be changed after hashchange. * @param {boolean} [options.volatileRecord=false] Sets if the current history entry will be modified or a new one will be created. * @param {boolean} [options.dataUrl] Sets if page has url attribute. * @param {?string} [options.container=null] It is used in RoutePopup as selector for container. * @member ns.router.Router */ Router.prototype.openPopup = function (to, options) { // call method open with overwrite rel option this.open(to, object.fastMerge({rel: "popup"}, options)); }; /** * Method closes popup. * * @example * var router = tau.router.Router.getInstance(); * router.closePopup(); * * @method closePopup * @param {Object} options * @param {string=} [options.transition] * @param {string=} [options.ext="in ui-pre-in"] options.ext * @member ns.router.Router */ Router.prototype.closePopup = function (options) { var popupRoute = this.getRoute("popup"); if (popupRoute) { popupRoute.close(null, options); } }; /** * Lock router * @method lock * @member ns.router.Router */ Router.prototype.lock = function () { this.locked = true; }; /** * Unlock router and history manager * @method unlock * @member ns.router.Router */ Router.prototype.unlock = function () { this.locked = false; }; /** * Load content from url. * * Method prepare url and call template function to load external file. * * If option showLoadMsg is ste to tru open loader widget before start loading. * * @method _loadUrl * @param {string} url full URL to load * @param {Object} options options for this and next methods in chain * @param {boolean} [options.data] Sets if page has url attribute. * @param {Object} rule rule which support given call * @param {Object} deferred object with callbacks * @param {Function} deferred.reject callback on error * @param {Function} deferred.resolve callback on success * @member ns.router.Router * @protected */ Router.prototype._loadUrl = function (url, options, rule, deferred) { var absUrl = path.makeUrlAbsolute(url, path.getLocation()), content, self = this, data = options.data || {}; // check if content is loaded in current document content = rule.find(absUrl); // if content doesn't find and url is embedded url if (!content && path.isEmbedded(absUrl)) { // reject deferred.reject({}); } else { // If the content we are interested in is already in the DOM, // and the caller did not indicate that we should force a // reload of the file, we are done. Resolve the deferred so that // users can bind to .done on the promise if (content) { // content was found and we resolve deferred.resolve(object.fastMerge({absUrl: absUrl}, options), content); } else { // Load the new content. eventUtils.trigger(self.getContainer().element, options.rel + "beforeload"); // force return full document from template system data.fullDocument = true; // we put url, not the whole path to function render, // because this path can be modified by template's module template.render(url, data, function (status, element) { // if template loaded successful if (status.success) { self._loadSuccess(status.absUrl, options, rule, deferred, element); eventUtils.trigger(self.getContainer().element, options.rel + "load"); } else { self._loadError(status.absUrl, options, deferred); } }); } } }; /** * Error handler for loading content by AJAX * @method _loadError * @param {string} absUrl full URL to load * @param {Object} options options for this and next methods in chain * @param {boolean} [options.showLoadMsg=true] Sets if message will be shown during loading. * @param {Object} deferred object with callbacks * @param {Function} deferred.reject callback on error * @member ns.router.Router * @protected */ Router.prototype._loadError = function (absUrl, options, deferred) { var detail = object.fastMerge({url: absUrl}, options), self = this; ns.error("load error, file: ", absUrl); self.container.trigger("loadfailed", detail); deferred.reject(detail); }; // TODO it would be nice to split this up more but everything appears to be "one off" // or require ordering such that other bits are sprinkled in between parts that // could be abstracted out as a group /** * Success handler for loading content by AJAX * @method _loadSuccess * @param {string} absUrl full URL to load * @param {Object} options options for this and next methods in chain * @param {boolean} [options.showLoadMsg=true] Sets if message will be shown during loading. * @param {Object} rule rule which support given call * @param {Object} deferred object with callbacks * @param {Function} deferred.reject callback on error * @param {Function} deferred.resolve callback on success * @param {string} html * @member ns.router.Router * @protected */ Router.prototype._loadSuccess = function (absUrl, options, rule, deferred, html) { var detail = object.fastMerge({url: absUrl}, options), // find element with given id in returned html content = rule.parse(html, absUrl); if (content) { deferred.resolve(detail, content); } else { deferred.reject(detail); } }; // TODO the first page should be a property set during _create using the logic // that currently resides in init /** * Get initial content * @method _getInitialContent * @member ns.router.Router * @return {HTMLElement} the first page * @protected */ Router.prototype._getInitialContent = function () { return this.getRoute("page").getFirstElement(); }; /** * Report an error loading * @method _showError * @param {string} absUrl * @member ns.router.Router * @protected */ Router.prototype._showError = function (absUrl) { ns.error("load error, file: ", absUrl); }; /** * Returns Page or Popup widget * @param {string} [routeName="page"] in default page or popup * @method getActive * @return {ns.widget.BaseWidget} * @member ns.router.Router */ Router.prototype.getActive = function (routeName) { var route = this.getRoute(routeName || "page"); return route && route.getActive(); }; /** * Returns true if element in given route is active. * @param {string} [routeName="page"] in default page or popup * @method hasActive * @return {boolean} * @member ns.router.Router */ Router.prototype.hasActive = function (routeName) { var route = this.getRoute(routeName || "page"); return !!(route && route.hasActive()); }; /** * Returns true if any popup is active. * * @example * var router = tau.router.Router.getInstance(), * hasActivePopup = router.hasActivePopup(); * // -> true | false * * @method hasActivePopup * @return {boolean} * @member ns.router.Router */ Router.prototype.hasActivePopup = function () { return this.hasActive("popup"); }; /** * This function returns proper route. * * @example * var router = tau.router.Router.getInstance(), * route = router.getRoute("page"), * // -> Object with pages support * activePage = route.getActive(); * // instance of Page widget * * @method getRoute * @param {string} type Type of route * @return {?ns.router.route.interface} * @member ns.router.Router */ Router.prototype.getRoute = function (type) { return route[type]; }; /** * Returns instance of loader widget. * * If loader not exist then is created on first element matched to selector * or is created new element. * * @example * var loader = router.getLoader(); * // get or create loader * loader.show(); * // show loader * * @return {?ns.widget.mobile.Loader} * @member ns.router.Page * @method getLoader */ Router.prototype.getLoader = function () { var loaderDefinition = engine.getWidgetDefinition("Loader"), loaderSelector = loaderDefinition.selector, loaderElement; if (loaderDefinition) { loaderElement = document.querySelector(loaderSelector); return engine.instanceWidget(loaderElement, "Loader"); } return null; }; /** * Creates a new instance of the router and returns it * * @example * var router = Router.newInstance(); * * @method newInstance * @member ns.router.Router * @static * @return {ns.router.Router} * @since 2.4 */ Router.newInstance = function () { return (routerInstance = new Router()); }; /** * Returns a instance of the router, creates a new if does not exist * * @example * var router = tau.router.Router.getInstance(), * // if router not exists create new instance and return * router2 = tau.router.Router.getInstance(); * // only return router from first step * // router === router2 * * @method getInstance * @member ns.router.Router * @return {ns.router.Router} * @since 2.4 * @static */ Router.getInstance = function () { if (!routerInstance) { return this.newInstance(); } return routerInstance; }; router.Router = Router; Router.eventType = eventType; /** * Returns router instance * @deprecated 2,4 * @return {ns.router.Router} */ engine.getRouter = function () { //@TODO FIX HACK old API //@TODO this is suppressed since the tests are unreadable // tests need fixes ns.warn("getRouter() method is deprecated! Use tau.router.Router.getInstance() instead"); return Router.getInstance(); }; if (!ns.getConfig("disableRouter", false)) { document.addEventListener(engine.eventType.READY, function () { Router.getInstance().init(); }, false); document.addEventListener(engine.eventType.DESTROY, function () { Router.getInstance().destroy(); }, false); } //engine.initRouter(Router); }(window, window.document)); /*global window, ns, define, ns */ /*jslint nomen: true */ /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * #Route Page * Support class for router to control changing pages. * @class ns.router.route.page * @author Maciej Urbanski */ (function (document) { "use strict"; var util = ns.util, path = util.path, DOM = util.DOM, object = util.object, utilSelector = util.selectors, history = ns.history, engine = ns.engine, baseElement, routePage = {}, head; /** * Tries to find a page element matching id and filter (selector). * Adds data url attribute to found page, sets page = null when nothing found * @method findPageAndSetDataUrl * @param {string} dataUrl DataUrl of searching element * @param {string} filter Query selector for searching page * @return {?HTMLElement} * @private * @static * @member ns.router.route.page */ function findPageAndSetDataUrl(dataUrl, filter) { var id = path.stripQueryParams(dataUrl).replace("#", ""), page = document.getElementById(id); if (page && utilSelector.matchesSelector(page, filter)) { if (dataUrl === id) { DOM.setNSData(page, "url", "#" + id); } else { DOM.setNSData(page, "url", dataUrl); } } else { // if we matched any element, but it doesn't match our filter // reset page to null page = null; } return page; } routePage.orderNumber = 1; /** * Property containing default properties * @property {Object} defaults * @property {string} defaults.transition="none" * @static * @member ns.router.route.page */ routePage.defaults = { transition: "none" }; /** * Property defining selector without spaces for filtering only page elements. * @property {string} filter * @member ns.router.route.page * @static */ routePage.filter = engine.getWidgetDefinition("Page").selector.replace(/(\s*)/g, ""); /** * Property contains first page element * @property {?HTMLElement} firstPage * @member ns.router.route.page * @static */ routePage.firstPage = null; /** * Returns default route options used inside Router. * @method option * @static * @member ns.router.route.page * @return {Object} default route options */ routePage.option = function () { var defaults = object.merge({}, routePage.defaults); defaults.transition = ns.getConfig("pageTransition", defaults.transition); return defaults; }; routePage.init = function () { var pages = [].slice.call(document.querySelectorAll(this.filter)); pages.forEach(function (page) { if (!DOM.getNSData(page, "url")) { DOM.setNSData(page, "url", (page.id && "#" + page.id) || location.pathname + location.search); } }); }; /** * This method changes page. It sets history and opens page passed as a parameter. * @method open * @param {HTMLElement|string} toPage The page which will be opened. * @param {Object} [options] * @param {boolean} [options.fromHashChange] Sets if call was made on hash change. * @param {string} [options.dataUrl] Sets if page has url attribute. * @member ns.router.route.page */ routePage.open = function (toPage, options) { var pageTitle = document.title, url, state; if (toPage === this.getFirstElement() && !options.dataUrl) { url = path.documentUrl.hrefNoHash; } else { url = DOM.getNSData(toPage, "url"); } // if no url is set, apply the address of chosen page to data-url attribute // and use it as url, as this is needed for history state if (!url && options.href) { url = options.href; DOM.setNSData(toPage, "url", url); } pageTitle = DOM.getNSData(toPage, "title") || utilSelector.getChildrenBySelector(toPage, ".ui-header > .ui-title").textContent || pageTitle; if (!DOM.getNSData(toPage, "title")) { DOM.setNSData(toPage, "title", pageTitle); } if (url && !options.fromHashChange) { if (!path.isPath(url) && url.indexOf("#") < 0) { url = path.makeUrlAbsolute("#" + url, path.documentUrl.hrefNoHash); } state = object.merge( {}, options, { url: url } ); history.replace(state, pageTitle, url); } // write base element this._setBase(url); //set page title document.title = pageTitle; this.active = true; this.getContainer().change(toPage, options); }; /** * This method determines target page to open. * @method find * @param {string} absUrl Absolute path to opened page * @member ns.router.route.page * @return {?HTMLElement} Element of page to open. */ routePage.find = function (absUrl) { var self = this, router = ns.router.Router.getInstance(), dataUrl = self._createDataUrl(absUrl), initialContent = self.getFirstElement(), pageContainer = router.getContainer(), page, selector = "[data-url='" + dataUrl + "']", filterRegexp = /,/gm; if (/#/.test(absUrl) && path.isPath(dataUrl)) { return null; } // Check to see if the page already exists in the DOM. // NOTE do _not_ use the :jqmData pseudo selector because parenthesis // are a valid url char and it breaks on the first occurrence // prepare selector for new page selector += self.filter.replace(filterRegexp, ",[data-url='" + dataUrl + "']"); page = pageContainer.element.querySelector(selector); // If we failed to find the page, check to see if the url is a // reference to an embedded page. If so, it may have been dynamically // injected by a developer, in which case it would be lacking a // data-url attribute and in need of enhancement. if (!page && dataUrl && !path.isPath(dataUrl)) { //Remove search data page = findPageAndSetDataUrl(dataUrl, self.filter); } // If we failed to find a page in the DOM, check the URL to see if it // refers to the first page in the application. Also check to make sure // our cached-first-page is actually in the DOM. Some user deployed // apps are pruning the first page from the DOM for various reasons. // We check for this case here because we don't want a first-page with // an id falling through to the non-existent embedded page error case. if (!page && path.isFirstPageUrl(dataUrl, self.getFirstElement()) && initialContent) { page = initialContent; } return page; }; /** * This method parses HTML and runs scripts from parsed code. * Fetched external scripts if required. * Sets document base to parsed document absolute path. * @method parse * @param {string} html HTML code to parse * @param {string} absUrl Absolute url for parsed page * @member ns.router.route.page * @return {?HTMLElement} Element of page in parsed document. */ routePage.parse = function (html, absUrl) { var self = this, page, dataUrl = self._createDataUrl(absUrl); // write base element self._setBase(absUrl); // Finding matching page inside created element page = html.querySelector(self.filter); // If a page exists... if (page) { DOM.setNSData(page, "url", dataUrl); DOM.setNSData(page, "external", true); } return page; }; /** * This method handles hash change, **currently does nothing**. * @method onHashChange * @static * @member ns.router.route.page * @return {null} */ routePage.onHashChange = function (/* url, options */) { return null; }; /** * This method creates data url from absolute url given as argument. * @method _createDataUrl * @param {string} absoluteUrl * @protected * @static * @member ns.router.route.page * @return {string} */ routePage._createDataUrl = function (absoluteUrl) { return path.convertUrlToDataUrl(absoluteUrl, true); }; /** * On open fail, currently never used * @method onOpenFailed * @member ns.router.route.page */ routePage.onOpenFailed = function (/* options */) { this._setBase(path.parseLocation().hrefNoSearch); }; /** * This method returns base element from document head. * If no base element is found, one is created based on current location. * @method _getBaseElement * @protected * @static * @member ns.router.route.page * @return {HTMLElement} */ routePage._getBaseElement = function () { // Fetch document head if never cached before if (!head) { head = document.querySelector("head"); } // Find base element if (!baseElement) { baseElement = document.querySelector("base"); if (!baseElement) { baseElement = document.createElement("base"); baseElement.href = path.documentBase.hrefNoHash; head.appendChild(baseElement); } } return baseElement; }; /** * Sets document base to url given as argument * @method _setBase * @param {string} url * @protected * @member ns.router.route.page */ routePage._setBase = function (url) { var base = this._getBaseElement(), baseHref = base.href; if (path.isPath(url)) { url = path.makeUrlAbsolute(url, path.documentBase); if (path.parseUrl(baseHref).hrefNoSearch !== path.parseUrl(url).hrefNoSearch) { base.href = url; path.documentBase = path.parseUrl(path.makeUrlAbsolute(url, path.documentUrl.href)); } } }; /** * Returns container of pages * @method getContainer * @return {?ns.widget.core.Page} * @member ns.router.route.page * @static */ routePage.getContainer = function () { return ns.router.Router.getInstance().getContainer(); }; /** * Returns active page. * @method getActive * @return {?ns.widget.core.Page} * @member ns.router.route.page * @static */ routePage.getActive = function () { return this.getContainer().getActivePage(); }; /** * Returns element of active page. * @method getActiveElement * @return {HTMLElement} * @member ns.router.route.page * @static */ routePage.getActiveElement = function () { return this.getActive().element; }; /** * Method returns ths first page. * @method getFirstElement * @return {HTMLElement} the first page * @member ns.router.route.page */ routePage.getFirstElement = function () { return this.firstPage; }; /** * Method sets ths first page. * @method setFirstElement * @param {HTMLElement} firstPage the first page * @member ns.router.route.page */ routePage.setFirstElement = function (firstPage) { this.firstPage = firstPage; }; ns.router.route.page = routePage; }(window.document)); /*global window, ns, define, ns */ /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * #Callback Utility * Class creates a callback list * * Create a callback list using the following parameters: * options: an optional list of space-separated options that will change how * the callback list behaves or a more traditional option object * * By default a callback list will act like an event callback list and can be * "fired" multiple times. * * Possible options: * * once: will ensure the callback list can only be fired once (like a Deferred) * * memory: will keep track of previous values and will call any callback added * after the list has been fired right away with the latest "memorized" * values (like a Deferred) * * unique: will ensure a callback can only be added once (no duplicate in the list) * * stopOnFalse: interrupt callings when a callback returns false * @class ns.util.callbacks */ (function (window, document, ns) { "use strict"; ns.util.callbacks = function (orgOptions) { var object = ns.util.object, options = object.copy(orgOptions), /** * Alias to Array.slice function * @method slice * @member ns.util.callbacks * @private */ slice = [].slice, /** * Last fire value (for non-forgettable lists) * @property {Object} memory * @member ns.util.callbacks * @private */ memory, /** * Flag to know if list was already fired * @property {boolean} fired * @member ns.util.callbacks * @private */ fired, /** * Flag to know if list is currently firing * @property {boolean} firing * @member ns.util.callbacks * @private */ firing, /** * First callback to fire (used internally by add and fireWith) * @property {number} [firingStart=0] * @member ns.util.callbacks * @private */ firingStart, /** * End of the loop when firing * @property {number} firingLength * @member ns.util.callbacks * @private */ firingLength, /** * Index of currently firing callback (modified by remove if needed) * @property {number} firingIndex * @member ns.util.callbacks * @private */ firingIndex, /** * Actual callback list * @property {Array} list * @member ns.util.callbacks * @private */ list = [], /** * Stack of fire calls for repeatable lists * @property {Array} stack * @member ns.util.callbacks * @private */ stack = !options.once && [], fire, add, self = { /** * Add a callback or a collection of callbacks to the list * @method add * @return {ns.util.callbacks} self * @member ns.util.callbacks */ add: function () { var start; if (list) { // First, we save the current length start = list.length; add(arguments); // Do we need to add the callbacks to the // current firing batch? if (firing) { firingLength = list.length; // With memory, if we're not firing then // we should call right away } else if (memory) { firingStart = start; fire(memory); } } return this; }, /** * Remove a callback from the list * @method remove * @return {ns.util.callbacks} self * @member ns.util.callbacks */ remove: function () { if (list) { slice.call(arguments).forEach(function (arg) { var index = list.indexOf(arg); while (index > -1) { list.splice(index, 1); // Handle firing indexes if (firing) { if (index <= firingLength) { firingLength--; } if (index <= firingIndex) { firingIndex--; } } index = list.indexOf(arg, index); } }); } return this; }, /** * Check if a given callback is in the list. * If no argument is given, * return whether or not list has callbacks attached. * @method has * @param {Function} fn * @return {boolean} * @member ns.util.callbacks */ has: function (fn) { return fn ? !!list && list.indexOf(fn) > -1 : !!(list && list.length); }, /** * Remove all callbacks from the list * @method empty * @return {ns.util.callbacks} self * @member ns.util.callbacks */ empty: function () { list = []; firingLength = 0; return this; }, /** * Have the list do nothing anymore * @method disable * @return {ns.util.callbacks} self * @member ns.util.callbacks */ disable: function () { list = stack = memory = undefined; return this; }, /** * Is it disabled? * @method disabled * @return {boolean} * @member ns.util.callbacks */ disabled: function () { return !list; }, /** * Lock the list in its current state * @method lock * @return {ns.util.callbacks} self * @member ns.util.callbacks */ lock: function () { stack = undefined; if (!memory) { self.disable(); } return this; }, /** * Is it locked? * @method locked * @return {boolean} stack * @member ns.util.callbacks */ locked: function () { return !stack; }, /** * Call all callbacks with the given context and * arguments * @method fireWith * @param {Object} context * @param {Array} args * @return {ns.util.callbacks} self * @member ns.util.callbacks */ fireWith: function (context, args) { if (list && (!fired || stack)) { args = args || []; args = [context, args.slice ? args.slice() : args]; if (firing) { stack.push(args); } else { fire(args); } } return this; }, /** * Call all the callbacks with the given arguments * @method fire * @return {ns.util.callbacks} self * @member ns.util.callbacks */ fire: function () { self.fireWith(this, arguments); return this; }, /** * To know if the callbacks have already been called at * least once * @method fired * @return {boolean} * @member ns.util.callbacks */ fired: function () { return !!fired; } }; /** * Adds functions to the callback list * @method add * @param {...*} argument * @member ns.util.bezierCurve * @private */ add = function (args) { slice.call(args).forEach(function (arg) { var type = typeof arg; if (type === "function") { if (!options.unique || !self.has(arg)) { list.push(arg); } } else if (arg && arg.length && type !== "string") { // Inspect recursively add(arg); } }); }; /** * Fire callbacks * @method fire * @param {Array} data * @member ns.util.bezierCurve * @private */ fire = function (data) { memory = options.memory && data; fired = true; firingIndex = firingStart || 0; firingStart = 0; firingLength = list.length; firing = true; while (list && firingIndex < firingLength) { if (list[firingIndex].apply(data[0], data[1]) === false && options.stopOnFalse) { memory = false; // To prevent further calls using add break; } firingIndex++; } firing = false; if (list) { if (stack) { if (stack.length) { fire(stack.shift()); } } else if (memory) { list = []; } else { self.disable(); } } }; return self; }; }(window, window.document, ns)); /*global window, ns, define, ns */ /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * #Deferred Utility * Class creates object which can call registered callback depend from * state of object.. * @class ns.util.deferred * @author Tomasz Lukawski * @author Maciej Urbanski * @author Piotr Karny */(function (window, document, ns) { "use strict"; var Deferred = function (callback) { var callbacks = ns.util.callbacks, object = ns.util.object, /** * Register additional action for deferred object * @property {Array} tuples * @member ns.util.deferred * @private */ tuples = [ // action, add listener, listener list, final state ["resolve", "done", callbacks({once: true, memory: true}), "resolved"], ["reject", "fail", callbacks({once: true, memory: true}), "rejected"], ["notify", "progress", callbacks({memory: true})] ], state = "pending", deferred = {}, promise = { /** * Determine the current state of a Deferred object. * @method state * @return {"pending" | "resolved" | "rejected"} representing the current state * @member ns.util.deferred */ state: function () { return state; }, /** * Add handlers to be called when the Deferred object * is either resolved or rejected. * @method always * @return {ns.util.deferred} self * @member ns.util.deferred */ always: function () { deferred.done(arguments).fail(arguments); return this; }, /** * Add handlers to be called when the Deferred object * is resolved, rejected, or still in progress. * @method then * @return {Object} returns a new promise * @member ns.util.deferred */ then: function () { /* fnDone, fnFail, fnProgress */ var functions = arguments; return new Deferred(function (newDefer) { tuples.forEach(function (tuple, i) { var fn = (typeof functions[i] === "function") && functions[i]; // deferred[ done | fail | progress ] for forwarding actions to newDefer deferred[tuple[1]](function () { var returned = fn && fn.apply(this, arguments); if (returned && (typeof returned.promise === "function")) { returned.promise() .done(newDefer.resolve) .fail(newDefer.reject) .progress(newDefer.notify); } else { newDefer[tuple[0] + "With"](this === promise ? newDefer.promise() : this, fn ? [returned] : arguments); } }); }); functions = null; }).promise(); }, /** * Get a promise for this deferred. If obj is provided, * the promise aspect is added to the object * @method promise * @param {Object} obj * @return {Object} return a Promise object * @member ns.util.deferred */ promise: function (obj) { if (obj) { return object.merge(obj, promise); } return promise; } }; /** * alias for promise.then, Keep pipe for back-compat * @method pipe * @member ns.util.deferred */ promise.pipe = promise.then; // Add list-specific methods tuples.forEach(function (tuple, i) { var list = tuple[2], stateString = tuple[3]; // promise[ done | fail | progress ] = list.add promise[tuple[1]] = list.add; // Handle state if (stateString) { list.add(function () { // state = [ resolved | rejected ] state = stateString; // mapping of values [ reject_list | resolve_list ].disable; progress_list.lock }, tuples[i ^ 1][2].disable, tuples[2][2].lock); } // deferred[ resolve | reject | notify ] deferred[tuple[0]] = function () { deferred[tuple[0] + "With"](this === deferred ? promise : this, arguments); return this; }; deferred[tuple[0] + "With"] = list.fireWith; }); // Make the deferred a promise promise.promise(deferred); // Call given func if any if (callback) { callback.call(deferred, deferred); } // All done! return deferred; }; ns.util.deferred = Deferred; }(window, window.document, ns)); /*global window, define, ns */ /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /*jslint nomen: true, plusplus: true */ /** * #Popup * Popup component supports 2 pop-ups: the position-to-window pop-up (like a system pop-up), and the context pop-up. * * @since 2.0 * @author Hyunkook Cho * @class ns.widget.mobile.Popup * @extends ns.widget.core.BaseWidget */ (function () { "use strict"; /** * Alias for {@link ns.widget.BaseWidget} * @property {Function} BaseWidget * @member ns.widget.core.Popup * @private */ var BaseWidget = ns.widget.BaseWidget, /** * Alias for class ns.engine * @property {ns.engine} engine * @member ns.widget.core.Popup * @private */ engine = ns.engine, /** * Alias for class ns.util.object * @property {Object} objectUtils * @member ns.widget.core.Popup * @private */ objectUtils = ns.util.object, /** * Alias for class ns.util.deferred * @property {Object} UtilDeferred * @member ns.widget.core.Popup * @private */ UtilDeferred = ns.util.deferred, /** * Alias for class ns.util.selectors * @property {Object} utilSelector * @member ns.widget.core.Popup * @private */ utilSelector = ns.util.selectors, /** * Alias for class ns.event * @property {Object} eventUtils * @member ns.widget.core.Popup * @private */ eventUtils = ns.event, /** * Alias for Router, loose requirement * @property {ns.router.Router} Router * @member ns.widget.core.Popup * @private */ Router = ns.router && ns.router.Router, POPUP_SELECTOR = "[data-role='popup'], .ui-popup", Popup = function () { var self = this, ui = {}; self.selectors = selectors; self.options = objectUtils.merge({}, Popup.defaults); self.storedOptions = null; /** * Popup state flag * @property {0|1|2|3} [state=null] * @member ns.widget.core.Popup * @private */ self.state = states.CLOSED; ui.overlay = null; ui.header = null; ui.footer = null; ui.content = null; ui.container = null; ui.wrapper = null; self._ui = ui; // event callbacks self._callbacks = {}; }, /** * Object with default options * @property {Object} defaults * @property {string} [options.transition="none"] Sets the default transition for the popup. * @property {string} [options.positionTo="window"] Sets the element relative to which the popup will be centered. * @property {boolean} [options.dismissible=true] Sets whether to close popup when a popup is open to support the back button. * @property {boolean} [options.overlay=true] Sets whether to show overlay when a popup is open. * @property {boolean|string} [options.header=false] Sets content of header. * @property {boolean|string} [options.footer=false] Sets content of footer. * @property {string} [options.content=null] Sets content of popup. * @property {string} [options.overlayClass=""] Sets the custom class for the popup background, which covers the entire window. * @property {string} [options.closeLinkSelector="a[data-rel='back']"] Sets selector for close buttons in popup. * @property {boolean} [options.history=true] Sets whether to alter the url when a popup is open to support the back button. * @member ns.widget.core.Popup * @static */ defaults = { transition: "none", dismissible: true, overlay: true, header: false, footer: false, content: null, overlayClass: "", closeLinkSelector: "[data-rel='back']", history: null, closeAfter: null }, states = { DURING_OPENING: 0, OPENED: 1, DURING_CLOSING: 2, CLOSED: 3 }, CLASSES_PREFIX = "ui-popup", /** * Dictionary for popup related css class names * @property {Object} classes * @member ns.widget.core.Popup * @static */ /** * Toast style of popup * @style ui-popup-toast * @member ns.widget.core.Popup * @wearable */ /** * Toast style of popup with graphic * @style ui-popup-toast-graphic * @member ns.widget.core.Popup * @wearable */ classes = { popup: CLASSES_PREFIX, active: CLASSES_PREFIX + "-active", overlay: CLASSES_PREFIX + "-overlay", header: CLASSES_PREFIX + "-header", footer: CLASSES_PREFIX + "-footer", content: CLASSES_PREFIX + "-content", wrapper: CLASSES_PREFIX + "-wrapper", toast: CLASSES_PREFIX + "-toast", toastSmall: CLASSES_PREFIX + "-toast-small", build: "ui-build" }, /** * Dictionary for popup related selectors * @property {Object} selectors * @member ns.widget.core.Popup * @static */ selectors = { header: "." + classes.header, content: "." + classes.content, footer: "." + classes.footer }, EVENTS_PREFIX = "popup", /** * Dictionary for popup related events * @property {Object} events * @member ns.widget.core.Popup * @static */ events = { /** * Triggered when the popup has been created in the DOM (via ajax or other) but before all widgets have had an opportunity to enhance the contained markup. * @event popupshow * @member ns.widget.core.Popup */ show: EVENTS_PREFIX + "show", /** * Triggered on the popup after the transition animation has completed. * @event popuphide * @member ns.widget.core.Popup */ hide: EVENTS_PREFIX + "hide", /** * Triggered on the popup we are transitioning to, before the actual transition animation is kicked off. * @event popupbeforeshow * @member ns.widget.core.Popup */ /* eslint-disable camelcase */ // we can't change this in this moment because this is part of API before_show: EVENTS_PREFIX + "beforeshow", /** * Triggered on the popup we are transitioning to, before the actual transition animation is kicked off, animation has started. * @event popuptransitionstart * @member ns.widget.core.Popup */ transition_start: EVENTS_PREFIX + "transitionstart", /** * Triggered on the popup we are transitioning away from, before the actual transition animation is kicked off. * @event popupbeforehide * @member ns.widget.core.Popup */ before_hide: EVENTS_PREFIX + "beforehide" /* eslint-enable camelcase */ }, prototype = new BaseWidget(); Popup.classes = classes; Popup.events = events; Popup.defaults = defaults; Popup.selector = POPUP_SELECTOR; /** * Build the content of popup * @method _buildContent * @param {HTMLElement} element * @protected * @member ns.widget.core.Popup */ prototype._buildContent = function (element) { var self = this, ui = self._ui, selectors = self.selectors, options = self.options, content = ui.content || element.querySelector(selectors.content), footer = ui.footer || element.querySelector(selectors.footer), elementChildren = [].slice.call(element.childNodes), elementChildrenLength = elementChildren.length, i, node; if (!content) { content = document.createElement("div"); content.className = classes.content; for (i = 0; i < elementChildrenLength; ++i) { node = elementChildren[i]; if (node !== ui.footer && node !== ui.header) { content.appendChild(node); } } if (typeof options.content === "string") { content.innerHTML = options.content; } element.insertBefore(content, footer); } content.classList.add(classes.content); ui.content = content; }; /** * Build the header of popup * @method _buildHeader * @param {HTMLElement} element * @protected * @member ns.widget.core.Popup */ prototype._buildHeader = function (element) { var self = this, ui = self._ui, options = self.options, selectors = self.selectors, content = ui.content || element.querySelector(selectors.content), header = ui.header || element.querySelector(selectors.header); if (!header && options.header !== false) { header = document.createElement("div"); header.className = classes.header; if (typeof options.header !== "boolean") { header.innerHTML = options.header; } element.insertBefore(header, content); } if (header) { header.classList.add(classes.header); } ui.header = header; }; /** * Set the header of popup. * This function is called by function "option" when the option "header" is set. * @method _setHeader * @param {HTMLElement} element * @param {boolean|string} value * @protected * @member ns.widget.core.Popup */ prototype._setHeader = function (element, value) { var self = this, ui = self._ui, header = ui.header; if (header) { header.parentNode.removeChild(header); ui.header = null; } self.options.header = value; self._buildHeader(ui.container); }; /** * Build the footer of popup * @method _buildFooter * @param {HTMLElement} element * @protected * @member ns.widget.core.Popup */ prototype._buildFooter = function (element) { var self = this, ui = self._ui, options = self.options, footer = ui.footer || element.querySelector(self.selectors.footer); if (!footer && options.footer !== false) { footer = document.createElement("div"); footer.className = classes.footer; if (typeof options.footer !== "boolean") { footer.innerHTML = options.footer; } element.appendChild(footer); } if (footer) { footer.classList.add(classes.footer); } ui.footer = footer; }; /** * Set the footer of popup. * This function is called by function "option" when the option "footer" is set. * @method _setFooter * @param {HTMLElement} element * @param {boolean|string} value * @protected * @member ns.widget.core.Popup */ prototype._setFooter = function (element, value) { var self = this, ui = self._ui, footer = ui.footer; if (footer) { footer.parentNode.removeChild(footer); ui.footer = null; } self.options.footer = value; self._buildFooter(ui.container); }; /** * Build structure of Popup widget * @method _build * @param {HTMLElement} element of popup * @return {HTMLElement} * @protected * @member ns.widget.Popup */ prototype._build = function (element) { var self = this, ui = self._ui, wrapper, child = element.firstChild, elementClassList = element.classList; // set class for element elementClassList.add(classes.popup); if (elementClassList.contains(classes.toastSmall)) { elementClassList.add(classes.toast); } // create wrapper wrapper = document.createElement("div"); wrapper.classList.add(classes.wrapper); ui.wrapper = wrapper; ui.container = wrapper; // move all children to wrapper while (child) { wrapper.appendChild(child); child = element.firstChild; } // add wrapper and arrow to popup element element.appendChild(wrapper); // build header, footer and content self._buildHeader(ui.container); self._buildFooter(ui.container); self._buildContent(ui.container); // set overlay self._setOverlay(element, self.options.overlay); return element; }; /** * Set overlay * @method _setOverlay * @param {HTMLElement} element * @param {boolean} enable * @protected * @member ns.widget.Popup */ prototype._setOverlay = function (element, enable) { var self = this, overlayClass = self.options.overlayClass, ui = self._ui, overlay = ui.overlay; // if this popup is not connected with slider, // we create overlay, which is invisible when // the value of option overlay is false /// @TODO: get class from widget if (!element.classList.contains("ui-slider-popup") && !element.classList.contains(classes.toast)) { // create overlay if (!overlay) { overlay = document.createElement("div"); if (element.parentNode) { element.parentNode.insertBefore(overlay, element); } else { ns.warn("Popup is creating on element outside DOM"); } ui.overlay = overlay; } overlay.className = classes.overlay + (overlayClass ? " " + overlayClass : ""); if (enable) { overlay.style.opacity = ""; } else { // if option is set on "false", the overlay is not visible overlay.style.opacity = 0; } } }; /** * Returns the state of the popup * @method _isActive * @protected * @member ns.widget.core.Popup */ prototype._isActive = function () { var state = this.state; return state === states.DURING_OPENING || state === states.OPENED; }; /** * Returns true if popup is already opened and visible * @method _isActive * @protected * @member ns.widget.core.Popup */ prototype._isOpened = function () { return this.state === states.OPENED; }; /** * Init widget * @method _init * @param {HTMLElement} element * @protected * @member ns.widget.core.Popup */ prototype._init = function (element) { var self = this, selectors = self.selectors, ui = self._ui, options = self.options, elementClassList = self.element.classList; ui.header = ui.header || element.querySelector(selectors.header); ui.footer = ui.footer || element.querySelector(selectors.footer); ui.content = ui.content || element.querySelector(selectors.content); ui.wrapper = ui.wrapper || element.querySelector("." + classes.wrapper); ui.container = ui.wrapper || element; // @todo - use selector from page's definition in engine ui.page = utilSelector.getClosestByClass(element, "ui-page") || window; if (elementClassList.contains(classes.toast)) { options.closeAfter = options.closeAfter || 2000; } // if option history is not set in constructor or in HTML if (options.history === null) { // for toast we set false for other true options.history = !elementClassList.contains(classes.toast); } }; /** * Set the state of the popup * @method _setActive * @param {boolean} active * @protected * @member ns.widget.core.Popup */ prototype._setActive = function (active) { var self = this, activeClass = classes.active, elementClassList = self.element.classList, route = Router && Router.getInstance().getRoute("popup"), options; // NOTE: popup's options object is stored in window.history at the router module, // and this window.history can't store DOM element object. options = objectUtils.merge({}, self.options, {positionTo: null, link: null}); // set state of popup and add proper class if (active) { // set global variable if (route) { route.setActive(self, options); } // add proper class elementClassList.add(activeClass); // set state of popup 358 self.state = states.OPENED; } else { // no popup is opened, so set global variable on "null" if (route) { route.setActive(null, options); } // remove proper class elementClassList.remove(activeClass); // set state of popup self.state = states.CLOSED; } }; /** * Bind events * @method _bindEvents * @protected * @member ns.widget.core.Popup */ prototype._bindEvents = function () { var self = this; eventUtils.on(self._ui.page, "pagebeforehide", self, false); eventUtils.on(window, "resize", self, false); eventUtils.on(document, "click touchstart", self, false); }; /** * Unbind events * @method _bindEvents * @protected * @member ns.widget.core.Popup */ prototype._unbindEvents = function () { var self = this; eventUtils.off(self._ui.page, "pagebeforehide", self, false); eventUtils.off(window, "resize", self, false); eventUtils.off(document, "click touchstart", self, false); }; /** * Layouting popup structure * @method layout * @member ns.widget.core.Popup */ prototype._layout = function () { }; /** * Open the popup * @method open * @param {Object=} [options] * @param {string=} [options.transition] options.transition * @member ns.widget.core.Popup */ prototype.open = function (options) { var self = this, newOptions, onClose = self.close.bind(self); if (!self._isActive()) { /* * Some passed options on open need to be kept until popup closing. * For example, transition parameter should be kept for closing animation. * On the other hand, fromHashChange or x, y parameter should be removed. * We store options and restore them on popup closing. */ self._storeOpenOptions(options); newOptions = objectUtils.merge(self.options, options); if (!newOptions.dismissible) { ns.router.Router.getInstance().lock(); } if (newOptions.closeAfter > 0) { if (self.element.classList.contains(classes.toast)) { newOptions.transition = "fade"; } self._show(newOptions); self._closeTimeout = window.setTimeout(onClose, newOptions.closeAfter); } else { self._show(newOptions); } } }; /** * Close the popup * @method close * @param {Object=} [options] * @param {string=} [options.transition] * @member ns.widget.core.Popup */ prototype.close = function (options) { var self = this, newOptions = objectUtils.merge(self.options, options); if (self._isActive()) { clearTimeout(self._closeTimeout); if (!newOptions.dismissible) { ns.router.Router.getInstance().unlock(); } self._hide(newOptions); } }; /** * Store Open options. * @method _storeOpenOptions * @param {Object} options * @protected * @member ns.widget.core.Popup */ prototype._storeOpenOptions = function (options) { var self = this, oldOptions = self.options, storedOptions = {}, key; for (key in options) { if (options.hasOwnProperty(key)) { storedOptions[key] = oldOptions[key]; } } self.storedOptions = storedOptions; }; /** * Restore Open options and remove some unnecessary ones. * @method _storeOpenOptions * @protected * @member ns.widget.core.Popup */ prototype._restoreOpenOptions = function () { var self = this, options = self.options, propertiesToRemove = ["x", "y", "fromHashChange"]; // we restore opening values of all options options = objectUtils.merge(options, self.storedOptions); // and remove all values which should not be stored objectUtils.removeProperties(options, propertiesToRemove); }; /** * Show popup. * @method _show * @param {Object} options * @protected * @member ns.widget.core.Popup */ prototype._show = function (options) { var self = this, transitionOptions = objectUtils.merge({}, options), overlay = self._ui.overlay; // set layout self._layout(self.element); // change state of popup self.state = states.DURING_OPENING; // set transition transitionOptions.ext = " in "; self.trigger(events.before_show); // show overlay if (overlay) { overlay.style.display = "block"; } // start opening animation self._transition(transitionOptions, self._onShow.bind(self)); // animation has started self.trigger(events.transition_start); }; /** * Show popup * @method _onShow * @protected * @member ns.widget.core.Popup */ prototype._onShow = function () { var self = this; self._setActive(true); self.trigger(events.show); }; /** * Hide popup * @method _hide * @param {Object} options * @protected * @member ns.widget.core.Popup */ prototype._hide = function (options) { var self = this, isOpened = self._isOpened(), callbacks = self._callbacks; // change state of popup self.state = states.DURING_CLOSING; self.trigger(events.before_hide); if (isOpened) { // popup is opened, so we start closing animation options.ext = " out "; self._transition(options, self._onHide.bind(self)); } else { // popup is active, but not opened yet (DURING_OPENING), so // we stop opening animation if (callbacks.transitionDeferred) { callbacks.transitionDeferred.reject(); } if (callbacks.animationEnd) { callbacks.animationEnd(); } // and set popup as inactive self._onHide(); } }; /** * Hide popup * @method _onHide * @protected * @member ns.widget.core.Popup */ prototype._onHide = function () { var self = this, overlay = self._ui.overlay; self._setActive(false); if (overlay) { overlay.style.display = ""; } self._restoreOpenOptions(); self.trigger(events.hide); }; /** * Handle events * @method handleEvent * @param {Event} event * @member ns.widget.core.Popup */ prototype.handleEvent = function (event) { var self = this, router = ns.router.Router.getInstance(); switch (event.type) { case "pagebeforehide": // we need close active popup if exists router.close(null, {transition: "none", rel: "popup"}); break; case "resize": self._onResize(event); break; case "click": if (event.target === self._ui.overlay) { self._onClickOverlay(event); } break; case "touchstart": if (self.element.classList.contains(classes.toast) && self._isActive()) { router.close(null, {rel: "popup"}); } break; } }; /** * Refresh structure * @method _refresh * @protected * @member ns.widget.core.Popup */ prototype._refresh = function () { var self = this; self._setOverlay(self.element, self.options.overlay); }; /** * Callback function fires after clicking on overlay. * @method _onClickOverlay * @param {Event} event * @protected * @member ns.widget.core.Popup */ prototype._onClickOverlay = function (event) { var options = this.options; event.preventDefault(); event.stopPropagation(); if (options.dismissible) { ns.router.Router.getInstance().close(null, {rel: "popup"}); } }; /** * Callback function fires on resizing * @method _onResize * @protected * @member ns.widget.core.Popup */ prototype._onResize = function () { if (this._isOpened()) { this._refresh(); } }; function clearAnimation(self, transitionClass, deferred) { var element = self.element, elementClassList = element.classList, overlay = self._ui.overlay, animationEndCallback = self._callbacks.animationEnd; // remove callbacks on animation events element.removeEventListener("animationend", animationEndCallback, false); element.removeEventListener("webkitAnimationEnd", animationEndCallback, false); element.removeEventListener("mozAnimationEnd", animationEndCallback, false); element.removeEventListener("oAnimationEnd", animationEndCallback, false); element.removeEventListener("msAnimationEnd", animationEndCallback, false); // clear classes transitionClass.split(" ").forEach(function (currentClass) { currentClass = currentClass.trim(); if (currentClass.length > 0) { elementClassList.remove(currentClass); if (overlay) { overlay.classList.remove(currentClass); } } }); if (deferred.state() === "pending") { // we resolve only pending (not rejected) deferred deferred.resolve(); } } function setTransitionDeferred(self, resolve) { var deferred = new UtilDeferred(); deferred.then(function () { if (deferred === self._callbacks.transitionDeferred) { resolve(); } }); self._callbacks.transitionDeferred = deferred; return deferred; } /** * Animate popup opening/closing * @method _transition * @protected * @param {Object} [options] * @param {string=} [options.transition] * @param {string=} [options.ext] * @param {?Function} [resolve] * @member ns.widget.core.Popup */ prototype._transition = function (options, resolve) { var self = this, transition = options.transition || self.options.transition || "none", transitionClass = transition + options.ext, element = self.element, elementClassList = element.classList, overlayClassList, deferred, animationEndCallback; if (self._ui.overlay) { overlayClassList = self._ui.overlay.classList; } deferred = setTransitionDeferred(self, resolve); if (transition !== "none") { // set animationEnd callback animationEndCallback = clearAnimation.bind(null, self, transitionClass, deferred); self._callbacks.animationEnd = animationEndCallback; // add animation callbacks element.addEventListener("animationend", animationEndCallback, false); element.addEventListener("webkitAnimationEnd", animationEndCallback, false); element.addEventListener("mozAnimationEnd", animationEndCallback, false); element.addEventListener("oAnimationEnd", animationEndCallback, false); element.addEventListener("msAnimationEnd", animationEndCallback, false); // add transition classes transitionClass.split(" ").forEach(function (currentClass) { currentClass = currentClass.trim(); if (currentClass.length > 0) { elementClassList.add(currentClass); if (overlayClassList) { overlayClassList.add(currentClass); } } }); } else { if (!ns.getConfig("noAsync", false)) { window.setTimeout(deferred.resolve, 0); } else { deferred.resolve(); } } return deferred; }; /** * Destroy popup * @method _destroy * @protected * @member ns.widget.core.Popup */ prototype._destroy = function () { var self = this, element = self.element, ui = self._ui, wrapper = ui.wrapper, child; if (wrapper) { // restore all children from wrapper child = wrapper.firstChild; while (child) { element.appendChild(child); child = wrapper.firstChild; } if (wrapper.parentNode) { wrapper.parentNode.removeChild(wrapper); } } self._unbindEvents(element); self._setOverlay(element, false); ui.wrapper = null; }; Popup.prototype = prototype; ns.widget.core.Popup = Popup; engine.defineWidget( "Popup", POPUP_SELECTOR, [ "open", "close", "reposition" ], Popup, "core" ); }()); /*global window, define, ns */ /*jslint nomen: true */ /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * #Route Popup * Support class for router to control changing popups. * @class ns.router.route.popup * @author Maciej Urbanski * @author Damian Osipiuk */ (function (window, document, ns) { "use strict"; var /** * @property {Object} Popup Alias for {@link ns.widget.Popup} * @member ns.router.route.popup * @private * @static */ Popup = ns.widget.core.Popup, util = ns.util, routePopup = { /** * Object with default options * @property {Object} defaults * @property {string} [defaults.transition='none'] Sets the animation used during change * of popup. * @property {?HTMLElement} [defaults.container=null] Sets container of element. * @property {boolean} [defaults.volatileRecord=true] Sets if the current history entry * will be modified or a new one will be created. * @member ns.router.route.popup * @static */ defaults: { transition: "none", container: null, volatileRecord: true }, /** * Popup Element Selector * @property {string} filter * @member ns.router.route.popup * @static */ filter: "." + Popup.classes.popup, /** * Storage variable for active popup * @property {?HTMLElement} activePopup * @member ns.router.route.popup * @static */ activePopup: null, /** * Dictionary for popup related event types * @property {Object} events * @property {string} [events.POPUP_HIDE='popuphide'] * @member ns.router.route.popup * @static */ events: { POPUP_HIDE: "popuphide" }, /** * Alias for {@link ns.util.path} * @property {Object} path * @member ns.router.route.popup * @protected * @static */ _path: ns.util.path, /** * Alias for {@link ns.router.history} * @property {Object} history * @member ns.router.route.popup * @protected * @static */ _history: ns.history }, /** * Alias for {@link ns.engine} * @property {Object} engine * @member ns.router.route.popup * @private * @static */ engine = ns.engine, /** * Alias for {@link ns.util.selectors} * @property {Object} utilSelector * @member ns.router.route.popup * @private * @static */ utilSelector = ns.util.selectors, /** * Alias for {@link ns.util.DOM} * @property {Object} DOM * @member ns.router.route.popup * @private * @static */ DOM = ns.util.DOM, /** * Alias for Object utils * @method slice * @member ns.router.route.popup * @private * @static */ object = ns.util.object, /** * Popup's hash added to url * @property {string} popupHashKey * @member ns.router.route.popup * @private * @static */ popupHashKey = "popup=true", /** * Regexp for popup's hash * @property {RegExp} popupHashKeyReg * @member ns.router.route.popup * @private * @static */ popupHashKeyReg = /([&|\?]popup=true)/; /** * Tries to find a popup element matching id and filter (selector). * Adds data url attribute to found page, sets page = null when nothing found. * @method findPopupAndSetDataUrl * @param {string} id * @param {string} filter * @return {HTMLElement} * @member ns.router.route.popup * @private * @static */ function findPopupAndSetDataUrl(id, filter) { var popup, hashReg = /^#/; id = id.replace(hashReg, ""); popup = document.getElementById(id); if (popup && utilSelector.matchesSelector(popup, filter)) { DOM.setNSData(popup, "url", "#" + id); } else { // if we matched any element, but it doesn't match our filter // reset page to null popup = null; } // probably there is a need for running onHashChange while going back to a history entry // without state, eg. manually entered #fragment. This may not be a problem on target device return popup; } routePopup.orderNumber = 100; /** * This method returns default options for popup router. * @method option * @return {Object} * @member ns.router.route.popup * @static */ routePopup.option = function () { var defaults = object.merge({}, routePopup.defaults); defaults.transition = ns.getConfig("popupTransition", defaults.transition); return defaults; }; /** * This method sets active popup and manages history. * @method setActive * @param {?ns.widget.core.popup} activePopup * @param {Object} options * @member ns.router.route.popup * @static */ routePopup.setActive = function (activePopup, options) { var url, pathLocation = routePopup._path.getLocation(), documentUrl = pathLocation.replace(popupHashKeyReg, ""); this.activePopup = activePopup; if (activePopup) { // If popup is being opened, the new state is added to history. if (options && !options.fromHashChange && options.history) { url = routePopup._path.addHashSearchParams(documentUrl, popupHashKey); routePopup._history.replace(options, "", url); this.active = true; } } else if (pathLocation !== documentUrl) { // If popup is being closed, the history.back() is called // but only if url has special hash. // Url is changed after opening animation and in some cases, // the popup is closed before this animation and then the history.back // could cause undesirable change of page. this.active = false; routePopup._history.back(); } }; /** * This method opens popup if no other popup is opened. * It also changes history to show that popup is opened. * If there is already active popup, it will be closed. * @method open * @param {HTMLElement|string} toPopup * @param {Object} options * @param {"page"|"popup"|"external"} [options.rel = 'popup'] Represents kind of link as * 'page' or 'popup' or 'external' for linking to another domain. * @param {string} [options.transition = 'none'] Sets the animation used during change of * popup. * @param {boolean} [options.reverse = false] Sets the direction of change. * @param {boolean} [options.fromHashChange = false] Sets if will be changed after hashchange. * @param {boolean} [options.showLoadMsg = true] Sets if message will be shown during loading. * @param {number} [options.loadMsgDelay = 0] Sets delay time for the show message during * loading. * @param {boolean} [options.dataUrl] Sets if page has url attribute. * @param {string} [options.container = null] Selector for container. * @param {boolean} [options.volatileRecord=true] Sets if the current history entry will be * modified or a new one will be created. * @param {Event} event * @member ns.router.route.popup * @static */ routePopup.open = function (toPopup, options, event) { var self = this, popup, router = ns.router.Router.getInstance(), events = self.events, removePopup = function () { document.removeEventListener(events.POPUP_HIDE, removePopup, false); toPopup.parentNode.removeChild(toPopup); self.activePopup = null; }, openPopup = function () { var positionTo = options["position-to"], touch; // add such option only if it exists if (positionTo) { options.positionTo = positionTo; } if (event) { touch = event.touches ? event.touches[0] : event; options.x = touch.clientX; options.y = touch.clientY; } document.removeEventListener(events.POPUP_HIDE, openPopup, false); popup = engine.instanceWidget(toPopup, "Popup", options); popup.open(options); self.activePopup = popup; self.active = popup.options.history; }, activePage = router.container.getActivePage(), container; if (DOM.getNSData(toPopup, "external") === true) { container = options.container ? activePage.element.querySelector(options.container) : activePage.element; if (toPopup.parentNode !== container) { toPopup = util.importEvaluateAndAppendElement(toPopup, container); } document.addEventListener(routePopup.events.POPUP_HIDE, removePopup, false); } if (self.hasActive()) { document.addEventListener(events.POPUP_HIDE, openPopup, false); if (!self.close()) { openPopup(); } } else { openPopup(); } }; /** * This method closes active popup. * @method close * @param {ns.widget.core.Popup} [activePopup] * @param {Object} options * @param {string} [options.transition] * @param {string} [options.ext= in ui-pre-in] options.ext * @member ns.router.route.popup * @protected * @static */ routePopup.close = function (activePopup, options) { var popupOptions, pathLocation = routePopup._path.getLocation(), documentUrl = pathLocation.replace(popupHashKeyReg, ""); options = options || {}; if (activePopup && !(activePopup instanceof Popup)) { activePopup = engine.instanceWidget(activePopup, "Popup", options); } activePopup = activePopup || this.activePopup; // if popup is active if (activePopup) { popupOptions = activePopup.options; // we check if it changed the history if (popupOptions.history && pathLocation !== documentUrl) { // and then set new options for popup popupOptions.transition = options.transition || popupOptions.transition; popupOptions.ext = options.ext || popupOptions.ext; // unlock the router if it was locked if (!popupOptions.dismissible) { ns.router.Router.getInstance().unlock(); } // and call history.back() routePopup._history.back(); } else { // if popup did not change the history, we close it normally activePopup.close(options); } return true; } return false; }; /** * This method handles hash change. * It closes opened popup. * @method onHashChange * @param {string} url * @param {Object} options * @return {boolean} * @member ns.router.route.popup * @static */ routePopup.onHashChange = function (url, options) { var activePopup = this.activePopup; if (activePopup) { activePopup.close(options); // Default routing setting cause to rewrite further window history // even if popup has been closed // To prevent this onHashChange after closing popup we need to change // disable volatile mode to allow pushing new history elements if (this.active) { this.active = false; return true; } } return false; }; /** * On open fail, currently never used * @method onOpenFailed * @member ns.router.route.popup * @return {null} * @static */ routePopup.onOpenFailed = function (/* options */) { return null; }; /** * This method finds popup by data-url. * @method find * @param {string} absUrl Absolute path to opened popup * @return {HTMLElement} Element of popup * @member ns.router.route.popup */ routePopup.find = function (absUrl) { var self = this, dataUrl = self._createDataUrl(absUrl), activePage = ns.router.Router.getInstance().getContainer().getActivePage(), popup; popup = activePage.element.querySelector("[data-url='" + dataUrl + "']" + self.filter); if (!popup && dataUrl && !routePopup._path.isPath(dataUrl)) { popup = findPopupAndSetDataUrl(dataUrl, self.filter); } return popup; }; /** * This method parses HTML and runs scripts from parsed code. * Fetched external scripts if required. * @method parse * @param {string} html HTML code to parse * @param {string} absUrl Absolute url for parsed popup * @return {HTMLElement} * @member ns.router.route.popup */ routePopup.parse = function (html, absUrl) { var self = this, popup, dataUrl = self._createDataUrl(absUrl); popup = html.querySelector(self.filter); if (popup) { // TODO tagging a popup with external to make sure that embedded popups aren't // removed by the various popup handling code is bad. Having popup handling code // in many places is bad. Solutions post 1.0 DOM.setNSData(popup, "url", dataUrl); DOM.setNSData(popup, "external", true); } return popup; }; /** * Convert url to data-url * @method _createDataUrl * @param {string} absoluteUrl * @return {string} * @member ns.router.route.popup * @protected * @static */ routePopup._createDataUrl = function (absoluteUrl) { return routePopup._path.convertUrlToDataUrl(absoluteUrl); }; /** * Return true if active popup exists. * @method hasActive * @return {boolean} * @member ns.router.route.popup * @static */ routePopup.hasActive = function () { return this.active; }; /** * Returns active popup. * @method getActive * @return {?ns.widget.core.Popup} * @member ns.router.route.popup * @static */ routePopup.getActive = function () { return this.activePopup; }; /** * Returns element of active popup. * @method getActiveElement * @return {HTMLElement} * @member ns.router.route.popup * @static */ routePopup.getActiveElement = function () { var active = this.getActive(); return active && active.element; }; ns.router.route.popup = routePopup; }(window, window.document, ns)); /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* global window, define, ns, CustomEvent */ /* * @class ns.event.gesture */ (function (ns) { "use strict"; var event = ns.event, gesture = function (elem, options) { return new ns.event.gesture.Instance(elem, options); }; /** * Default values for Gesture feature * @property {Object} defaults * @property {boolean} [defaults.triggerEvent=false] * @property {number} [defaults.updateVelocityInterval=16] * Interval in which Gesture recalculates current velocity in ms * @property {number} [defaults.estimatedPointerTimeDifference=15] * pause time threshold.. tune the number to up if it is slow * @member ns.event.gesture * @static */ gesture.defaults = { triggerEvent: false, updateVelocityInterval: 16, estimatedPointerTimeDifference: 15 }; /** * Dictionary of orientation * @property {Object} Orientation * @property {1} Orientation.VERTICAL vertical orientation * @property {2} Orientation.HORIZONTAL horizontal orientation * @member ns.event.gesture * @static */ gesture.Orientation = { VERTICAL: "vertical", HORIZONTAL: "horizontal" }; /** * Dictionary of direction * @property {Object} Direction * @property {1} Direction.UP up * @property {2} Direction.DOWN down * @property {3} Direction.LEFT left * @property {4} Direction.RIGHT right * @member ns.event.gesture * @static */ gesture.Direction = { UP: "up", DOWN: "down", LEFT: "left", RIGHT: "right" }; /** * Dictionary of gesture events state * @property {Object} Event * @property {"start"} Event.START start * @property {"move"} Event.MOVE move * @property {"end"} Event.END end * @property {"cancel"} Event.CANCEL cancel * @property {"blocked"} Event.BLOCKED blocked * @member ns.event.gesture * @static */ gesture.Event = { START: "start", MOVE: "move", END: "end", CANCEL: "cancel", BLOCKED: "blocked" }; /** * Dictionary of gesture events flags * @property {Object} Result * @property {number} [Result.PENDING=1] is pending * @property {number} [Result.RUNNING=2] is running * @property {number} [Result.FINISHED=4] is finished * @property {number} [Result.BLOCK=8] is blocked * @member ns.event.gesture * @static */ gesture.Result = { PENDING: 1, RUNNING: 2, FINISHED: 4, BLOCK: 8 }; /** * Create plugin namespace. * @property {Object} plugin * @member ns.event.gesture * @static */ gesture.plugin = {}; /** * Create object of Detector * @method createDetector * @param {string} gesture * @param {HTMLElement} eventSender * @param {Object} options * @return {ns.event.gesture.Gesture} * @member ns.event.gesture * @static */ gesture.createDetector = function (gesture, eventSender, options) { if (!gesture.plugin[gesture]) { throw gesture + " gesture is not supported"; } return new gesture.plugin[gesture](eventSender, options); }; event.gesture = gesture; }(ns)); /*global window, ns, define, ns */ /*jslint nomen: true */ /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * #Drawer Widget * Core Drawer widget is a base for creating Drawer widgets for profiles. It * provides drawer functionality - container with ability to open and close with * an animation. * * ### Positioning Drawer left / right (option) * To change position of a Drawer please set data-position attribute of Drawer * element to: * * - left (left position, default) * - right (right position) * * ##Opening / Closing Drawer * To open / close Drawer one can use open() and close() methods. * * ##Checking if Drawer is opened. * To check if Drawer is opened use widget`s isOpen() method. * * ##Creating widget * Core drawer is a base class - examples of creating widgets are described in * documentation of profiles * * @class ns.widget.core.Drawer * @extends ns.widget.BaseWidget * @author Hyeoncheol Choi */ (function (document, ns) { "use strict"; /** * @property {Object} Widget Alias for {@link ns.widget.BaseWidget} * @member ns.widget.core.Drawer * @private * @static */ var BaseWidget = ns.widget.BaseWidget, /** * @property {Object} selectors Alias for class ns.util.selectors * @member ns.widget.core.Drawer * @private * @static * @readonly */ selectors = ns.util.selectors, utilDOM = ns.util.DOM, events = ns.event, history = ns.history, Gesture = ns.event.gesture, Page = ns.widget.core.Page, STATE = { CLOSED: "closed", OPENED: "opened", SLIDING: "sliding", SETTLING: "settling" }, /** * Events * @event draweropen Event triggered then the drawer is opened. * @event drawerclose Event triggered then the drawer is closed. * @member ns.widget.core.Drawer */ CUSTOM_EVENTS = { OPEN: "draweropen", CLOSE: "drawerclose" }, /** * Default values */ DEFAULT = { WIDTH: 240, DURATION: 300, POSITION: "left" }, /** * Drawer constructor * @method Drawer */ Drawer = function () { var self = this; /** * Drawer field containing options * @property {string} options.position Position of Drawer ("left" or "right") * @property {number} options.width Width of Drawer * @property {number} options.duration Duration of Drawer entrance animation * @property {boolean} options.closeOnClick If true Drawer will be closed on overlay * @property {boolean} options.overlay Sets whether to show an overlay when Drawer is open. * @property {string} options.drawerTarget Set drawer target element as the css selector * @property {boolean} options.enable Enable drawer component * @property {number} options.dragEdge Set the area that can open the drawer as drag gesture in drawer target element * @member ns.widget.core.Drawer */ self.options = { position: DEFAULT.POSITION, width: DEFAULT.WIDTH, duration: DEFAULT.DURATION, closeOnClick: true, overlay: true, drawerTarget: "." + Page.classes.uiPage, enable: true, dragEdge: 1 }; self._pageSelector = null; self._isDrag = false; self._state = STATE.CLOSED; self._settlingType = STATE.CLOSED; self._translatedX = 0; self._ui = {}; self._eventBoundElement = null; self._drawerOverlay = null; }, /** * Dictionary object containing commonly used widget classes * @property {Object} classes * @member ns.widget.core.Drawer * @private * @static * @readonly */ classes = { page: Page.classes.uiPage, drawer: "ui-drawer", left: "ui-drawer-left", right: "ui-drawer-right", overlay: "ui-drawer-overlay", open: "ui-drawer-open", close: "ui-drawer-close" }, /** * {Object} Drawer widget prototype * @member ns.widget.core.Drawer * @private * @static */ prototype = new BaseWidget(); Drawer.prototype = prototype; Drawer.classes = classes; /** * Unbind drag events * @method unbindDragEvents * @param {Object} self * @param {HTMLElement} element * @member ns.widget.core.Drawer * @private * @static */ function unbindDragEvents(self, element) { var overlayElement = self._ui.drawerOverlay; events.disableGesture(element); events.off(element, "drag dragstart dragend dragcancel swipe swipeleft swiperight vmouseup", self, false); events.prefixedFastOff(self.element, "transitionEnd", self, false); events.off(window, "resize", self, false); if (overlayElement) { events.off(overlayElement, "vclick", self, false); } } /** * Bind drag events * @method bindDragEvents * @param {Object} self * @param {HTMLElement} element * @member ns.widget.core.Drawer * @private * @static */ function bindDragEvents(self, element) { var overlayElement = self._ui.drawerOverlay; self._eventBoundElement = element; events.enableGesture( element, new Gesture.Drag(), new Gesture.Swipe({ orientation: Gesture.Orientation.HORIZONTAL }) ); events.on(element, "drag dragstart dragend dragcancel swipe swipeleft swiperight vmouseup", self, false); events.prefixedFastOn(self.element, "transitionEnd", self, false); events.on(window, "resize", self, false); if (overlayElement) { events.on(overlayElement, "vclick", self, false); } } /** * Handle events * @method handleEvent * @param {Event} event * @member ns.widget.core.Drawer */ prototype.handleEvent = function (event) { var self = this; switch (event.type) { case "drag": self._onDrag(event); break; case "dragstart": self._onDragStart(event); break; case "dragend": self._onDragEnd(event); break; case "dragcancel": self._onDragCancel(event); break; case "vmouseup": self._onMouseup(event); break; case "swipe": case "swipeleft": case "swiperight": self._onSwipe(event); break; case "vclick": self._onClick(event); break; case "transitionend": case "webkitTransitionEnd": case "mozTransitionEnd": case "oTransitionEnd": case "msTransitionEnd": self._onTransitionEnd(event); break; case "resize": self._onResize(event); break; } }; /** * MouseUp event handler * @method _onMouseup * @member ns.widget.core.Drawer * @protected */ prototype._onMouseup = function () { var self = this; if (self._state === STATE.SLIDING) { self.close(); } }; /** * Click event handler * @method _onClick * @member ns.widget.core.Drawer * @protected */ prototype._onClick = function () { var self = this; if (self._state === STATE.OPENED) { self.close(); } }; /** * Resize event handler * @method _onResize * @member ns.widget.core.Drawer * @protected */ prototype._onResize = function () { var self = this; // resize event handler self._refresh(); }; /** * webkitTransitionEnd event handler * @method _onTransitionEnd * @member ns.widget.core.Drawer * @protected */ prototype._onTransitionEnd = function () { var self = this, position = self.options.position, drawerOverlay = self._drawerOverlay; if (self._state === STATE.SETTLING) { if (self._settlingType === STATE.OPENED) { self.trigger(CUSTOM_EVENTS.OPEN, { position: position }); self._setActive(true); self._state = STATE.OPENED; } else { self.close(); self.trigger(CUSTOM_EVENTS.CLOSE, { position: position }); self._setActive(false); self._state = STATE.CLOSED; if (drawerOverlay) { drawerOverlay.style.visibility = "hidden"; } } } }; /** * Swipe event handler * @method _onSwipe * @protected * @param {Event} event * @member ns.widget.core.Drawer */ prototype._onSwipe = function (event) { var self = this, direction, options = self.options; // Now mobile has two swipe event if (event.detail) { direction = event.detail.direction === "left" ? "right" : "left"; } else if (event.type === "swiperight") { direction = "left"; } else if (event.type === "swipeleft") { direction = "right"; } if (options.enable && self._isDrag && options.position === direction) { self.open(); self._isDrag = false; } }; /** * Dragstart event handler * @method _onDragStart * @protected * @param {Event} event * @member ns.widget.core.Drawer */ prototype._onDragStart = function (event) { var self = this; if (self._state === STATE.OPENED) { return; } if (self.options.enable && !self._isDrag && self._state !== STATE.SETTLING && self._checkSideEdge(event)) { self._isDrag = true; } else { self.close(); } }; /** * Drag event handler * @method _onDrag * @protected * @param {Event} event * @member ns.widget.core.Drawer */ prototype._onDrag = function (event) { var self = this, deltaX = event.detail.deltaX, options = self.options, translatedX = self._translatedX, movedX; if (options.enable && self._isDrag && self._state !== STATE.SETTLING) { if (options.position === "left") { movedX = -options.width + deltaX + translatedX; if (movedX < 0) { self._translate(movedX, 0); } } else { movedX = window.innerWidth + deltaX - translatedX; if (movedX > 0 && movedX > window.innerWidth - options.width) { self._translate(movedX, 0); } } } }; /** * DragEnd event handler * @method _onDragEnd * @protected * @param {Event} event * @member ns.widget.core.Drawer */ prototype._onDragEnd = function (event) { var self = this, options = self.options, detail = event.detail; if (options.enable && self._isDrag) { if (Math.abs(detail.deltaX) > options.width / 2) { self.open(); } else if (self._state !== STATE.SETTLING) { self.close(); } } self._isDrag = false; }; /** * DragCancel event handler * @method _onDragCancel * @protected * @member ns.widget.core.Drawer */ prototype._onDragCancel = function () { var self = this; if (self.options.enable && self._isDrag) { self.close(); } self._isDrag = false; }; /** * Drawer translate function * @method _translate * @param {number} x * @param {number} duration * @member ns.widget.core.Drawer * @protected */ prototype._translate = function (x, duration) { var self = this, element = self.element; if (self._state !== STATE.SETTLING) { self._state = STATE.SLIDING; } if (duration) { utilDOM.setPrefixedStyle(element, "transition", utilDOM.getPrefixedValue("transform " + duration / 1000 + "s ease-out")); } // there should be a helper for this :( utilDOM.setPrefixedStyle(element, "transform", "translate3d(" + x + "px, 0px, 0px)"); if (self.options.overlay) { self._setOverlay(x); } if (!duration) { self._onTransitionEnd(); } }; /** * Set overlay opacity and visibility * @method _setOverlay * @param {number} x * @member ns.widget.core.Drawer * @protected */ prototype._setOverlay = function (x) { var self = this, options = self.options, overlay = self._ui.drawerOverlay, overlayStyle = overlay.style, absX = Math.abs(x), ratio = options.position === "right" ? absX / window.innerWidth : absX / options.width; if (ratio < 1) { overlayStyle.visibility = "visible"; } else { overlayStyle.visibility = "hidden"; } overlayStyle.opacity = 1 - ratio; }; /** * Set active status in drawer router * @method _setActive * @param {boolean} active * @member ns.widget.core.Drawer * @protected */ prototype._setActive = function (active) { var self = this, route = ns.router.getInstance().getRoute("drawer"); if (active) { route.setActive(self); } else { route.setActive(null); } }; /** * Build structure of Drawer widget * @method _build * @param {HTMLElement} element * @return {HTMLElement} Returns built element * @member ns.widget.core.Drawer * @protected */ prototype._build = function (element) { var self = this, ui = self._ui, options = self.options, targetElement; element.classList.add(classes.drawer); element.style.top = 0; targetElement = selectors.getClosestBySelector(element, options.drawerTarget); if (targetElement) { targetElement.appendChild(element); targetElement.style.overflowX = "hidden"; } if (self.options.overlay) { ui.drawerOverlay = self._createOverlay(element); ui.drawerOverlay.style.visibility = "hidden"; } if (!ui.placeholder) { ui.placeholder = document.createComment(element.id + "-placeholder"); element.parentNode.insertBefore(ui.placeholder, element); } ui.targetElement = targetElement; return element; }; /** * Initialization of Drawer widget * @method _init * @param {HTMLElement} element * @member ns.widget.core.Drawer * @protected */ prototype._init = function (element) { var self = this, ui = self._ui; ui.drawerPage = selectors.getClosestByClass(element, classes.page); ui.drawerPage.style.overflowX = "hidden"; self._initLayout(); return element; }; /** * init Drawer widget layout * @method _initLayout * @protected * @member ns.widget.core.Drawer */ prototype._initLayout = function () { var self = this, options = self.options, element = self.element, elementStyle = element.style, ui = self._ui, overlayStyle = ui.drawerOverlay ? ui.drawerOverlay.style : null, height; options.width = options.width || ui.targetElement.offsetWidth; height = ui.targetElement.offsetHeight; elementStyle.width = (options.width !== 0) ? options.width + "px" : "100%"; elementStyle.height = (height !== 0) ? height + "px" : "100%"; elementStyle.top = "0"; if (overlayStyle) { overlayStyle.width = window.innerWidth + "px"; overlayStyle.height = window.innerHeight + "px"; overlayStyle.top = 0; } if (options.position === "right") { element.classList.add(classes.right); self._translate(window.innerWidth, 0); } else { // left or default element.classList.add(classes.left); self._translate(-options.width, 0); } self._state = STATE.CLOSED; }; /** * Provides translation if position is set to right * @method _translateRight * @member ns.widget.core.Drawer * @protected */ prototype._translateRight = function () { var self = this, options = self.options; if (options.position === "right") { // If drawer position is right, drawer should be moved right side if (self._state === STATE.OPENED) { // drawer opened self._translate(window.innerWidth - options.width, 0); } else { // drawer closed self._translate(window.innerWidth, 0); } } }; /** * Check dragstart event whether triggered on side edge area or not * @method _checkSideEdge * @protected * @param {Event} event * @member ns.widget.core.Drawer */ prototype._checkSideEdge = function (event) { var self = this, detail = event.detail, eventClientX = detail.pointer.clientX - detail.estimatedDeltaX, options = self.options, position = options.position, boundElement = self._eventBoundElement, boundElementOffsetWidth = boundElement.offsetWidth, boundElementRightEdge = boundElement.offsetLeft + boundElementOffsetWidth, dragStartArea = boundElementOffsetWidth * options.dragEdge; return ((position === "left" && eventClientX > 0 && eventClientX < dragStartArea) || (position === "right" && eventClientX > boundElementRightEdge - dragStartArea && eventClientX < boundElementRightEdge)); }; /** * Refreshes Drawer widget * @method _refresh * @member ns.widget.core.Drawer * @protected */ prototype._refresh = function () { // Drawer layout has been set by parent element layout var self = this; self._translateRight(); self._initLayout(); }; /** * Creates Drawer overlay element * @method _createOverlay * @param {HTMLElement} element * @member ns.widget.core.Drawer * @protected */ prototype._createOverlay = function (element) { var overlayElement = document.createElement("div"); overlayElement.classList.add(classes.overlay); element.parentNode.insertBefore(overlayElement, element); return overlayElement; }; /** * Binds events to a Drawer widget * @method _bindEvents * @member ns.widget.core.Drawer * @protected */ prototype._bindEvents = function () { var self = this, targetElement = self._ui.targetElement; bindDragEvents(self, targetElement); }; /** * Enable Drawer widget * @method _enable * @protected * @member ns.widget.core.Drawer */ prototype._enable = function () { this._oneOption("enable", true); }; /** * Disable Drawer widget * @method _disable * @protected * @member ns.widget.core.Drawer */ prototype._disable = function () { this._oneOption("enable", false); }; /** * Checks Drawer status * @method isOpen * @member ns.widget.core.Drawer * @return {boolean} Returns true if Drawer is open */ prototype.isOpen = function () { return (this._state === STATE.OPENED); }; /** * Opens Drawer widget * @method open * @param {number} [duration] Duration for opening, if is not set then method take value from options * @member ns.widget.core.Drawer */ prototype.open = function (duration) { var self = this, options = self.options, drawerClassList = self.element.classList, drawerOverlay = self._ui.drawerOverlay; if (self._state !== STATE.OPENED) { self._state = STATE.SETTLING; self._settlingType = STATE.OPENED; duration = duration !== undefined ? duration : options.duration; if (drawerOverlay) { drawerOverlay.style.visibility = "visible"; } drawerClassList.remove(classes.close); drawerClassList.add(classes.open); if (options.position === "left") { self._translate(0, duration); } else { self._translate(window.innerWidth - options.width, duration); } } }; /** * Closes Drawer widget * @requires mobile wearable * * * @method close * @param {Object} options This value is router options whether reverse or not. * @param {number} [duration] Duration for closing, if is not set then method take value from options * @member ns.widget.core.Drawer */ prototype.close = function (options, duration) { var self = this, reverse = options ? options.reverse : false, selfOptions = self.options, drawerClassList = self.element.classList; if (self._state !== STATE.CLOSED) { if (!reverse && self._state === STATE.OPENED && !ns.getConfig("disableRouter")) { // This method was fired by JS code or this widget. history.back(); return; } self._state = STATE.SETTLING; self._settlingType = STATE.CLOSED; duration = duration !== undefined ? duration : selfOptions.duration; drawerClassList.remove(classes.open); drawerClassList.add(classes.close); if (selfOptions.position === "left") { self._translate(-selfOptions.width, duration); } else { self._translate(window.innerWidth, duration); } } }; /** * Set Drawer drag handler. * If developer use handler, drag event is bound at handler only. * @method setDragHandler * @param {HTMLElement} element * @member ns.widget.core.Drawer */ prototype.setDragHandler = function (element) { var self = this; self.options.dragEdge = 1; unbindDragEvents(self, self._eventBoundElement); bindDragEvents(self, element); }; /** * Transition Drawer widget. * This method use only positive integer number. * @method transition * @param {number} position * @member ns.widget.core.Drawer */ prototype.transition = function (position) { var self = this, options = self.options; if (options.position === "left") { self._translate(-options.width + position, options.duration); } else { self._translate(options.width - position, options.duration); } self._translatedX = position; }; /** * Get state of Drawer widget. */ prototype.getState = function () { return this._state; }; /** * Destroys Drawer widget * @method _destroy * @member ns.widget.core.Drawer * @protected */ prototype._destroy = function () { var self = this, ui = self._ui, drawerOverlay = ui.drawerOverlay, placeholder = ui.placeholder, placeholderParent = placeholder.parentNode, element = self.element; placeholderParent.insertBefore(element, placeholder); placeholderParent.removeChild(placeholder); if (drawerOverlay) { drawerOverlay.removeEventListener("vclick", self._onClickBound, false); } unbindDragEvents(self, self._eventBoundElement); }; ns.widget.core.Drawer = Drawer; }(window.document, ns)); /*global window, ns, define */ /*jslint nomen: true */ /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * #Route Drawer * Support class for router to control drawer widget in profile Wearable. * @class ns.router.route.drawer * @author Hyeoncheol Choi */ (function () { "use strict"; var CoreDrawer = ns.widget.core.Drawer, Router = ns.router.Router, path = ns.util.path, history = ns.history, engine = ns.engine, routeDrawer = {}, drawerHashKey = "drawer=true", drawerHashKeyReg = /([&|\?]drawer=true)/; routeDrawer.orderNumber = 1000; /** * Property containing default properties * @property {Object} defaults * @property {string} defaults.transition="none" * @static * @member ns.router.route.drawer */ routeDrawer.defaults = { transition: "none" }; /** * Property defining selector for filtering only drawer elements * @property {string} filter * @member ns.router.route.drawer * @static */ routeDrawer.filter = "." + CoreDrawer.classes.drawer; /** * Returns default route options used inside Router. * But, drawer router has not options. * @method option * @static * @member ns.router.route.drawer * @return {null} */ routeDrawer.option = function () { return null; }; /** * This method opens the drawer. * @method open * @param {HTMLElement} drawerElement * @member ns.router.route.drawer */ routeDrawer.open = function (drawerElement) { var drawer = engine.instanceWidget(drawerElement, "Drawer"); drawer.open(); }; /** * This method determines target drawer to open. * @method find * @param {string} absUrl Absolute path to opened drawer widget * @member ns.router.route.drawer * @return {?HTMLElement} drawerElement */ routeDrawer.find = function (absUrl) { var dataUrl = path.convertUrlToDataUrl(absUrl), activePage = Router.getInstance().getContainer().getActivePage(), drawer; drawer = activePage.element.querySelector("#" + dataUrl); return drawer; }; /** * This method parses HTML and runs scripts from parsed code. * But, drawer router doesn't need to that. * @method parse * @member ns.router.route.drawer */ routeDrawer.parse = function () { return null; }; /** * This method sets active drawer and manages history. * @method setActive * @param {Object} activeDrawer * @member ns.router.route.drawer * @static */ routeDrawer.setActive = function (activeDrawer) { var url, pathLocation = path.getLocation(), documentUrl = pathLocation.replace(drawerHashKeyReg, ""); this._activeDrawer = activeDrawer; if (activeDrawer) { url = path.addHashSearchParams(documentUrl, drawerHashKey); history.replace({}, "", url); this.active = true; } else if (pathLocation !== documentUrl) { history.back(); } }; /** * This method handles hash change. * @method onHashChange * @param {string} url * @param {Object} options * @param {string} prev Previous url string * @static * @member ns.router.route.drawer * @return {null} */ routeDrawer.onHashChange = function (url, options, prev) { var self = this, activeDrawer = self._activeDrawer, stateUrl = prev.stateUrl; if (activeDrawer && stateUrl.search(drawerHashKey) > 0 && url.search(drawerHashKey) < 0) { activeDrawer.close(options); this.active = false; return true; } return false; }; ns.router.route.drawer = routeDrawer; }()); /*global window, define, ns */ /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * #Anchor Highlight Utility * * Utility enables highlight on clickable components. * @class ns.util.anchorHighlight * @author Maciej Urbanski * @author Damian Osipiuk * @author Konrad Lipner */ (function (document, window, ns) { "use strict"; /* anchorHighlightController.js To prevent performance regression when scrolling, do not apply hover class in anchor. Instead, this code checks scrolling for time threshold and decide how to handle the color. When scrolling with anchor, it checks flag and decide to highlight anchor. While it helps to improve scroll performance, it lowers responsiveness of the element for 50msec. */ /** * Touch start x * @property {number} startX * @member ns.util.anchorHighlight * @private * @static */ var startX = 0, /** * Touch start y * @property {number} startY * @member ns.util.anchorHighlight * @private * @static */ startY = 0, /** * Touch target element * @property {HTMLElement} target * @member ns.util.anchorHighlight * @private * @static */ classes = { /** * Class used to mark element as active * @property {string} [classes.ACTIVE_LI="ui-li-active"] * @member ns.util.anchorHighlight * @private * @static */ ACTIVE_LI: "ui-li-active", /** * Class used to mark button as active * @property {string} [classes.ACTIVE_BTN="ui-btn-active"] * @member ns.util.anchorHighlight * @private * @static */ ACTIVE_BTN: "ui-btn-active", /** * Class used to mark button as inactive * @property {string} [classes.INACTIVE_BTN="ui-btn-inactive"] * @member ns.util.anchorHighlight * @private * @static */ INACTIVE_BTN: "ui-btn-inactive", /** * Class used to select button * @property {string} [classes.BUTTON="ui-btn"] btn * @member ns.util.anchorHighlight * @private * @static */ BUTTON: "ui-btn", /** * Class used to select button in header (old notation) * @property {string} [classes.HEADER_BUTTON="ui-header-btn"] btn * @member ns.util.anchorHighlight * @private * @static */ HEADER_BUTTON: "ui-header-btn", /** * Class used to select anchor in tabbar widget * @property {string} [classes.TABBAR_ANCHOR="ui-tabbar-anchor"] anchor * @member ns.util.anchorHighlight * @private * @static */ TABBAR_ANCHOR: "ui-tabbar-anchor", /** * Class used to select navigation item * @property {string} [classes.NAVIGATION_BUTTON="ui-navigation-item"] btn * @member ns.util.anchorHighlight * @private * @static */ NAVIGATION_BUTTON: "ui-navigation-item" }, events = { ACTIVE_LI: "anchorhighlightactiveli" }, /** * Alias for class {@link ns.util.selectors} * @property {Object} selectors * @member ns.util.anchorHighlight * @private * @static */ selectors = ns.util.selectors, /** * Alias for class {@link ns.event} * @property {Object} event * @member ns.util.anchorHighlight * @private * @static */ eventUtil = ns.event, // cache function abs = Math.abs, /** * Get closest li element * @method detectLiElement * @param {HTMLElement} target * @return {HTMLElement} * @member ns.util.anchorHighlight * @private * @static */ detectLiElement = function (target) { return selectors.getClosestByTag(target, "li"); }, anchorHighlight = { /** * Object with default options * @property {Object} options * Threshold after which didScroll will be set * @property {number} [options.scrollThreshold=10] * Time to wait before adding activeClass * @property {number} [options.addActiveClassDelay=50] * Time to stay activeClass after touch end * @property {number} [options.keepActiveClassDelay=100] * @member ns.util.anchorHighlight * @private * @static */ options: { scrollThreshold: 10, addActiveClassDelay: 50, keepActiveClassDelay: 100 }, _startTime: 0, _startRemoveTime: 0, // inform that touch was ended _touchEnd: false, _liTarget: null, /** * Touch button target element * @property {HTMLElement} buttonTarget * @member ns.util.anchorHighlight * @private * @static */ _target: null, /** * Did page scrolled * @property {boolean} didScroll * @member ns.util.anchorHighlight * @private * @static */ _didScroll: false, _buttonTarget: null, // inform that animation of button's activation was ended _activeAnimationFinished: false, //cache function _requestAnimationFrame: ns.util.windowRequestAnimationFrame }, // cache function slice = Array.prototype.slice; /** * Get closest highlightable element * @method detectHighlightTarget * @param {HTMLElement} target * @return {HTMLElement} * @member ns.util.anchorHighlight * @private * @static */ function detectHighlightTarget(target) { return selectors.getClosestBySelector(target, "a, label"); } /** * Get closest button element * @method detectLiElement * @param {HTMLElement} target * @return {HTMLElement} * @member ns.util.anchorHighlight * @private * @static */ function detectBtnElement(target) { return selectors.getClosestByClass(target, classes.BUTTON) || selectors.getClosestByClass(target, classes.HEADER_BUTTON) || selectors.getClosestByClass(target, classes.NAVIGATION_BUTTON) || selectors.getClosestByClass(target, classes.TABBAR_ANCHOR); } /** * Clear active class on button * @method clearBtnActiveClass * @param {Event} event * @member ns.util.anchorHighlight * @private * @static */ function clearBtnActiveClass(event) { var target = event.target, classList = target.classList; // if this is callback of activate animation and if (classList.contains(classes.ACTIVE_BTN) && !classList.contains(classes.INACTIVE_BTN)) { // set that animation was ended (used in touch end) anchorHighlight._activeAnimationFinished = true; // if touch end previously if (anchorHighlight._touchEnd || target !== anchorHighlight._buttonTarget) { // start inactivate animation classList.add(classes.INACTIVE_BTN); } } else { //when target of animationend event is child of active element instead of active element // itself if (!classList.contains(classes.ACTIVE_BTN) && !classList.contains(classes.INACTIVE_BTN)) { target.parentNode.classList.remove(classes.ACTIVE_BTN); target.parentNode.classList.remove(classes.INACTIVE_BTN); } // this is callback for inactive animation end classList.remove(classes.INACTIVE_BTN); classList.remove(classes.ACTIVE_BTN); } } /** * Add inactive class on touch end * @method addButtonInactiveClass * @member ns.util.anchorHighlight * @private * @static */ function addButtonInactiveClass() { if (anchorHighlight._buttonTarget) { anchorHighlight._buttonTarget.classList.add(classes.INACTIVE_BTN); } } /** * Add active class on touch end * @method addButtonActiveClass * @member ns.util.anchorHighlight * @private * @static */ function addButtonActiveClass() { anchorHighlight._buttonTarget.classList.add(classes.ACTIVE_BTN); anchorHighlight._activeAnimationFinished = false; } /** * Clear classes on page or popup hide * @method hideClear * @member ns.util.anchorHighlight * @private * @static */ function hideClear() { var btnTarget = anchorHighlight._buttonTarget; if (btnTarget) { btnTarget.classList.remove(classes.ACTIVE_BTN); btnTarget.classList.remove(classes.INACTIVE_BTN); } if (anchorHighlight._target) { anchorHighlight._target.classList.remove(classes.ACTIVE_LI); } } /** * Add active class to touched element * @method addActiveClass * @member ns.util.anchorHighlight * @private * @static */ function addActiveClass() { var btnTargetClassList, dTime; if (anchorHighlight._startTime) { dTime = Date.now() - anchorHighlight._startTime; if (dTime > anchorHighlight.options.addActiveClassDelay) { anchorHighlight._startTime = 0; anchorHighlight._buttonTarget = detectBtnElement(anchorHighlight._target); anchorHighlight._target = detectHighlightTarget(anchorHighlight._target); if (!anchorHighlight._didScroll) { anchorHighlight._liTarget = anchorHighlight._detectLiElement(anchorHighlight._target); if (anchorHighlight._liTarget) { anchorHighlight._liTarget.classList.add(classes.ACTIVE_LI); eventUtil.trigger(anchorHighlight._liTarget, events.ACTIVE_LI, {}); } anchorHighlight._liTarget = null; if (anchorHighlight._buttonTarget) { btnTargetClassList = anchorHighlight._buttonTarget.classList; btnTargetClassList.remove(classes.ACTIVE_BTN); btnTargetClassList.remove(classes.INACTIVE_BTN); anchorHighlight._requestAnimationFrame(addButtonActiveClass); } } } else { anchorHighlight._requestAnimationFrame(addActiveClass); } } } /** * Get all active elements * @method getActiveElements * @return {Array} * @member ns.util.anchorHighlight * @private * @static */ function getActiveElements() { return slice.call(document.getElementsByClassName(classes.ACTIVE_LI)); } /** * Remove active class from current active objects * @method clearActiveClass * @member ns.util.anchorHighlight * @private * @static */ function clearActiveClass() { var activeA = getActiveElements(), activeALength = activeA.length, i = 0; for (; i < activeALength; i++) { activeA[i].classList.remove(classes.ACTIVE_LI); } } /** * Remove active class from active elements * @method removeActiveClass * @member ns.util.anchorHighlight * @private * @static */ function removeActiveClassLoop() { var dTime = Date.now() - anchorHighlight._startRemoveTime; if (dTime > anchorHighlight.options.keepActiveClassDelay) { // after touchend clearActiveClass(); } else { anchorHighlight._requestAnimationFrame(removeActiveClassLoop); } } /** * Function invoked during touch move * @method touchmoveHandler * @param {Event} event * @member ns.util.anchorHighlight * @private * @static */ function touchmoveHandler(event) { var touch = event.touches[0], scrollThreshold = anchorHighlight.options.scrollThreshold; // if move looks like scroll if (!anchorHighlight._didScroll && // if move is bigger then threshold (abs(touch.clientX - startX) > scrollThreshold || abs(touch.clientY - startY) > scrollThreshold)) { anchorHighlight._startTime = 0; // we clear active classes anchorHighlight._requestAnimationFrame(clearActiveClass); anchorHighlight._didScroll = true; } } /** * Function invoked after touch start * @method touchstartHandler * @param {Event} event * @member ns.util.anchorHighlight * @private * @static */ function touchstartHandler(event) { var touches = event.touches, touch; if (touches.length === 1) { touch = touches[0]; anchorHighlight._didScroll = false; startX = touch.clientX; startY = touch.clientY; anchorHighlight._target = event.target; anchorHighlight._startTime = Date.now(); anchorHighlight._startRemoveTime = 0; anchorHighlight._requestAnimationFrame(addActiveClass); anchorHighlight._touchEnd = false; } } /** * Function invoked after touch * @method touchendHandler * @param {Event} event * @member ns.util.anchorHighlight * @private * @static */ function touchendHandler(event) { anchorHighlight._startRemoveTime = event.timeStamp; if (event.touches.length === 0) { if (!anchorHighlight._didScroll) { anchorHighlight._startTime = 0; anchorHighlight._requestAnimationFrame(removeActiveClassLoop); } // if we finished activate animation then start inactive animation if (anchorHighlight._activeAnimationFinished) { anchorHighlight._requestAnimationFrame(addButtonInactiveClass); } anchorHighlight._didScroll = false; anchorHighlight._touchEnd = true; } } /** * Function invoked after visibilitychange event * @method checkPageVisibility * @member ns.util.anchorHighlight * @private * @static */ function checkPageVisibility() { /* istanbul ignore if */ if (document.visibilityState === "hidden") { anchorHighlight._removeActiveClassLoop(); } } ns.util.anchorHighlight = anchorHighlight; anchorHighlight.enable = enable; anchorHighlight.disable = disable; anchorHighlight._clearActiveClass = clearActiveClass; anchorHighlight._detectHighlightTarget = detectHighlightTarget; anchorHighlight._detectBtnElement = detectBtnElement; anchorHighlight._clearBtnActiveClass = clearBtnActiveClass; anchorHighlight._removeActiveClassLoop = removeActiveClassLoop; anchorHighlight._addButtonInactiveClass = addButtonInactiveClass; anchorHighlight._addButtonActiveClass = addButtonActiveClass; anchorHighlight._hideClear = hideClear; anchorHighlight._addActiveClass = addActiveClass; anchorHighlight._detectLiElement = detectLiElement; anchorHighlight._touchmoveHandler = touchmoveHandler; anchorHighlight._touchendHandler = touchendHandler; anchorHighlight._touchstartHandler = touchstartHandler; anchorHighlight._checkPageVisibility = checkPageVisibility; anchorHighlight._hideClear = hideClear; anchorHighlight._clearBtnActiveClass = clearBtnActiveClass; /** * Bind events to document * @method enable * @member ns.util.anchorHighlight * @static */ function enable() { document.addEventListener("touchstart", anchorHighlight._touchstartHandler, false); document.addEventListener("touchend", anchorHighlight._touchendHandler, false); document.addEventListener("touchmove", anchorHighlight._touchmoveHandler, false); document.addEventListener("visibilitychange", anchorHighlight._checkPageVisibility, false); document.addEventListener("pagehide", anchorHighlight._hideClear, false); document.addEventListener("popuphide", anchorHighlight._hideClear, false); document.addEventListener("animationend", anchorHighlight._clearBtnActiveClass, false); document.addEventListener("animationEnd", anchorHighlight._clearBtnActiveClass, false); document.addEventListener("webkitAnimationEnd", anchorHighlight._clearBtnActiveClass, false); } /** * Unbinds events from document. * @method disable * @member ns.util.anchorHighlight * @static */ function disable() { document.removeEventListener("touchstart", anchorHighlight._touchstartHandler, false); document.removeEventListener("touchend", anchorHighlight._touchendHandler, false); document.removeEventListener("touchmove", anchorHighlight._touchmoveHandler, false); document.removeEventListener("visibilitychange", anchorHighlight._checkPageVisibility, false); document.removeEventListener("pagehide", anchorHighlight._hideClear, false); document.removeEventListener("popuphide", anchorHighlight._hideClear, false); document.removeEventListener("animationend", anchorHighlight._clearBtnActiveClass, false); document.removeEventListener("animationEnd", anchorHighlight._clearBtnActiveClass, false); document.removeEventListener("webkitAnimationEnd", anchorHighlight._clearBtnActiveClass, false); } enable(); }(document, window, ns)); /*global window, ns, define */ (function (window, document, ns) { "use strict"; var PI = Math.PI, cos = Math.cos, sin = Math.sin, SVGNS = "http://www.w3.org/2000/svg", objectUtils = ns.util.object, classes = { polar: "ui-polar", animated: "ui-animated" }, defaultsArc = { x: 180, y: 180, r: 170, arcStart: 0, arcEnd: 90, width: 5, color: "black", animation: false, linecap: "butt", referenceDegree: 0 }, defaultsRadius = { x: 180, y: 180, r: 170, degrees: 0, length: 180, direction: "in", width: 5, color: "black" }, defaultsText = { x: 180, y: 180, text: "Text", position: "middle", color: "white" }, defaultsCircle = { x: 180, y: 180, r: 170, color: "white" }, polar; /** * Calculate polar coords to cartesian * @param {number} centerX * @param {number} centerY * @param {number} radius * @param {number} angleInDegrees * @return {{x: number, y: number}} */ function polarToCartesian(centerX, centerY, radius, angleInDegrees) { var angleInRadians = angleInDegrees * PI / 180.0; return { x: centerX + (radius * sin(angleInRadians)), y: centerY - (radius * cos(angleInRadians)) }; } /** * Create description of path for arc * @param {number} x * @param {number} y * @param {number} radius * @param {number} startAngle Angle in degrees where arc starts * @param {number} endAngle Angle in degrees where arc ends * @return {string} */ function describeArc(x, y, radius, startAngle, endAngle) { var start = polarToCartesian(x, y, radius, endAngle), end = polarToCartesian(x, y, radius, startAngle), arcSweep = endAngle - startAngle <= 180 ? "0" : "1", clockWise = 0; return [ "M", start.x, start.y, "A", radius, radius, 0, arcSweep, clockWise, end.x, end.y ].join(" "); } function addPath(svg, options) { var path = document.createElementNS(SVGNS, "path"); path.setAttribute("class", options.classes); path.setAttribute("fill", "none"); path.setAttribute("stroke", options.color); path.setAttribute("stroke-width", options.width); path.setAttribute("d", describeArc(options.x, options.y, options.r, options.referenceDegree + options.arcStart, options.referenceDegree + options.arcEnd)); path.setAttribute("data-initial-degree", options.referenceDegree); path.setAttribute("stroke-linecap", options.linecap); svg.appendChild(path); } function addAnimation(element, options) { var style = element.style, value = options.x + "px " + options.y + "px", degrees = (options.referenceDegree + options.arcStart) || options.degrees; // phantom not support classList on SVG if (element.classList) { // add class for transition element.classList.add(classes.animated); } // set transform style.webkitTransformOrigin = value; style.mozTransformOrigin = value; style.transformOrigin = value; value = "rotate(" + degrees + "deg)"; style.webkitTransform = value; style.mozTransform = value; style.transform = value; } function addRadius(svg, options) { var line = document.createElementNS(SVGNS, "line"), positionStart, positionEnd; line.setAttribute("class", options.classes); line.setAttribute("stroke", options.color); line.setAttribute("stroke-width", options.width); if (options.direction === "out") { positionStart = polarToCartesian(options.x, options.y, options.r, options.degrees); positionEnd = polarToCartesian(options.x, options.y, options.r - options.length, options.degrees); } else { positionStart = polarToCartesian(options.x, options.y, options.r - options.length, options.degrees); positionEnd = polarToCartesian(options.x, options.y, options.r, options.degrees); } line.setAttribute("x1", positionStart.x); line.setAttribute("y1", positionStart.y); line.setAttribute("x2", positionEnd.x); line.setAttribute("y2", positionEnd.y); svg.appendChild(line); return line; } function addText(svg, options) { var text = document.createElementNS(SVGNS, "text"); text.setAttribute("x", options.x); text.setAttribute("y", options.y); text.setAttribute("text-anchor", options.position); text.setAttribute("fill", options.color); text.setAttribute("transform", options.transform); text.textContent = options.text; svg.appendChild(text); } function addCircle(svg, options) { var circle = document.createElementNS(SVGNS, "circle"); circle.setAttribute("stroke", options.color); circle.setAttribute("stroke-width", options.width); circle.setAttribute("cx", options.x); circle.setAttribute("cy", options.y); circle.setAttribute("r", options.r); circle.setAttribute("fill", options.fill); svg.appendChild(circle); return circle; } function updatePathPosition(path, options) { var reference; if (options.animation) { addAnimation(path, options); } else { if (path) { reference = parseInt(path.getAttribute("data-initial-degree"), 10) || options.referenceDegree; path.setAttribute("data-initial-degree", reference); path.setAttribute("d", describeArc(options.x, options.y, options.r, reference + options.arcStart, reference + options.arcEnd)); } } } function updateLinePosition(line, options) { var positionStart, positionEnd; if (options.animation) { addAnimation(line, options); } else { if (line) { positionStart = polarToCartesian(options.x, options.y, options.r, options.degrees); positionEnd = polarToCartesian(options.x, options.y, options.r - options.length, options.degrees); line.setAttribute("x1", positionStart.x); line.setAttribute("y1", positionStart.y); line.setAttribute("x2", positionEnd.x); line.setAttribute("y2", positionEnd.y); } } } polar = { default: { arc: defaultsArc, radius: defaultsRadius, text: defaultsText }, classes: classes, polarToCartesian: polarToCartesian, /** * creates SVG element * @method createSVG * @member ns.util.polar * @param {HTMLElement} element * @return {SVGElement} * @static */ createSVG: function (element) { var svg = document.createElementNS(SVGNS, "svg"); // phantom not support classList on SVG if (svg.classList) { // add class to svg element svg.classList.add(classes.polar); } // if element is set, add svg as child node if (element) { element.appendChild(svg); } return svg; }, /** * draw arc on the svg element * @method addArc * @member ns.util.polar * @param {SVGElement} svg * @param {Object} options * @return {SVGElement} * @static */ addArc: function (svg, options) { // read or create new svg svg = svg || this.createSVG(); // set options options = objectUtils.merge({}, defaultsArc, options || {}); // add path with arc addPath(svg, options); return svg; }, /** * draw radius on the svg element * @method addRadius * @member ns.util.polar * @param {SVGElement} svg * @param {Object} options * @return {SVGElement} * @static */ addRadius: function (svg, options) { // read or create new svg svg = svg || this.createSVG(); // add path with radius options = objectUtils.merge({}, defaultsRadius, options || {}); return addRadius(svg, options); }, /** * draw text on the svg element * @method addText * @member ns.util.polar * @param {SVGElement} svg * @param {Object} options * @return {SVGElement} * @static */ addText: function (svg, options) { // read or create new svg svg = svg || this.createSVG(); // add path with radius options = objectUtils.merge({}, defaultsText, options || {}); addText(svg, options); return svg; }, /** * updatePosition for path or for line drawings in svg * @method updatePosition * @member ns.util.polar * @param {SVGElement} svg * @param {string} selector * @param {Object} options * @static */ updatePosition: function (svg, selector, options) { var path = svg && svg.querySelector("path" + selector), line; if (path) { // set options options = objectUtils.merge({}, defaultsArc, options || {}); updatePathPosition(path, options); } else { line = svg && svg.querySelector("line" + selector); if (line) { updateLinePosition(line, options); } } }, /** * draw circle on the svg element * @method addCircle * @member ns.util.polar * @param {SVGElement} svg * @param {Object} options * @return {SVGElement} * @static */ addCircle: function (svg, options) { var self = this; // read or create svg svg = svg || self.createSVG(); options = objectUtils.merge({}, defaultsCircle, options || {}); addCircle(svg, options); return svg; } }; ns.util.polar = polar; }(window, window.document, ns)); /* global requestAnimationFrame, define, ns, Math */ /** * # JS base scrolling tool * * This enable fast scrolling on element * * @class ns.util.scrolling */ (function (document, window, ns) { "use strict"; var eventUtil = ns.event, polarUtil = ns.util.polar, classes = { circular: "scrolling-circular", direction: "scrolling-direction", scrollbar: "scrolling-scrollbar", path: "scrolling-path", thumb: "scrolling-scrollthumb", fadeIn: "fade-in" }, bounceBack = false, EVENTS = { SCROLL_BEFORE_START: "beforeScrollStart", SCROLL_START: "scrollStart", SCROLL_END: "scrollEnd", SCROLL_FLICK: "flick", SCROLL: "scroll" }, // 1px line space from screen edge RADIUS = 174, // position when was last touch start startPosition = 0, // current state of scroll position scrollPosition = 0, lastScrollPosition = 0, moveToPosition = 0, lastRenderedPosition = 0, lastTime = Date.now(), elementStyle = null, maxScrollPosition = 0, // scrolling element scrollingElement = null, childElement = null, // cache of previous overflow style to revert after disable previousOverflow = "", // cache abs function abs = Math.abs, // inform that is touched isTouch = false, isScrollableTarget = false, // direction of scrolling, 0 - mean Y, 1 - mean X direction = 0, // cache of round function round = Math.round, // cache max function max = Math.max, min = Math.min, // Circular scrollbar config CIRCULAR_SCROLL_BAR_SIZE = 60, // degrees CIRCULAR_SCROLL_MIN_THUMB_SIZE = 6, // Scrollbar is placed after scrolled element // that's why normal css values cannot be applied // margin needs to be subtracted from position SCROLL_MARGIN = 11, // ScrollBar variables scrollBar = null, scrollThumb = null, scrollBarPosition = 0, maxScrollBarPosition = 0, circularScrollBar = ns.support.shape.circle, circularScrollThumbSize = CIRCULAR_SCROLL_MIN_THUMB_SIZE, svgScrollBar = null, scrollBarTimeout = null, fromAPI = false, virtualMode = false, snapSize = null, requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame; /** * Shows scrollbar using fadeIn class and sets timeout to hide it when not used */ function fadeInScrollBar() { if (scrollBar) { clearTimeout(scrollBarTimeout); scrollBar.classList.add(classes.fadeIn); scrollBarTimeout = setTimeout(function () { if (scrollBar) { scrollBar.classList.remove(classes.fadeIn); } }, 2000); } } /** * Check that current target is inside scrolling element * @param {HTMLElement} target * @return {boolean} */ function detectTarget(target) { while (target && target !== document) { if (target === scrollingElement) { return true; } target = target.parentElement; } return false; } /** * Handler for touchstart event * @param {Event} event */ function touchStart(event) { var touches = event.touches, touch = touches[0]; isScrollableTarget = detectTarget(event.target); // is is only one touch if (isScrollableTarget && touches.length === 1) { // save current touch point startPosition = direction ? touch.clientX : touch.clientY; // save current time for calculate acceleration on touchend lastTime = Date.now(); eventUtil.trigger(scrollingElement, EVENTS.SCROLL_BEFORE_START, { scrollLeft: direction ? -scrollPosition : 0, scrollTop: direction ? 0 : -scrollPosition, fromAPI: fromAPI }); } } function touchMoveMain(event) { var touches = event.touches, touch = touches[0], // get current position in correct direction clientPosition = direction ? touch.clientX : touch.clientY, scrollLeft = 0, scrollTop = 0; fromAPI = false; // calculate difference between touch start and current position lastScrollPosition = clientPosition - startPosition; if (!bounceBack) { // normalize value to be in bound [0, maxScroll] if (scrollPosition + lastScrollPosition > 0) { lastScrollPosition = -scrollPosition; } if (scrollPosition + lastScrollPosition < -maxScrollPosition) { lastScrollPosition = -maxScrollPosition - scrollPosition; } } if (direction) { scrollLeft = -(scrollPosition + lastScrollPosition); } else { scrollTop = -(scrollPosition + lastScrollPosition); } // trigger event scroll start if it is the first touch move if (!isTouch) { eventUtil.trigger(scrollingElement, EVENTS.SCROLL_START, { scrollLeft: scrollLeft, scrollTop: scrollTop, fromAPI: fromAPI }); } // trigger event scroll eventUtil.trigger(scrollingElement, EVENTS.SCROLL, { scrollLeft: scrollLeft, scrollTop: scrollTop, inBounds: (scrollPosition + lastScrollPosition >= -maxScrollPosition) && (scrollPosition + lastScrollPosition <= 0), fromAPI: fromAPI }); fadeInScrollBar(); } /** * Handler for touchmove event * @param {Event} event */ function touchMove(event) { var touches = event.touches; // if touch start was on scrolled element if (isScrollableTarget) { // if is only one touch if (touches.length === 1) { touchMoveMain(event); } // if this is first touch move if (!isTouch) { // we need start request loop isTouch = true; } requestAnimationFrame(render); } } function touchEndCalculateSpeed(inBounds) { var diffTime = Date.now() - lastTime; if (inBounds && abs(lastScrollPosition / diffTime) > 1) { // if it was fast move, we start animation of scrolling after touch end moveToPosition = max(min(round(scrollPosition + 1000 * lastScrollPosition / diffTime), 0), -maxScrollPosition); if (snapSize) { moveToPosition = snapSize * round(moveToPosition / snapSize); } if (abs(lastScrollPosition / diffTime) > 1) { eventUtil.trigger(scrollingElement, EVENTS.SCROLL_FLICK, { scrollLeft: direction ? -moveToPosition : 0, scrollTop: direction ? 0 : -moveToPosition, fromAPI: fromAPI }); } requestAnimationFrame(moveTo); } else { // touch move was slow if (snapSize) { moveToPosition = snapSize * round(scrollPosition / snapSize); requestAnimationFrame(moveTo); } isTouch = false; } } function touchEndCalculatePosition(inBounds) { if (bounceBack) { if (!inBounds) { // if it was fast move, we start animation of scrolling after touch end if (scrollPosition > 0) { moveToPosition = 0; } else { moveToPosition = -maxScrollPosition; } requestAnimationFrame(moveTo); } } else { // normalize value to be in bound [0, maxScroll] if (scrollPosition < -maxScrollPosition) { scrollPosition = -maxScrollPosition; } if (scrollPosition > 0) { scrollPosition = 0; } } } function touchEndTriggerEvents(details) { eventUtil.trigger(scrollingElement, EVENTS.SCROLL, details); eventUtil.trigger(scrollingElement, EVENTS.SCROLL_END, details); } /** * Handler for touchend event */ function touchEnd() { var inBounds, scrollLeft = 0, scrollTop = 0; // only if the event touchmove was noticed before if (isTouch) { // update state of scrolling scrollPosition += lastScrollPosition; inBounds = (scrollPosition >= -maxScrollPosition) && (scrollPosition <= 0); // calculate speed of touch move touchEndCalculateSpeed(inBounds); touchEndCalculatePosition(inBounds); if (direction) { scrollLeft = -(scrollPosition); } else { scrollTop = -(scrollPosition); } lastScrollPosition = 0; // trigger event scroll touchEndTriggerEvents({ scrollLeft: scrollLeft, scrollTop: scrollTop, inBounds: inBounds, fromAPI: fromAPI }); fadeInScrollBar(); // we stop scrolling isScrollableTarget = false; requestAnimationFrame(render); } } /** * Handler for rotary event * @param {Event} event */ function rotary(event) { var eventDirection = event.detail && event.detail.direction; // update position by snapSize if (eventDirection === "CW") { moveToPosition -= snapSize || 50; } else { moveToPosition += snapSize || 50; } if (snapSize) { moveToPosition = snapSize * round(moveToPosition / snapSize); } if (moveToPosition < -maxScrollPosition) { moveToPosition = -maxScrollPosition; } if (moveToPosition > 0) { moveToPosition = 0; } requestAnimationFrame(moveTo); requestAnimationFrame(render); eventUtil.trigger(scrollingElement, EVENTS.SCROLL_START, { scrollLeft: direction ? -(moveToPosition) : 0, scrollTop: direction ? 0 : -(moveToPosition), fromAPI: false }); event.stopImmediatePropagation(); } function moveToCalculatePosition() { var diffPosition = moveToPosition - scrollPosition, // get absolute value absDiffPosition = abs(diffPosition); if (absDiffPosition > 10) { // we move 10% of difference scrollPosition = round(scrollPosition + diffPosition / 10); requestAnimationFrame(moveTo); } else if (absDiffPosition > 2) { // else if is difference < 10 then we move 50% scrollPosition = round(scrollPosition + diffPosition / 2); requestAnimationFrame(moveTo); } else { // if difference is <=2 then we move to end value and finish loop scrollPosition = moveToPosition; } if (!bounceBack) { // normalize scroll value if (scrollPosition < -maxScrollPosition) { scrollPosition = -maxScrollPosition; } if (scrollPosition > 0) { scrollPosition = 0; } } } /** * Loop function to calculate state in animation after touchend */ function moveTo() { // calculate difference between current position and expected scroll end var scrollLeft = 0, scrollTop = 0; // if difference is big if (scrollingElement) { moveToCalculatePosition(); if (direction) { scrollLeft = -scrollPosition; } else { scrollTop = -scrollPosition; } // trigger event scroll eventUtil.trigger(scrollingElement, EVENTS.SCROLL, { scrollLeft: scrollLeft, scrollTop: scrollTop, inBounds: (scrollPosition >= -maxScrollPosition) && (scrollPosition <= 0), fromAPI: fromAPI }); if (!isTouch) { eventUtil.trigger(scrollingElement, EVENTS.SCROLL_END, { scrollLeft: scrollLeft, scrollTop: scrollTop, fromAPI: fromAPI }); } fadeInScrollBar(); } } function renderScrollbar() { if (circularScrollBar) { polarUtil.updatePosition(svgScrollBar, "." + classes.thumb, { arcStart: scrollBarPosition, arcEnd: scrollBarPosition + circularScrollThumbSize, r: RADIUS }); } else { if (scrollThumb) { if (direction) { scrollThumb.style.transform = "translate(" + scrollBarPosition + "px, 0)"; } else { scrollThumb.style.transform = "translate(0, " + scrollBarPosition + "px)"; } } } } /** * Render loop on request animation frame */ function render() { // calculate ne position of scrolling as sum of last scrolling state + move var newRenderedPosition = scrollPosition + lastScrollPosition; // is position was changed if (newRenderedPosition !== lastRenderedPosition) { // we update styles lastRenderedPosition = newRenderedPosition; if (-newRenderedPosition < maxScrollPosition) { scrollBarPosition = -newRenderedPosition / maxScrollPosition * maxScrollBarPosition; } else { scrollBarPosition = maxScrollBarPosition; } if (scrollBarPosition < 0) { scrollBarPosition = 0; } if (!virtualMode && elementStyle) { elementStyle.transform = direction ? "translate(" + lastRenderedPosition + "px, 0)" : "translate(0, " + lastRenderedPosition + "px)"; } renderScrollbar(); requestAnimationFrame(render); } } function initPosition() { // init internal variables startPosition = 0; scrollPosition = 0; scrollBarPosition = 0; lastScrollPosition = 0; moveToPosition = 0; lastRenderedPosition = 0; lastTime = Date.now(); } /** * Enable JS scrolling on element * @method enable * @param {HTMLElement} element element for scrolling * @param {"x"|"y"} [setDirection="y"] direction of scrolling * @param {boolean} setVirtualMode if is set to true then send event without scroll element * @member ns.util.scrolling */ function enable(element, setDirection, setVirtualMode) { var parentRectangle, contentRectangle; virtualMode = setVirtualMode; bounceBack = false; snapSize = false; if (scrollingElement) { ns.warn("Scrolling exist on another element, first call disable method"); } else { // detect direction direction = (setDirection === "x") ? 1 : 0; // we are creating a container to position transform childElement = document.createElement("div"); // ... and appending all children to it while (element.firstElementChild) { childElement.appendChild(element.firstElementChild); } element.appendChild(childElement); // setting scrolling element scrollingElement = element; // calculate maxScroll parentRectangle = element.getBoundingClientRect(); contentRectangle = childElement.getBoundingClientRect(); // Max scroll position is determined by size of the content - clip window size if (direction) { maxScrollPosition = round(contentRectangle.width - parentRectangle.width); } else { maxScrollPosition = round(contentRectangle.height - parentRectangle.height); } // cache style element elementStyle = childElement.style; initPosition(); // cache current overflow value to restore in disable previousOverflow = window.getComputedStyle(element).getPropertyValue("overflow"); // set overflow hidden element.style.overflow = "hidden"; // add event listeners document.addEventListener("touchstart", touchStart, false); document.addEventListener("touchmove", touchMove, false); document.addEventListener("touchend", touchEnd, false); window.addEventListener("rotarydetent", rotary, true); } } /** * @method disable * @member ns.util.scrolling */ function disable() { disableScrollBar(); // clear event listeners document.removeEventListener("touchstart", touchStart, false); document.removeEventListener("touchmove", touchMove, false); document.removeEventListener("touchend", touchEnd, false); window.removeEventListener("rotarydetent", rotary, true); // after changed page and removed it this element can not exists if (scrollingElement) { scrollingElement.style.overflow = previousOverflow; } elementStyle = null; scrollingElement = null; childElement = null; svgScrollBar = null; } function enableScrollBarCircular() { var arcStartPoint = direction ? 0 : 90; scrollBar.classList.add(classes.circular); svgScrollBar = polarUtil.createSVG(); // create background polarUtil.addArc(svgScrollBar, { arcStart: arcStartPoint - (CIRCULAR_SCROLL_BAR_SIZE / 2), arcEnd: arcStartPoint + (CIRCULAR_SCROLL_BAR_SIZE / 2), classes: classes.path, width: 10, r: RADIUS, linecap: "round" }); // create thumb polarUtil.addArc(svgScrollBar, { referenceDegree: arcStartPoint - (CIRCULAR_SCROLL_BAR_SIZE / 2), arcStart: 0, arcEnd: circularScrollThumbSize, classes: classes.thumb, width: 10, r: RADIUS, linecap: "round" }); scrollBar.appendChild(svgScrollBar); scrollingElement.parentElement.insertBefore(scrollBar, scrollingElement.nextSibling); } function enableScrollBarRectangular(scrollBarStyle) { var boundingRect, childElementRect, scrollThumbStyle, scrollBarWidth = 0, scrollBarHeight = 0; scrollBar.classList.add(classes.direction + "-" + (direction ? "x" : "y")); scrollThumb = document.createElement("div"); scrollThumbStyle = scrollThumb.style; scrollThumb.classList.add(classes.thumb); boundingRect = scrollingElement.getBoundingClientRect(); childElementRect = childElement.getBoundingClientRect(); if (direction) { scrollBarWidth = (boundingRect.width - (2 * SCROLL_MARGIN)); scrollBarStyle.width = scrollBarWidth + "px"; scrollBarStyle.left = (boundingRect.left + SCROLL_MARGIN) + "px"; scrollThumbStyle.transform = "translate3d(" + scrollBarPosition + "px,0,0)"; // Calculate size of the thumb (only useful when enabling after content has size > 0) scrollThumbStyle.width = (scrollBarWidth / childElementRect.width * scrollBarWidth) + "px"; } else { scrollBarHeight = (boundingRect.height - (2 * SCROLL_MARGIN)); scrollBarStyle.height = scrollBarHeight + "px"; scrollBarStyle.top = (boundingRect.top + SCROLL_MARGIN) + "px"; scrollThumbStyle.transform = "translate3d(0," + scrollBarPosition + "px,0)"; // Calculate size of the thumb (only useful when enabling after content has size > 0) scrollThumbStyle.height = (scrollBarHeight / childElementRect.height * scrollBarHeight) + "px"; } scrollBar.appendChild(scrollThumb); scrollingElement.parentElement.insertBefore(scrollBar, scrollingElement.nextSibling); // Get max scrollbar position after appending if (direction) { maxScrollBarPosition = scrollBarWidth - scrollThumb.getBoundingClientRect().width; } else { maxScrollBarPosition = scrollBarHeight - scrollThumb.getBoundingClientRect().height; } } function enableScrollBar() { scrollBar = document.createElement("div"); scrollBar.classList.add(classes.scrollbar); if (circularScrollBar) { enableScrollBarCircular(); } else { enableScrollBarRectangular(scrollBar.style); } } function disableScrollBar() { if (scrollBar) { scrollBar.parentElement.removeChild(scrollBar); scrollBar = null; scrollThumb = null; } } /** * Scroll to give position * @method scrollTo * @param {number} value * @member ns.util.scrolling */ function scrollTo(value) { moveToPosition = value; fromAPI = true; eventUtil.trigger(scrollingElement, EVENTS.SCROLL_BEFORE_START, { scrollLeft: direction ? -scrollPosition : 0, scrollTop: direction ? 0 : -scrollPosition, fromAPI: fromAPI }); requestAnimationFrame(moveTo); render(); } /** * Return scroll position * @method getScrollPosition * @member ns.util.scrolling */ function getScrollPosition() { return -scrollPosition; } /** * Return max scroll position * @method getMaxScroll * @member ns.util.scrolling */ function getMaxScroll() { return maxScrollPosition; } ns.util.scrolling = { getScrollPosition: getScrollPosition, enable: enable, disable: disable, enableScrollBar: enableScrollBar, disableScrollBar: disableScrollBar, scrollTo: scrollTo, /** * Return true is given element is current scrolling element * @method isElement * @param {HTMLElement} element element to check * @return {boolean} * @member ns.util.scrolling */ isElement: function (element) { return scrollingElement === element; }, /** * Update max scrolling position * @method setMaxScroll * @param {number} maxValue * @member ns.util.scrolling */ setMaxScroll: function (maxValue) { var boundingRect = scrollingElement.getBoundingClientRect(), directionDimension = direction ? "width" : "height", directionSize = boundingRect[directionDimension], tempMaxPosition = max(maxValue - directionSize, 0); // Change size of thumb only when necessary if (tempMaxPosition !== maxScrollPosition) { maxScrollPosition = tempMaxPosition || Number.POSITIVE_INFINITY; if (scrollBar) { if (circularScrollBar) { // Calculate new thumb size based on max scrollbar size circularScrollThumbSize = max((directionSize / (maxScrollPosition + directionSize)) * CIRCULAR_SCROLL_BAR_SIZE, CIRCULAR_SCROLL_MIN_THUMB_SIZE); maxScrollBarPosition = CIRCULAR_SCROLL_BAR_SIZE - circularScrollThumbSize; polarUtil.updatePosition(svgScrollBar, "." + classes.thumb, { arcStart: scrollBarPosition, arcEnd: scrollBarPosition + circularScrollThumbSize, r: RADIUS }); } else { directionSize -= 2 * SCROLL_MARGIN; scrollThumb.style[directionDimension] = (directionSize / (maxScrollPosition + directionSize) * directionSize) + "px"; // Cannot use direct value from style here because CSS may override the minimum // size of thumb here maxScrollBarPosition = directionSize - scrollThumb.getBoundingClientRect()[directionDimension]; } } } }, getMaxScroll: getMaxScroll, setSnapSize: function (setSnapSize) { snapSize = setSnapSize; if (snapSize) { maxScrollPosition = snapSize * round(maxScrollPosition / snapSize); } }, setBounceBack: function (setBounceBack) { bounceBack = setBounceBack; } }; }(document, window, ns)); /* global define, ns */ /** * #Scrolling by rotary event * * Tool to enable scrolling by rotary event. * * ##How tu use * * @example *
* Long content to scroll *
* * * * @class ns.util.rotaryScrolling */ (function (document, window, ns) { "use strict"; var rotaryScrolling = {}, element = null, /** * Scroll step * @type {number} */ scrollStep = 40; /** * Handler for rotary event * @param {Event} event Event object */ function rotaryDetentHandler(event) { if (event.detail.direction === "CW") { element.scrollTop += scrollStep; } else { element.scrollTop -= scrollStep; } } /** * Enable Rotary event scrolling * @param {HTMLElement} newElement Base element to scroll * @param {number} newScrollDiff Value of scroll step * @method enable * @memberof ns.util.rotaryScrolling */ function enable(newElement, newScrollDiff) { element = newElement; if (newScrollDiff) { scrollStep = newScrollDiff; } document.addEventListener("rotarydetent", rotaryDetentHandler); } /** * Disable rotary event scrolling * @method disable * @memberof ns.util.rotaryScrolling */ function disable() { scrollStep = 40; document.removeEventListener("rotarydetent", rotaryDetentHandler); } /** * Get value of step which is changed in each rotate * @method getScrollStep * @memberof ns.util.rotaryScrolling * @return {number} */ function getScrollStep() { return scrollStep; } /** * Set value of step which is changed in each rotate * @param {number} newScrollStep New value of scroll step * @method setScrollStep * @memberof ns.util.rotaryScrolling */ function setScrollStep(newScrollStep) { scrollStep = newScrollStep; } rotaryScrolling.enable = enable; rotaryScrolling.disable = disable; rotaryScrolling.setScrollStep = setScrollStep; rotaryScrolling.getScrollStep = getScrollStep; ns.util.rotaryScrolling = rotaryScrolling; }(document, window, ns)); /* * Copyright (c) 2015 Samsung Electronics Co., Ltd * * Licensed under the Flora License, Version 1.1 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://floralicense.org/license/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /*global window, define, ns */ /** * #Date Utility * Object supports work with date and time * @author Krzysztof Antoszek * @class ns.util.date */ (function (ns) { "use strict"; var timeRegex = /([\-0-9.]*)(ms|s)?/i, date = { /** * Convert string time length to miliseconds * Note: this was implemented only for animation package * and the string input should be conforming to css