496 lines
12 KiB
JavaScript
496 lines
12 KiB
JavaScript
var $ = require('../static'),
|
|
utils = require('../utils'),
|
|
isTag = utils.isTag,
|
|
domEach = utils.domEach,
|
|
hasOwn = Object.prototype.hasOwnProperty,
|
|
camelCase = utils.camelCase,
|
|
cssCase = utils.cssCase,
|
|
rspace = /\s+/,
|
|
dataAttrPrefix = 'data-',
|
|
_ = {
|
|
forEach: require('lodash.foreach'),
|
|
extend: require('lodash.assignin'),
|
|
some: require('lodash.some')
|
|
},
|
|
|
|
// Lookup table for coercing string data-* attributes to their corresponding
|
|
// JavaScript primitives
|
|
primitives = {
|
|
null: null,
|
|
true: true,
|
|
false: false
|
|
},
|
|
|
|
// Attributes that are booleans
|
|
rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
|
|
// Matches strings that look like JSON objects or arrays
|
|
rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/;
|
|
|
|
|
|
var getAttr = function(elem, name) {
|
|
if (!elem || !isTag(elem)) return;
|
|
|
|
if (!elem.attribs) {
|
|
elem.attribs = {};
|
|
}
|
|
|
|
// Return the entire attribs object if no attribute specified
|
|
if (!name) {
|
|
return elem.attribs;
|
|
}
|
|
|
|
if (hasOwn.call(elem.attribs, name)) {
|
|
// Get the (decoded) attribute
|
|
return rboolean.test(name) ? name : elem.attribs[name];
|
|
}
|
|
|
|
// Mimic the DOM and return text content as value for `option's`
|
|
if (elem.name === 'option' && name === 'value') {
|
|
return $.text(elem.children);
|
|
}
|
|
|
|
// Mimic DOM with default value for radios/checkboxes
|
|
if (elem.name === 'input' &&
|
|
(elem.attribs.type === 'radio' || elem.attribs.type === 'checkbox') &&
|
|
name === 'value') {
|
|
return 'on';
|
|
}
|
|
};
|
|
|
|
var setAttr = function(el, name, value) {
|
|
|
|
if (value === null) {
|
|
removeAttribute(el, name);
|
|
} else {
|
|
el.attribs[name] = value+'';
|
|
}
|
|
};
|
|
|
|
exports.attr = function(name, value) {
|
|
// Set the value (with attr map support)
|
|
if (typeof name === 'object' || value !== undefined) {
|
|
if (typeof value === 'function') {
|
|
return domEach(this, function(i, el) {
|
|
setAttr(el, name, value.call(el, i, el.attribs[name]));
|
|
});
|
|
}
|
|
return domEach(this, function(i, el) {
|
|
if (!isTag(el)) return;
|
|
|
|
if (typeof name === 'object') {
|
|
_.forEach(name, function(value, name) {
|
|
setAttr(el, name, value);
|
|
});
|
|
} else {
|
|
setAttr(el, name, value);
|
|
}
|
|
});
|
|
}
|
|
|
|
return getAttr(this[0], name);
|
|
};
|
|
|
|
var getProp = function (el, name) {
|
|
if (!el || !isTag(el)) return;
|
|
|
|
return el.hasOwnProperty(name)
|
|
? el[name]
|
|
: rboolean.test(name)
|
|
? getAttr(el, name) !== undefined
|
|
: getAttr(el, name);
|
|
};
|
|
|
|
var setProp = function (el, name, value) {
|
|
el[name] = rboolean.test(name) ? !!value : value;
|
|
};
|
|
|
|
exports.prop = function (name, value) {
|
|
var i = 0,
|
|
property;
|
|
|
|
if (typeof name === 'string' && value === undefined) {
|
|
|
|
switch (name) {
|
|
case 'style':
|
|
property = this.css();
|
|
|
|
_.forEach(property, function (v, p) {
|
|
property[i++] = p;
|
|
});
|
|
|
|
property.length = i;
|
|
|
|
break;
|
|
case 'tagName':
|
|
case 'nodeName':
|
|
property = this[0].name.toUpperCase();
|
|
break;
|
|
default:
|
|
property = getProp(this[0], name);
|
|
}
|
|
|
|
return property;
|
|
}
|
|
|
|
if (typeof name === 'object' || value !== undefined) {
|
|
|
|
if (typeof value === 'function') {
|
|
return domEach(this, function(i, el) {
|
|
setProp(el, name, value.call(el, i, getProp(el, name)));
|
|
});
|
|
}
|
|
|
|
return domEach(this, function(i, el) {
|
|
if (!isTag(el)) return;
|
|
|
|
if (typeof name === 'object') {
|
|
|
|
_.forEach(name, function(val, name) {
|
|
setProp(el, name, val);
|
|
});
|
|
|
|
} else {
|
|
setProp(el, name, value);
|
|
}
|
|
});
|
|
|
|
}
|
|
};
|
|
|
|
var setData = function(el, name, value) {
|
|
if (!el.data) {
|
|
el.data = {};
|
|
}
|
|
|
|
if (typeof name === 'object') return _.extend(el.data, name);
|
|
if (typeof name === 'string' && value !== undefined) {
|
|
el.data[name] = value;
|
|
} else if (typeof name === 'object') {
|
|
_.extend(el.data, name);
|
|
}
|
|
};
|
|
|
|
// Read the specified attribute from the equivalent HTML5 `data-*` attribute,
|
|
// and (if present) cache the value in the node's internal data store. If no
|
|
// attribute name is specified, read *all* HTML5 `data-*` attributes in this
|
|
// manner.
|
|
var readData = function(el, name) {
|
|
var readAll = arguments.length === 1;
|
|
var domNames, domName, jsNames, jsName, value, idx, length;
|
|
|
|
if (readAll) {
|
|
domNames = Object.keys(el.attribs).filter(function(attrName) {
|
|
return attrName.slice(0, dataAttrPrefix.length) === dataAttrPrefix;
|
|
});
|
|
jsNames = domNames.map(function(domName) {
|
|
return camelCase(domName.slice(dataAttrPrefix.length));
|
|
});
|
|
} else {
|
|
domNames = [dataAttrPrefix + cssCase(name)];
|
|
jsNames = [name];
|
|
}
|
|
|
|
for (idx = 0, length = domNames.length; idx < length; ++idx) {
|
|
domName = domNames[idx];
|
|
jsName = jsNames[idx];
|
|
if (hasOwn.call(el.attribs, domName)) {
|
|
value = el.attribs[domName];
|
|
|
|
if (hasOwn.call(primitives, value)) {
|
|
value = primitives[value];
|
|
} else if (value === String(Number(value))) {
|
|
value = Number(value);
|
|
} else if (rbrace.test(value)) {
|
|
try {
|
|
value = JSON.parse(value);
|
|
} catch(e){ }
|
|
}
|
|
|
|
el.data[jsName] = value;
|
|
}
|
|
}
|
|
|
|
return readAll ? el.data : value;
|
|
};
|
|
|
|
exports.data = function(name, value) {
|
|
var elem = this[0];
|
|
|
|
if (!elem || !isTag(elem)) return;
|
|
|
|
if (!elem.data) {
|
|
elem.data = {};
|
|
}
|
|
|
|
// Return the entire data object if no data specified
|
|
if (!name) {
|
|
return readData(elem);
|
|
}
|
|
|
|
// Set the value (with attr map support)
|
|
if (typeof name === 'object' || value !== undefined) {
|
|
domEach(this, function(i, el) {
|
|
setData(el, name, value);
|
|
});
|
|
return this;
|
|
} else if (hasOwn.call(elem.data, name)) {
|
|
return elem.data[name];
|
|
}
|
|
|
|
return readData(elem, name);
|
|
};
|
|
|
|
/**
|
|
* Get the value of an element
|
|
*/
|
|
|
|
exports.val = function(value) {
|
|
var querying = arguments.length === 0,
|
|
element = this[0];
|
|
|
|
if(!element) return;
|
|
|
|
switch (element.name) {
|
|
case 'textarea':
|
|
return this.text(value);
|
|
case 'input':
|
|
switch (this.attr('type')) {
|
|
case 'radio':
|
|
if (querying) {
|
|
return this.attr('value');
|
|
} else {
|
|
this.attr('value', value);
|
|
return this;
|
|
}
|
|
break;
|
|
default:
|
|
return this.attr('value', value);
|
|
}
|
|
return;
|
|
case 'select':
|
|
var option = this.find('option:selected'),
|
|
returnValue;
|
|
if (option === undefined) return undefined;
|
|
if (!querying) {
|
|
if (!this.attr().hasOwnProperty('multiple') && typeof value == 'object') {
|
|
return this;
|
|
}
|
|
if (typeof value != 'object') {
|
|
value = [value];
|
|
}
|
|
this.find('option').removeAttr('selected');
|
|
for (var i = 0; i < value.length; i++) {
|
|
this.find('option[value="' + value[i] + '"]').attr('selected', '');
|
|
}
|
|
return this;
|
|
}
|
|
returnValue = option.attr('value');
|
|
if (this.attr().hasOwnProperty('multiple')) {
|
|
returnValue = [];
|
|
domEach(option, function(i, el) {
|
|
returnValue.push(getAttr(el, 'value'));
|
|
});
|
|
}
|
|
return returnValue;
|
|
case 'option':
|
|
if (!querying) {
|
|
this.attr('value', value);
|
|
return this;
|
|
}
|
|
return this.attr('value');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Remove an attribute
|
|
*/
|
|
|
|
var removeAttribute = function(elem, name) {
|
|
if (!elem.attribs || !hasOwn.call(elem.attribs, name))
|
|
return;
|
|
|
|
delete elem.attribs[name];
|
|
};
|
|
|
|
|
|
exports.removeAttr = function(name) {
|
|
domEach(this, function(i, elem) {
|
|
removeAttribute(elem, name);
|
|
});
|
|
|
|
return this;
|
|
};
|
|
|
|
exports.hasClass = function(className) {
|
|
return _.some(this, function(elem) {
|
|
var attrs = elem.attribs,
|
|
clazz = attrs && attrs['class'],
|
|
idx = -1,
|
|
end;
|
|
|
|
if (clazz) {
|
|
while ((idx = clazz.indexOf(className, idx+1)) > -1) {
|
|
end = idx + className.length;
|
|
|
|
if ((idx === 0 || rspace.test(clazz[idx-1]))
|
|
&& (end === clazz.length || rspace.test(clazz[end]))) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
exports.addClass = function(value) {
|
|
// Support functions
|
|
if (typeof value === 'function') {
|
|
return domEach(this, function(i, el) {
|
|
var className = el.attribs['class'] || '';
|
|
exports.addClass.call([el], value.call(el, i, className));
|
|
});
|
|
}
|
|
|
|
// Return if no value or not a string or function
|
|
if (!value || typeof value !== 'string') return this;
|
|
|
|
var classNames = value.split(rspace),
|
|
numElements = this.length;
|
|
|
|
|
|
for (var i = 0; i < numElements; i++) {
|
|
// If selected element isn't a tag, move on
|
|
if (!isTag(this[i])) continue;
|
|
|
|
// If we don't already have classes
|
|
var className = getAttr(this[i], 'class'),
|
|
numClasses,
|
|
setClass;
|
|
|
|
if (!className) {
|
|
setAttr(this[i], 'class', classNames.join(' ').trim());
|
|
} else {
|
|
setClass = ' ' + className + ' ';
|
|
numClasses = classNames.length;
|
|
|
|
// Check if class already exists
|
|
for (var j = 0; j < numClasses; j++) {
|
|
var appendClass = classNames[j] + ' ';
|
|
if (setClass.indexOf(' ' + appendClass) < 0)
|
|
setClass += appendClass;
|
|
}
|
|
|
|
setAttr(this[i], 'class', setClass.trim());
|
|
}
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
var splitClass = function(className) {
|
|
return className ? className.trim().split(rspace) : [];
|
|
};
|
|
|
|
exports.removeClass = function(value) {
|
|
var classes,
|
|
numClasses,
|
|
removeAll;
|
|
|
|
// Handle if value is a function
|
|
if (typeof value === 'function') {
|
|
return domEach(this, function(i, el) {
|
|
exports.removeClass.call(
|
|
[el], value.call(el, i, el.attribs['class'] || '')
|
|
);
|
|
});
|
|
}
|
|
|
|
classes = splitClass(value);
|
|
numClasses = classes.length;
|
|
removeAll = arguments.length === 0;
|
|
|
|
return domEach(this, function(i, el) {
|
|
if (!isTag(el)) return;
|
|
|
|
if (removeAll) {
|
|
// Short circuit the remove all case as this is the nice one
|
|
el.attribs.class = '';
|
|
} else {
|
|
var elClasses = splitClass(el.attribs.class),
|
|
index,
|
|
changed;
|
|
|
|
for (var j = 0; j < numClasses; j++) {
|
|
index = elClasses.indexOf(classes[j]);
|
|
|
|
if (index >= 0) {
|
|
elClasses.splice(index, 1);
|
|
changed = true;
|
|
|
|
// We have to do another pass to ensure that there are not duplicate
|
|
// classes listed
|
|
j--;
|
|
}
|
|
}
|
|
if (changed) {
|
|
el.attribs.class = elClasses.join(' ');
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
exports.toggleClass = function(value, stateVal) {
|
|
// Support functions
|
|
if (typeof value === 'function') {
|
|
return domEach(this, function(i, el) {
|
|
exports.toggleClass.call(
|
|
[el],
|
|
value.call(el, i, el.attribs['class'] || '', stateVal),
|
|
stateVal
|
|
);
|
|
});
|
|
}
|
|
|
|
// Return if no value or not a string or function
|
|
if (!value || typeof value !== 'string') return this;
|
|
|
|
var classNames = value.split(rspace),
|
|
numClasses = classNames.length,
|
|
state = typeof stateVal === 'boolean' ? stateVal ? 1 : -1 : 0,
|
|
numElements = this.length,
|
|
elementClasses,
|
|
index;
|
|
|
|
for (var i = 0; i < numElements; i++) {
|
|
// If selected element isn't a tag, move on
|
|
if (!isTag(this[i])) continue;
|
|
|
|
elementClasses = splitClass(this[i].attribs.class);
|
|
|
|
// Check if class already exists
|
|
for (var j = 0; j < numClasses; j++) {
|
|
// Check if the class name is currently defined
|
|
index = elementClasses.indexOf(classNames[j]);
|
|
|
|
// Add if stateValue === true or we are toggling and there is no value
|
|
if (state >= 0 && index < 0) {
|
|
elementClasses.push(classNames[j]);
|
|
} else if (state <= 0 && index >= 0) {
|
|
// Otherwise remove but only if the item exists
|
|
elementClasses.splice(index, 1);
|
|
}
|
|
}
|
|
|
|
this[i].attribs.class = elementClasses.join(' ');
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
exports.is = function (selector) {
|
|
if (selector) {
|
|
return this.filter(selector).length > 0;
|
|
}
|
|
return false;
|
|
};
|
|
|