/* * This file is part of Toolkit. * * Toolkit is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * Toolkit is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU General * Public License along with this program; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA */ "use strict"; /** @namespace TK * * @description This is the namespace of the Toolkit library. * It contains all Toolkit classes and constant. * There are also a couple of utility functions which provide * compatibility for older browsers. */ var TK; (function(w) { var has_class, add_class, remove_class, toggle_class; // IE9 function get_class_name(e) { if (HTMLElement.prototype.isPrototypeOf(e)) { return e.className; } else { return e.getAttribute("class") || ""; } } function set_class_name(e, s) { if (HTMLElement.prototype.isPrototypeOf(e)) { e.className = s; } else { e.setAttribute("class", s); } } if ('classList' in document.createElement("_") && 'classList' in make_svg('text')) { /** * Returns true if the node has the given class. * @param {HTMLElement|SVGElement} node - The DOM node. * @param {string} name - The class name. * @returns {boolean} * @function TK.has_class */ has_class = function (e, cls) { return e.classList.contains(cls); } /** * Adds a CSS class to a DOM node. * * @param {HTMLElement|SVGElement} node - The DOM node. * @param {...*} names - The class names. * @function TK.add_class */ add_class = function (e) { var i; e = e.classList; for (i = 1; i < arguments.length; i++) { var a = arguments[i].split(" "); for (var j = 0; j < a.length; j++) e.add(a[j]); } } /** * Removes a CSS class from a DOM node. * @param {HTMLElement|SVGElement} node - The DOM node. * @param {...*} names - The class names. * @function TK.remove_class */ remove_class = function (e) { var i; e = e.classList; for (i = 1; i < arguments.length; i++) e.remove(arguments[i]); } /** * Toggles a CSS class from a DOM node. * @param {HTMLElement|SVGElement} node - The DOM node. * @param {string} name - The class name. * @function TK.toggle_class */ toggle_class = function (e, cls, cond) { /* The second argument to toggle is not implemented in IE, * so we never use it */ if (arguments.length >= 3) { if (cond) { add_class(e, cls); } else { remove_class(e, cls); } } else e.classList.toggle(cls); }; } else { has_class = function (e, cls) { return get_class_name(e).split(" ").indexOf(cls) !== -1; }; add_class = function (e) { var i, cls; var a = get_class_name(e).split(" "); for (i = 1; i < arguments.length; i++) { cls = arguments[i]; if (a.indexOf(cls) === -1) { a.push(cls); } } set_class_name(e, a.join(" ")); }; remove_class = function(e) { var j, cls, i; var a = get_class_name(e).split(" "); for (j = 1; j < arguments.length; j++) { cls = arguments[j]; i = a.indexOf(cls); if (i !== -1) { do { a.splice(i, 1); i = a.indexOf(cls); } while (i !== -1); } } set_class_name(e, a.join(" ")); }; toggle_class = function(e, cls, cond) { if (arguments.length < 3) { cond = !has_class(e, cls); } if (cond) { add_class(e, cls); } else { remove_class(e, cls); } }; } var data_store; var data; if ('WeakMap' in window) { data = function(e) { var r; if (!data_store) data_store = new window.WeakMap(); r = data_store[e]; if (!r) { data_store[e] = r = {}; } return r; }; } else { data_store = []; var data_keys = []; data = function(e) { if (typeof(e) !== "object") throw("Cannot store data for non-objects."); var k = data_keys.indexOf(e); var r; if (k === -1) { data_keys.push(e); k = data_store.push({}) - 1; } return data_store[k]; }; } var get_style; if ('getComputedStyle' in window) { /** * Returns the computed style of a node. * * @param {HTMLElement|SVGElement} node - The DOM node. * @param {string} property - The CSS property name. * @returns {string} * * @function TK.get_style */ get_style = function(e, style) { return window.getComputedStyle(e).getPropertyValue(style); }; } else { get_style = function(e, style) { return e.currentStyle[style]; }; } var class_regex = /[^A-Za-z0-9_\-]/; function is_class_name (str) { /** * Returns true ii a string could be a class name. * @param {string} string - The string to test * @function TK.is_class_name * @returns {boolean} */ return !class_regex.test(str); } function get_max_time(string) { /** * Returns the maximum value (float) of a comma separated string. It is used * to find the longest CSS animation in a set of multiple animations. * @param {string} string - The comma separated string. * @function TK.get_max_time * @returns {number} * @example * get_max_time(get_style(DOMNode, "animation-duration")); */ var ret = 0, i, tmp, s = string; if (typeof(s) === "string") { s = s.split(","); for (i = 0; i < s.length; i++) { tmp = parseFloat(s[i]); if (tmp > 0) { if (-1 === s[i].search("ms")) tmp *= 1000; if (tmp > ret) ret = tmp; } } } return ret|0; } function get_duration(element) { /** * Returns the longest animation duration of CSS animations and transitions. * @param {HTMLElement} element - The element to evalute the animation duration for. * @function TK.get_duration * @returns {number} */ return Math.max(get_max_time(get_style(element, "animation-duration")) + get_max_time(get_style(element, "animation-delay")), get_max_time(get_style(element, "transition-duration")) + get_max_time(get_style(element, "transition-delay"))); } function get_id(id) { /** * Returns the DOM node with the given ID. Shorthand for document.getElementById. * @param {string} id - The ID to search for * @function TK.get_id * @returns {HTMLElement} */ return document.getElementById(id); } function get_class(cls, element) { /** * Returns all elements as NodeList of a given class name. Optionally limit the list * to all children of a specific DOM node. Shorthand for element.getElementsByClassName. * @param {string} class - The name of the class * @param {DOMNode} element - Limit search to child nodes of this element. Optional. * @returns {NodeList} * @function TK.get_class */ return (element ? element : document).getElementsByClassName(cls); } function get_tag(tag, element) { /** * Returns all elements as NodeList of a given tag name. Optionally limit the list * to all children of a specific DOM node. Shorthand for element.getElementsByTagName. * @param {string} tag - The name of the tag * @param {DOMNode} element - Limit search to child nodes of this element. Optional. * @returns {NodeList} * @function TK.get_tag */ return (element ? element : document).getElementsByTagName(tag); } function element(tag) { /** * Returns a newly created HTMLElement. * @param {string} tag - The type of the element * @param {...object} attributes - Optional mapping of attributes for the new node * @param {...string} class - Optional class name for the new node * @returns HTMLElement * @function TK.element */ var n = document.createElement(tag); var i, v, j; for (i = 1; i < arguments.length; i++) { v = arguments[i]; if (typeof v === "object") { for (var key in v) { if (v.hasOwnProperty(key)) n.setAttribute(key, v[key]); } } else if (typeof v === "string") { add_class(n, v); } else throw("unsupported argument to TK.element"); } return n; } function empty(element) { /** * Removes all child nodes from an HTMLElement. * @param {HTMLElement} element - The element to clean up * @function TK.empty */ while (element.lastChild) element.removeChild(element.lastChild); } function set_text(element, text) { /** * Sets a string as new exclusive text node of an HTMLElement. * @param {HTMLElement} element - The element to clean up * @param {string} text - The string to set as text content * @function TK.set_text */ if (element.childNodes.length === 1 && typeof element.childNodes[0].data === "string") element.childNodes[0].data = text; else element.textContent = text; } function html(string) { /** * Returns a documentFragment containing the result of a string parsed as HTML. * @param {string} html - A string to parse as HTML * @returns {HTMLFragment} * @function TK.html */ /* NOTE: setting innerHTML on a document fragment is not supported */ var e = document.createElement("div"); var f = document.createDocumentFragment(); e.innerHTML = string; while (e.firstChild) f.appendChild(e.firstChild); return f; } function set_content(element, content) { /** * Sets the (exclusive) content of an HTMLElement. * @param {HTMLElement} element - The element receiving the content * @param {String|HTMLElement} content - A string or HTMLElement to set as content * @function TK.set_content */ if (is_dom_node(content)) { empty(element); if (content.parentNode) { TK.warn("set_content: possible reuse of a DOM node. cloning\n"); content = content.cloneNode(true); } element.appendChild(content); } else { set_text(element, content + ""); } } function insert_after(newnode, refnode) { /** * Inserts one HTMLELement after another in the DOM tree. * @param {HTMLElement} newnode - The new node to insert into the DOM tree * @param {HTMLElement} refnode - The reference element to add the new element after * @function TK.insert_after */ if (refnode.parentNode) refnode.parentNode.insertBefore(newnode, refnode.nextSibling); } function insert_before(newnode, refnode) { /** * Inserts one HTMLELement before another in the DOM tree. * @param {HTMLElement} newnode - The new node to insert into the DOM tree * @param {HTMLElement} refnode - The reference element to add the new element before * @function TK.insert_before */ if (refnode.parentNode) refnode.parentNode.insertBefore(newnode, refnode); } function width() { /** * Returns the width of the viewport. * @returns {number} * @function TK.width */ return Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0, document.body.clientWidth || 0); } function height() { /** * Returns the height of the viewport. * @returns {number} * @function TK.height */ return Math.max(document.documentElement.clientHeight, window.innerHeight || 0, document.body.clientHeight || 0); } function scroll_top(element) { /** * Returns the amount of CSS pixels the document or an optional element is scrolled from top. * @param {HTMLElement} element - The element to evaluate. Optional. * @returns {number} * @functionTK.scroll_top */ if (element) return element.scrollTop; return Math.max(document.documentElement.scrollTop || 0, window.pageYOffset || 0, document.body.scrollTop || 0); } function scroll_left(element) { /** * Returns the amount of CSS pixels the document or an optional element is scrolled from left. * @param {HTMLElement} element - The element to evaluate. Optional. * @returns {number} * @functionTK.scroll_left */ if (element) return element.scrollLeft; return Math.max(document.documentElement.scrollLeft, window.pageXOffset || 0, document.body.scrollLeft || 0); } function scroll_all_top(element) { /** * Returns the sum of CSS pixels an element and all of its parents are scrolled from top. * @param {HTMLElement} element - The element to evaluate * @returns {number} * @functionTK.scroll_all_top */ var v = 0; while (element = element.parentNode) v += element.scrollTop || 0; return v; } function scroll_all_left(element) { /** * Returns the sum of CSS pixels an element and all of its parents are scrolled from left. * @param {HTMLElement} element - The element to evaluate * @returns {number} * @functionTK.scroll_all_left */ var v = 0; while (element = element.parentNode) v += element.scrollLeft || 0; return v; } function position_top(e, rel) { /** * Returns the position from top of an element in relation to the document * or an optional HTMLElement. Scrolling of the parent is taken into account. * @param {HTMLElement} element - The element to evaluate * @param {HTMLElement} relation - The element to use as reference. Optional. * @returns {number} * @function TK.position_top */ var top = parseInt(e.getBoundingClientRect().top); var f = fixed(e) ? 0 : scroll_top(); return top + f - (rel ? position_top(rel) : 0); } function position_left(e, rel) { /** * Returns the position from the left of an element in relation to the document * or an optional HTMLElement. Scrolling of the parent is taken into account. * @param {HTMLElement} element - The element to evaluate * @param {HTMLElement} relation - The element to use as reference. Optional. * @returns {number} * @function TK.position_left */ var left = parseInt(e.getBoundingClientRect().left); var f = fixed(e) ? 0 : scroll_left(); return left + f - (rel ? position_left(rel) : 0); } function fixed(e) { /** * Returns if an element is positioned fixed to the viewport * @param {HTMLElement} element - the element to evaluate * @returns {boolean} * @function TK.fixed */ return getComputedStyle(e).getPropertyValue("position") === "fixed"; } function outer_width(element, margin, width) { /** * Gets or sets the outer width of an element as CSS pixels. The box sizing * method is taken into account. * @param {HTMLElement} element - the element to evaluate / manipulate * @param {boolean} margin - Determine if margin is included * @param {number} width - If defined the elements outer width is set to this value * @returns {number} * @functionTK.outer_width */ var m = 0; if (margin) { var cs = getComputedStyle(element); m += parseFloat(cs.getPropertyValue("margin-left")); m += parseFloat(cs.getPropertyValue("margin-right")); } if (width !== void(0)) { if (box_sizing(element) === "content-box") { var css = css_space(element, "padding", "border"); width -= css.left + css.right; } width -= m; // TODO: fixme if (width < 0) return 0; element.style.width = width + "px"; return width; } else { var w = element.getBoundingClientRect().width; return w + m; } } function outer_height(element, margin, height) { /** * Gets or sets the outer height of an element as CSS pixels. The box sizing * method is taken into account. * @param {HTMLElement} element - the element to evaluate / manipulate * @param {boolean} margin - Determine if margin is included * @param {number} height - If defined the elements outer height is set to this value * @returns {number} * @functionTK.outer_height */ var m = 0; if (margin) { var cs = getComputedStyle(element, null); m += parseFloat(cs.getPropertyValue("margin-top")); m += parseFloat(cs.getPropertyValue("margin-bottom")); } if (height !== void(0)) { if (box_sizing(element) === "content-box") { var css = css_space(element, "padding", "border"); height -= css.top + css.bottom; } height -= m; // TODO: fixme if (height < 0) return 0; element.style.height = height + "px"; return height; } else { var h = element.getBoundingClientRect().height; return h + m; } } function inner_width(element, width) { /** * Gets or sets the inner width of an element as CSS pixels. The box sizing * method is taken into account. * @param {HTMLElement} element - the element to evaluate / manipulate * @param {number} width - If defined the elements inner width is set to this value * @returns {number} * @functionTK.inner_width */ var css = css_space(element, "padding", "border"); var x = css.left + css.right; if (width !== void(0)) { if (box_sizing(element) === "border-box") width += x; // TODO: fixme if (width < 0) return 0; element.style.width = width + "px"; return width; } else { var w = element.getBoundingClientRect().width; return w - x; } } function inner_height(element, height) { /** * Gets or sets the inner height of an element as CSS pixels. The box sizing * method is taken into account. * @param {HTMLElement} element - the element to evaluate / manipulate * @param {number} height - If defined the elements outer height is set to this value * @returns {number} * @functionTK.inner_height */ var css = css_space(element, "padding", "border"); var y = css.top + css.bottom; if (height !== void(0)) { if (box_sizing(element) === "border-box") height += y; // TODO: fixme if (height < 0) return 0; element.style.height = height + "px"; return height; } else { var h = element.getBoundingClientRect().height; return h - y; } } function box_sizing(element) { /** * Returns the box-sizing method of an HTMLElement. * @param {HTMLElement} element - The element to evaluate * @returns {string} * @functionTK.box_sizing */ var cs = getComputedStyle(element, null); if (cs.getPropertyValue("box-sizing")) return cs.getPropertyValue("box-sizing"); if (cs.getPropertyValue("-moz-box-sizing")) return cs.getPropertyValue("-moz-box-sizing"); if (cs.getPropertyValue("-webkit-box-sizing")) return cs.getPropertyValue("-webkit-box-sizing"); if (cs.getPropertyValue("-ms-box-sizing")) return cs.getPropertyValue("-ms-box-sizing"); if (cs.getPropertyValue("-khtml-box-sizing")) return cs.getPropertyValue("-khtml-box-sizing"); } function css_space(element) { /** * Returns the overall spacing around an HTMLElement of all given attributes. * @param {HTMLElement} element - The element to evaluate * @param{...string} The CSS attributes to take into account * @returns {object} An object with the members "top", "bottom", "lfet", "right" * @function TK.css_space * @example * TK.css_space(element, "padding", "border"); */ var cs = getComputedStyle(element, null); var o = {top: 0, right: 0, bottom: 0, left: 0}; var a; var s; for (var i = 1; i < arguments.length; i++) { a = arguments[i]; for (var p in o) { if (o.hasOwnProperty(p)) { s = a + "-" + p; if (a === "border") s += "-width"; } o[p] += parseFloat(cs.getPropertyValue(s)); } } return o; } var number_attributes = [ "animation-iteration-count", "column-count", "flex-grow", "flex-shrink", "opacity", "order", "z-index" ] function set_styles(elem, styles) { /** * Set multiple CSS styles onto an HTMLElement. * @param {HTMLElement} element - the element to add the styles to * @param {object} styles - A mapping containing all styles to add * @function TK.set_styles * @example * TK.set_styles(element, {"width":"100px", "height":"100px"}); */ var key, v; var s = elem.style; for (key in styles) if (styles.hasOwnProperty(key)) { v = styles[key]; if (typeof v !== "number" && !v) { delete s[key]; } else { if (typeof v === "number" && number_attributes.indexOf(key) == -1) { TK.warn("TK.set_styles: use of implicit px conversion is _deprecated_ and will be removed in the future."); v = v.toFixed(3) + "px"; } s[key] = v; } } } function set_style(e, style, value) { /** * Sets a single CSS style onto an HTMLElement. It is used to autimatically * add "px" to numbers and trim them to 3 digits at max. DEPRECATED! * @param {HTMLElement} element - The element to set the style to * @param {string} style - The CSS attribute to set * @param {string|number} value - The value to set the CSS attribute to * @function TK.set_style */ if (typeof value === "number") { /* By default, numbers are transformed to px. I believe this is a very _dangerous_ default * behavior, because it breaks other number like properties _without_ warning. * this is now deprecated. */ TK.warn("TK.set_style: use of implicit px conversion is _deprecated_ and will be removed in the future."); value = value.toFixed(3) + "px"; } e.style[style] = value; } var _id_cnt = 0; function unique_id() { /** * Generate a unique ID string. * @returns {string} * @function TK.unique_id */ var id; do { id = "tk-" + _id_cnt++; } while (document.getElementById(id)); return id; }; /** * Generates formatting functions from sprintf-style format strings. * This is generally faster when the same format string is used many times. * * @returns {function} A formatting function. * @param {string} fmt - The format string. * @function TK.FORMAT * @example * var f = TK.FORMAT("%.2f Hz"); * @see TK.sprintf */ function FORMAT(fmt) { var args = []; var s = "return "; var res; var last = 0; var argnum = 0; var precision; var regexp = /%(\.\d+)?([bcdefgosO%])/g; var argname; while (res = regexp.exec(fmt)) { if (argnum) s += "+"; s += JSON.stringify(fmt.substr(last, regexp.lastIndex - res[0].length - last)); s += "+"; argname = "a"+argnum; if (args.indexOf(argname) === -1) args.push(argname); if (argnum+1 < arguments.length) { argname = "(" + sprintf(arguments[argnum+1].replace("%", "%s"), argname) + ")"; } switch (res[2].charCodeAt(0)) { case 100: // d s += "("+argname+" | 0)"; break; case 102: // f if (res[1]) { // length qualifier precision = parseInt(res[1].substr(1)); s += "(+"+argname+").toFixed("+precision+")"; } else { s += "(+"+argname+")"; } break; case 115: // s s += argname; break; case 37: s += "\"%\""; argnum--; break; case 79: case 111: s += "JSON.stringify("+argname+")"; break; default: throw("unknown format:"+res[0]); break; } argnum++; last = regexp.lastIndex; } if (argnum) s += "+"; s += JSON.stringify(fmt.substr(last)); return new Function(args, s); } /** * Formats the arguments according to a given format string. * @returns {function} A formatting function. * @param {string} fmt - The format string. * @param {...*} args - The format arguments. * @function TK.sprintf * @example * TK.sprintf("%d Hz", 440); * @see TK.FORMAT */ function sprintf(fmt) { var arg_len = arguments.length; var i, last_fmt; var c, arg_num = 1; var ret = []; var precision, s; var has_precision = false; for (last_fmt = 0; -1 !== (i = fmt.indexOf("%", last_fmt)); last_fmt = i+1) { if (last_fmt < i) { ret.push(fmt.substring(last_fmt, i)); } i ++; if (has_precision = (fmt.charCodeAt(i) === 46 /* '.' */)) { i++; precision = parseInt(fmt.substr(i)); while ((c = fmt.charCodeAt(i)) >= 48 && c <= 57) i++; } c = fmt.charCodeAt(i); if (c === 37) { ret.push("%"); continue; } s = arguments[arg_num++]; switch (fmt.charCodeAt(i)) { case 102: /* f */ s = +s; if (has_precision) { s = s.toFixed(precision); } break; case 100: /* d */ s = s|0; break; case 115: /* s */ break; case 79: /* O */ case 111: /* o */ s = JSON.stringify(s); break; default: throw("Unsupported format."); } ret.push(s); last_fmt = i+1; } if (last_fmt < fmt.length) { ret.push(fmt.substring(last_fmt, fmt.length)); } return ret.join(""); } function escapeHTML(text) { /** * Escape an HTML string to be displayed as text. * @param {string} html - The HTML code to escape * @returns {string} * @function TK.escapeHTML */ var map = { '&' : '&', '<' : '<', '>' : '>', '"' : '"', "'" : ''' }; return text.replace(/[&<>"']/g, function(m) { return map[m]; }); } function is_touch() { /** * Check if a device is touch-enabled. * @returns {boolean} * @function TK.is_touch */ return 'ontouchstart' in window // works on most browsers || 'onmsgesturechange' in window; // works on ie10 } function os() { /** * Return the operating system * @returns {string} * @function TK.os */ var ua = navigator.userAgent.toLowerCase(); if (ua.indexOf("android") > -1) return "Android"; if (/iPad/i.test(ua) || /iPhone OS 3_1_2/i.test(ua) || /iPhone OS 3_2_2/i.test(ua)) return "iOS"; if ((ua.match(/iPhone/i)) || (ua.match(/iPod/i))) return "iOS"; if (navigator.appVersion.indexOf("Win")!=-1) return "Windows"; if (navigator.appVersion.indexOf("Mac")!=-1) return "MacOS"; if (navigator.appVersion.indexOf("X11")!=-1) return "UNIX"; if (navigator.appVersion.indexOf("Linux")!=-1) return "Linux"; } function make_svg(tag, args) { /** * Creates and returns an SVG child element. * @param {string} tag - The element to create as string, e.g. "line" or "g" * @param {object} arguments - The attributes to set onto the element * @returns {SVGElement} */ var el = document.createElementNS('http://www.w3.org/2000/svg', "svg:" + tag); for (var k in args) el.setAttribute(k, args[k]); return el; } function seat_all_svg(parent) { /** * Searches for all SVG that don't have the class "svg-fixed" and re-positions them * in order to avoid blurry lines. * @param {HTMLElement} parent - If set only children of parent are searched * @function TK.seat_all_svg */ var a = get_tag("svg", parent); for (var i = 0; i < a.length; i++) { if (!has_class(a[i], "svg-fixed")) seat_svg(a[i]); } } function seat_svg(e) { /** * Move SVG for some sub-pixel if their position in viewport is not int. * @param {SVGElement} svg - The SVG to manipulate * @function TK.seat_svg */ if (retrieve(e, "margin-left") === null) { store(e, "margin-left", parseFloat(get_style(e, "margin-left"))); } else { e.style.marginLeft = retrieve(e, "margin-left") || 0; } var l = parseFloat(retrieve(e, "margin-left")) || 0; var b = e.getBoundingClientRect(); var x = b.left % 1; if (x) { if (x < 0.5) l -= x; else l += (1 - x); } if (e.parentElement && get_style(e.parentElement, "text-align") === "center") l += 0.5; e.style.marginLeft = l + "px"; //console.log(l); if (retrieve(e, "margin-top") === null) { store(e, "margin-top", parseFloat(get_style(e, "margin-top"))); } else { e.style.marginTop = retrieve(e, "margin-top") || 0; } var t = parseFloat(retrieve(e, "margin-top") || 0); var b = e.getBoundingClientRect(); var y = b.top % 1; if (y) { if (x < 0.5) t -= y; else t += (1 - y); } //console.log(t); e.style.marginTop = t + "px"; } function delayed_callback(timeout, cb, once) { var tid; var args; var my_cb = function() { tid = null; cb.apply(this, args); }; return function() { args = Array.prototype.slice.call(arguments); if (tid) window.clearTimeout(tid); else if (once) once(); tid = window.setTimeout(my_cb, timeout); }; } function store(e, key, val) { /** * Store a piece of data in an object. * @param {object} object - The object to store the data * @param {string} key - The key to identify the memory * @param {*} data - The data to store * @function TK.store */ data(e)[key] = val; } function retrieve(e, key) { /** * Retrieve a piece of data from an object. * @param {object} object - The object to retrieve the data from * @param {string} key - The key to identify the memory * @function TK.retrieve * @returns {*} */ return data(e)[key]; } function merge(dst) { /** * Merge two or more objects. The second and all following objects * will be merged into the first one. * @param {...object} object - The objects to merge * @returns {object} * @function TK.merge */ //console.log("merging", src, "into", dst); var key, i, src; for (i = 1; i < arguments.length; i++) { src = arguments[i]; for (key in src) { dst[key] = src[key]; } } return dst; } function object_and(orig, filter) { /** * Filter an object via white list. * @param {object} origin - The object to filter * @param {object} filter - The object containing the white list * @returns {object} The filtered result * @function TK.object_and */ var ret = {}; for (var key in orig) { if (filter[key]) ret[key] = orig[key]; } return ret; } function object_sub(orig, filter) { /** * Filter an object via black list. * @param {object} origin - The object to filter * @param {object} filter - The object containing the black list * @returns {object} The filtered result * @function TK.object_sub */ var ret = {}; for (var key in orig) { if (!filter[key]) ret[key] = orig[key]; } return ret; } function to_array(collection) { /** * Convert any collection (like NodeList) into an array. * @param {collection} collection - The collection to convert into an array * @returns {array} * @functionTK.to_array */ var ret = new Array(collection.length); var i; for (i = 0; i < ret.length; i++) { ret[i] = collection[i]; } return ret; } function is_dom_node(o) { /* this is broken for SVG */ return typeof o === "object" && o instanceof Node; } // NOTE: IE9 will throw errors when console is used without debugging tools. In general, it // is better for log/warn to silently fail in case of error. This unfortunately means that // warnings might be lost, but probably better than having diagnostics and debugging code // break an application /** * Generates an error to the JavaScript console. This is virtually identical to console.error, however * it can safely be used in browsers which do not support it. * * @param {...*} args * @function TK.error */ function error() { if (!window.console) return; try { window.console.error.apply(window.console, arguments); } catch(e) {} } /** * Generates a warning to the JavaScript console. This is virtually identical to console.warn, however * it can safely be used in browsers which do not support it. * * @param {...*} args * @function TK.warn */ function warn() { if (!window.console) return; try { window.console.warn.apply(window.console, arguments); } catch(e) {} } /** * Generates a log message to the JavaScript console. This is virtually identical to console.log, however * it can safely be used in browsers which do not support it. * * @param {...*} args * @function TK.log */ function log() { if (!window.console) return; try { window.console.log.apply(window.console, arguments); } catch(e) {} } function print_widget_tree(w, depth) { if (!depth) depth = 0; var print = function(fmt) { var extra = Array.prototype.slice.call(arguments, 1); if (depth) fmt = nchars(depth, " ") + fmt; var args = [ fmt ]; log.apply(TK, args.concat(extra)); }; var nchars = function(n, c) { var ret = new Array(n); for (var i = 0; i < n; i++) ret[i] = c; return ret.join(""); }; var C = w.children; var nchildren = C ? C.length : 0; var state = [ ]; state.push(w._drawn ? "show" : "hide"); if (w.needs_redraw) state.push("redraw"); if (w.needs_resize) state.push("resize"); print("%s (%s, children: %o)", w._class, state.join(" "), nchildren); if (C) { for (var i = 0; i < C.length; i++) print_widget_tree(C[i], depth+1); } } /* Detection and handling for passive event handler support. * The chrome team has threatened to make passive event handlers * the default in a future version. To make sure that this does * not break our code, we explicitly register 'active' event handlers * for most cases. */ /* generic code, supports node arrays */ function add_event_listener(e, type, cb, options) { if (Array.isArray(e)) { for (var i = 0; i < e.length; i++) e[i].addEventListener(type, cb, options); } else e.addEventListener(type, cb, options); } function remove_event_listener(e, type, cb, options) { if (Array.isArray(e)) { for (var i = 0; i < e.length; i++) e[i].removeEventListener(type, cb, options); } else e.removeEventListener(type, cb, options); } /* Detect if the 'passive' option is supported. * This code has been borrowed from mdn */ var passiveSupported = false; try { var options = Object.defineProperty({}, "passive", { get: function() { passiveSupported = true; } }); window.addEventListener("test", null, options); window.removeEventListener("test", null); } catch(err) {} var active_options, passive_options; if (passiveSupported) { active_options = { passive: false }; passive_options = { passive: true }; } else { active_options = false; passive_options = false; } function add_active_event_listener(e, type, cb) { add_event_listener(e, type, cb, active_options); } function remove_active_event_listener(e, type, cb) { remove_event_listener(e, type, cb, active_options); } function add_passive_event_listener(e, type, cb) { add_event_listener(e, type, cb, passive_options); } function remove_passive_event_listener(e, type, cb) { remove_event_listener(e, type, cb, passive_options); } TK = w.toolkit = w.TK = { // ELEMENTS S: new w.DOMScheduler(), is_dom_node: is_dom_node, get_id: get_id, get_class: get_class, get_tag: get_tag, element : element, empty: empty, set_text : set_text, set_content : set_content, has_class : has_class, remove_class : remove_class, add_class : add_class, toggle_class : toggle_class, is_class_name : is_class_name, insert_after: insert_after, insert_before: insert_before, // WINDOW width: width, height: height, // DIMENSIONS scroll_top: scroll_top, scroll_left: scroll_left, scroll_all_top: scroll_all_top, scroll_all_left: scroll_all_left, position_top: position_top, position_left: position_left, fixed: fixed, outer_width : outer_width, outer_height : outer_height, inner_width: inner_width, inner_height: inner_height, box_sizing: box_sizing, css_space: css_space, // CSS AND CLASSES set_styles : set_styles, set_style: set_style, get_style: get_style, get_duration: get_duration, // STRINGS unique_id: unique_id, FORMAT : FORMAT, sprintf : sprintf, html : html, escapeHTML : escapeHTML, // OS AND BROWSER CAPABILITIES is_touch: is_touch, os: os, browser: function () { /** * Returns the name of the browser * @returns {string} * @function TK.browser */ var ua = navigator.userAgent, tem, M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || []; if (/trident/i.test(M[1])) { tem = /\brv[ :]+(\d+)/g.exec(ua) || []; return { name : 'IE', version : (tem[1]||'') }; } if (M[1] === 'Chrome') { tem = ua.match(/\bOPR\/(\d+)/) if (tem!=null) return { name : 'Opera', version : tem[1] }; } M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?']; if ((tem = ua.match(/version\/(\d+)/i)) !== null) { M.splice(1, 1, tem[1]); } return { name : M[0], version : M[1] }; }(), supports_transform: function () { return 'transform' in document.createElement("div").style; }(), // SVG make_svg: make_svg, seat_all_svg: seat_all_svg, seat_svg: seat_svg, // EVENTS delayed_callback : delayed_callback, add_event_listener: add_active_event_listener, remove_event_listener: remove_active_event_listener, add_passive_event_listener: add_passive_event_listener, remove_passive_event_listener: remove_passive_event_listener, // OTHER data: data, store: store, retrieve: retrieve, merge: merge, object_and: object_and, object_sub: object_sub, to_array: to_array, warn: warn, error: error, log: log, assign_warn: function(a) { for (var i = 1; i < arguments.length; i++) { var b = arguments[i]; for (var key in b) if (b.hasOwnProperty(key)) { if (a[key] === b[key]) { TK.warn("overwriting identical", key, "(", a[key], ")"); } else if (a[key]) { TK.warn("overwriting", key, "(", a[key], "vs", b[key], ")"); } a[key] = b[key]; } } return a; }, print_widget_tree: print_widget_tree, }; // POLYFILLS if (Array.isArray === void(0)) { Array.isArray = function(obj) { return Object.prototype.toString.call(obj) === '[object Array]'; } }; if (Object.assign === void(0)) { Object.defineProperty(Object, 'assign', { enumerable: false, configurable: true, writable: true, value: function(target) { 'use strict'; if (target === void(0) || target === null) { throw new TypeError('Cannot convert first argument to object'); } var to = Object(target); for (var i = 1; i < arguments.length; i++) { var nextSource = arguments[i]; if (nextSource === void(0) || nextSource === null) { continue; } nextSource = Object(nextSource); var keysArray = Object.keys(Object(nextSource)); for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) { var nextKey = keysArray[nextIndex]; var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey); if (desc !== void(0) && desc.enumerable) { to[nextKey] = nextSource[nextKey]; } } } return to; } }); } if (!('remove' in Element.prototype)) { Element.prototype.remove = function() { this.parentNode.removeChild(this); }; } })(this);