| 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; |
| }; |
| |