@qooxdoo/framework
Version:
The JS Framework for Coders
497 lines (436 loc) • 17.4 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2012 1&1 Internet AG, Germany, http://www.1und1.de
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Martin Wittemann (wittemann)
************************************************************************ */
/**
* The Core module's responsibility is to query the DOM for elements and offer
* these elements as a collection. The Core module itself does not offer any methods to
* work with the collection. These methods are added by the other included modules,
* such as Manipulating or Attributes.
*
* Core also provides the plugin API which allows modules to attach either
* static functions to the global <code>q</code> object or define methods on the
* collection it returns.
*
* By default, the core module is assigned to a global module named <code>q</code>.
* In case <code>q</code> is already defined, the name <code>qxWeb</code>
* is used instead.
*
* For further details, take a look at the documentation in the
* <a href='http://manual.qooxdoo.org/${qxversion}/pages/website.html' target='_blank'>user manual</a>.
*
* @ignore(q)
*
* @group (Core)
*/
qx.Bootstrap.define("qxWeb", {
extend : qx.type.BaseArray,
statics : {
// internal storage for all initializers
__init : [],
// internal reference to the used qx namespace
$$qx : qx,
/**
* Internal helper to initialize collections.
*
* @param arg {var} An array of Elements which will
* be initialized as {@link q}. All items in the array which are not
* either a window object, a DOM element node or a DOM document node will
* be ignored.
* @param clazz {Class} The class of the new collection.
* @return {q} A new initialized collection.
*/
$init : function(arg, clazz) {
// restore widget instance
if (arg.length && arg.length == 1 && arg[0] && arg[0].$widget instanceof qxWeb) {
return arg[0].$widget;
}
var clean = [];
for (var i = 0; i < arg.length; i++) {
// check for node or window object
var isNode = !!(arg[i] && (arg[i].nodeType === 1 ||
arg[i].nodeType === 9 || arg[i].nodeType === 11));
if (isNode) {
clean.push(arg[i]);
continue;
}
var isWindow = !!(arg[i] && arg[i].history && arg[i].location && arg[i].document);
if (isWindow) {
clean.push(arg[i]);
}
}
if (arg[0] && arg[0].getAttribute && arg[0].getAttribute("data-qx-class") && clean.length < 2) {
clazz = qx.Bootstrap.getByName(arg[0].getAttribute("data-qx-class")) || clazz;
}
var col = qx.lang.Array.cast(clean, clazz);
for (var i=0; i < qxWeb.__init.length; i++) {
qxWeb.__init[i].call(col);
}
return col;
},
/**
* This is an API for module development and can be used to attach new methods
* to {@link q}.
*
* @param module {Map} A map containing the methods to attach.
* @param override {Boolean?false} Force to override
*/
$attach : function(module, override) {
for (var name in module) {
if (qxWeb.prototype[name] != undefined && Array.prototype[name] == undefined && override !== true) {
if (qx.core.Environment.get("qx.debug")) {
throw new Error("Method '" + name + "' already available.");
}
} else {
qxWeb.prototype[name] = module[name];
}
}
},
/**
* This is an API for module development and can be used to attach new methods
* to {@link q}.
*
* @param module {Map} A map containing the methods to attach.
* @param override {Boolean?false} Force to override
*/
$attachStatic : function(module, override) {
for (var name in module) {
if (qx.core.Environment.get("qx.debug")) {
if (qxWeb[name] != undefined && override !== true) {
throw new Error("Method '" + name + "' already available as static method.");
}
}
qxWeb[name] = module[name];
}
},
/**
* This is an API for module development and can be used to attach new methods
* to {@link q} during runtime according to the following conventions:
*
* Public members of the module are attached to the collection similar to
* <code>qxWeb.$attach</code>. Members beginning with '$' or '_' are ignored.
*
* Statics of the module are attached to {@link q} similar to
* <code>qxWeb.$attachStatic</code>. Statics beginning with '$' or '_', as well as constants
* (all upper case) and some qooxdoo-internal statics of the module are ignored.
*
*
* If more fine-grained control outside if these conventions is needed,
* simply use <code>qxWeb.$attach</code> or <code>qxWeb$attachStatic</code>.
*
* @param clazz {Object} the qooxdoo class definition calling this method.
* @param staticsNamespace {String?undefined} Specifies the namespace under which statics are attached to {@link q}.
*/
$attachAll : function(clazz, staticsNamespace) {
// members
for (var name in clazz.members) {
if (name.indexOf("$") !== 0 && name.indexOf("_") !== 0)
qxWeb.prototype[name] = clazz.members[name];
}
// statics
var destination;
if (staticsNamespace != null) {
qxWeb[staticsNamespace] = qxWeb[staticsNamespace] || {};
destination = qxWeb[staticsNamespace];
} else {
destination = qxWeb;
}
for (var name in clazz.statics) {
if (name.indexOf("$") !== 0 && name.indexOf("_") !== 0 && name !== "name" && name !== "basename" &&
name !== "classname" && name !== "toString" && name !== name.toUpperCase())
destination[name] = clazz.statics[name];
}
},
/**
* This is an API for module development and can be used to attach new initialization
* methods to {@link q} which will be called when a new collection is
* created.
*
* @param init {Function} The initialization method for a module.
*/
$attachInit : function(init) {
this.__init.push(init);
},
/**
* Define a new class using the qooxdoo class system.
*
* @param name {String?} Name of the class. If null, the class will not be
* attached to a namespace.
* @param config {Map ? null} Class definition structure. The configuration map has the following keys:
* <table>
* <thead>
* <tr><th>Name</th><th>Type</th><th>Description</th></tr>
* </thead>
* <tr><td>extend</td><td>Class</td><td>The super class the current class inherits from.</td></tr>
* <tr><td>construct</td><td>Function</td><td>The constructor of the class.</td></tr>
* <tr><td>statics</td><td>Map</td><td>Map of static values / functions of the class.</td></tr>
* <tr><td>members</td><td>Map</td><td>Map of instance members of the class.</td></tr>
* <tr><td>defer</td><td>Function</td><td>Function that is called at the end of
* processing the class declaration.</td></tr>
* </table>
*
* <strong>Important hint:</strong> Please do not initialize reference types
* (<code>Object</code> or <code>Array</code>) directly inside the <strong>members</strong> section.
* Only list and initialize it with <code>null</code>. Initialize your reference type inside the
* <code>construct</code>. Check out one of the samples below to get the idea.
* @return {Function} The defined class.
*/
define : function(name, config) {
if (config == undefined) {
config = name;
name = null;
}
return qx.Bootstrap.define.call(qx.Bootstrap, name, config);
}
},
/**
* Primary usage:
* Accepts a selector string and returns a collection of found items. The optional context
* element can be used to reduce the amount of found elements to children of the
* context element. If the context object is a collection, its first item is used.
*
* Secondary usage:
* Creates a collection from an existing DOM element, document node or window object
* (or an Array containing any such objects)
*
* <a href="http://sizzlejs.com/" target="_blank">Sizzle</a> is used as selector engine.
* Check out the <a href="https://github.com/jquery/sizzle/wiki/Sizzle-Home" target="_blank">documentation</a>
* for more details.
*
* @param selector {String|Element|Document|Window|Array} Valid selector (CSS3 + extensions),
* window object, DOM element/document or Array of DOM Elements.
* @param context {Element|q} Only the children of this element are considered.
* @return {q} A collection of DOM elements.
*/
construct : function(selector, context) {
if (!selector && this instanceof qxWeb) {
return this;
}
if (!selector) {
selector = [];
}
else if (qx.Bootstrap.isString(selector)) {
if (context instanceof qxWeb && context.length != 0) {
context = context[0];
}
if (context instanceof qxWeb) {
selector = [];
} else {
selector = qx.bom.Selector.query(selector, context);
}
}
else if ((selector.nodeType === 1 || selector.nodeType === 9 ||
selector.nodeType === 11) ||
(selector.history && selector.location && selector.document))
{
selector = [selector];
}
return qxWeb.$init(selector, qxWeb);
},
members : {
/**
* Gets a new collection containing only those elements that passed the
* given filter. This can be either a selector expression or a filter
* function.
*
* @param selector {String|Function} Selector expression or filter function
* @return {q} New collection containing the elements that passed the filter
*/
filter : function(selector) {
if (qx.lang.Type.isFunction(selector)) {
return qxWeb.$init(Array.prototype.filter.call(this, selector), this.constructor);
}
return qxWeb.$init(qx.bom.Selector.matches(selector, this), this.constructor);
},
/**
* Recreates a collection which is free of all duplicate elements from the original.
*
* @return {q} Returns a copy with no duplicates
*/
unique : function() {
var unique = qx.lang.Array.unique(this);
return qxWeb.$init(unique, this.constructor);
},
/**
* Returns a copy of the collection within the given range.
*
* @param begin {Number} The index to begin.
* @param end {Number?} The index to end.
* @return {q} A new collection containing a slice of the original collection.
*/
slice : function(begin, end) {
// Old IEs return an empty array if the second argument is undefined
// check 'end' explicit for "undefined" [BUG #7322]
if (end !== undefined) {
return qxWeb.$init(Array.prototype.slice.call(this, begin, end), this.constructor);
}
return qxWeb.$init(Array.prototype.slice.call(this, begin), this.constructor);
},
/**
* Removes the given number of items and returns the removed items as a new collection.
* This method can also add items. Take a look at the
* <a href='https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/splice' target='_blank'>documentation of MDN</a> for more details.
*
* @param index {Number} The index to begin.
* @param howMany {Number} the amount of items to remove.
* @param varargs {var} As many items as you want to add.
* @return {q} A new collection containing the removed items.
*/
splice : function(index , howMany, varargs) {
return qxWeb.$init(Array.prototype.splice.apply(this, arguments), this.constructor);
},
/**
* Returns a new collection containing the modified elements. For more details, check out the
* <a href='https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/map' target='_blank'>MDN documentation</a>.
*
* @param callback {Function} Function which produces the new element.
* @param thisarg {var} Context of the callback.
* @return {q} New collection containing the elements that passed the filter
*/
map : function(callback, thisarg) {
return qxWeb.$init(Array.prototype.map.apply(this, arguments), qxWeb);
},
/**
* Returns a copy of the collection including the given elements.
*
* @param varargs {var} As many items as you want to add.
* @return {q} A new collection containing all items.
*/
concat : function(varargs) {
var clone = Array.prototype.slice.call(this, 0);
for (var i=0; i < arguments.length; i++) {
if (arguments[i] instanceof qxWeb) {
clone = clone.concat(Array.prototype.slice.call(arguments[i], 0));
} else {
clone.push(arguments[i]);
}
}
return qxWeb.$init(clone, this.constructor);
},
/**
* Returns the index of the given element within the current
* collection or -1 if the element is not in the collection
* @param elem {Element|Element[]|qxWeb} Element or collection of elements
* @param fromIndex {Integer} The index to start the search at
* @return {Number} The element's index
*/
indexOf : function(elem, fromIndex) {
if (!elem) {
return -1;
}
if (!fromIndex) {
fromIndex = 0;
}
if (typeof fromIndex !== "number") {
return -1;
}
if (fromIndex < 0) {
fromIndex = this.length + fromIndex;
if (fromIndex < 0) {
fromIndex = 0;
}
}
if (qx.lang.Type.isArray(elem)) {
elem = elem[0];
}
for (var i = fromIndex, l = this.length; i<l; i++) {
if (this[i] === elem) {
return i;
}
}
return -1;
},
/**
* Calls the browser's native debugger to easily allow debugging within
* chained calls.
*
* Unlike the <a href="#.logThis">logThis</a> method this implementation blocks the further processing.
*
* @return {q} The collection for chaining
* @ignore(debugger)
*/
debug : function() {
if (qx.core.Environment.get("qx.debug")) {
debugger;
}
return this;
},
/**
* Logs information about the current collection, its DOM elements and the
* length. Very useful during development to easily check the current state of
* your collection and avoid common pitfalls like an empty collection.
*
* Unlike the <a href="#.debug">debug</a> method this implementation works non-blocking.
*
* @return {q} The collection for chaining
*
*/
logThis : function() {
if (qx.core.Environment.get("qx.debug")) {
// loop over the collection elements to make sure we get the current content
// of the collection and not the reference values later (they might change depending on
// manipulation of the collection)
var elements = [];
this.forEach(function(item) {
elements.push(item);
});
var length = this.length;
console.group("*** Collection infos ***");
console.info("Length:", length);
console.info("Elements:", elements);
console.info("Instance:", this);
console.groupEnd();
}
return this;
},
/**
* Calls a function for each DOM element or document fragment in the
* collection. This is used for DOM manipulations which can't be
* applied to document nodes or window objects.
*
* @param func {Function} Callback function. Will be called with three arguments:
* The element, the element's index within the collection and the collection itself.
* @param ctx {Object} The context for the callback function (default: The collection)
* @return {q} The collection for chaining
*/
_forEachElement : function(func, ctx) {
for (var i=0, l=this.length; i<l; i++) {
if (this[i] && (this[i].nodeType === 1 || this[i].nodeType === 11)) {
func.apply(ctx || this, [this[i], i, this]);
}
}
return this;
},
/**
* Calls a function for each DOM element node in the collection. Each node is wrapped
* in a collection before the function is called.
*
* @param func {Function} Callback function. Will be called with three arguments:
* The element wrappend in a collection, the element's index within the collection and
* the collection itself.
* @param ctx {Object} The context for the callback function (default: The collection)
* @return {q} The collection for chaining
*/
_forEachElementWrapped : function(func, ctx) {
this._forEachElement(function(item, idx, arr) {
func.apply(this, [qxWeb(item), idx, arr]);
}, ctx);
return this;
}
},
/**
* @ignore(q)
*/
defer : function(statics) {
if (window.q == undefined) {
window.q = statics;
}
}
});