var _ = require('underscore'), utils = require('../utils'), isTag = utils.isTag, decode = utils.decode, encode = utils.encode, hasOwn = Object.prototype.hasOwnProperty, rspace = /\s+/, // 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 setAttr = function(el, name, value) { if (typeof name === 'object') return _.extend(el.attribs, name); if (value === null) { removeAttribute(el, name); } else { el.attribs[name] = encode(value); } return el.attribs; }; var attr = exports.attr = function(name, value) { // Set the value (with attr map support) if (typeof name === 'object' || value !== undefined) { if (_.isFunction(value)) { return this.each(function(i, el) { setAttr(el, name, value.call(this, i, el.attribs[name])); }); } return this.each(function(i, el) { el.attribs = setAttr(el, name, value); }); } var elem = this[0]; if (!elem || !isTag(elem)) return; if (!elem.attribs) { elem.attribs = {}; } // Return the entire attribs object if no attribute specified if (!name) { for (var a in elem.attribs) { elem.attribs[a] = decode(elem.attribs[a]); } return elem.attribs; } if (hasOwn.call(elem.attribs, name)) { // Get the (decoded) attribute return decode(elem.attribs[name]); } }; var setData = function(el, name, value) { if (typeof name === 'object') return _.extend(el.data, name); if (typeof name === 'string' && value !== undefined) { el.data[name] = encode(value); } else if (typeof name === 'object') { // If its an object, loop through it _.each(name, function(value, key) { el.data[key] = encode(value); }); } return el.data; }; var data = 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) { _.each(elem.data, function(value, key) { elem.data[key] = decode(value); }); return elem.data; } // Set the value (with attr map support) if (typeof name === 'object' || value !== undefined) { this.each(function(i, el) { el.data = setData(el, name, value); }); return this; } else if (hasOwn.call(elem.data, name)) { // Get the (decoded) data var val = decode(elem.data[name]); if (hasOwn.call(primitives, val)) { val = primitives[val]; } else if (val === String(Number(val))) { val = Number(val); } else if (rbrace.test(val)) { val = JSON.parse(val); } return val; } else if (typeof name === 'string' && value === undefined) { return undefined; } return this; }; /** * Get the value of an element */ var val = exports.val = function(value) { var querying = arguments.length === 0, element = this[0]; if(!element) return; switch (element.name) { case 'textarea': return querying ? this.text() : this.each(function() { this.text(value); }); case 'input': switch (this.attr('type')) { case 'radio': var queryString = 'input[type=radio][name=' + this.attr('name') + ']:checked'; var parentEl, root; // Go up until we hit a form or root parentEl = this.closest('form'); if (parentEl.length === 0) { root = (this.parents().last()[0] || this[0]).root; parentEl = this._make(root); } if (querying) { return parentEl.find(queryString).attr('value'); } else { parentEl.find(':checked').removeAttr('checked'); parentEl.find('input[type=radio][value="' + value + '"]').attr('checked', ''); return this; } break; default: return querying ? this.attr('value') : this.each(function() { 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 = []; option.each(function() { returnValue.push(this.attr('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 (!isTag(elem.type) || !elem.attribs || !Object.hasOwnProperty.call(elem.attribs, name)) return; if (rboolean.test(elem.attribs[name])) elem.attribs[name] = false; else delete elem.attribs[name]; }; var removeAttr = exports.removeAttr = function(name) { this.each(function(i, elem) { removeAttribute(elem, name); }); return this; }; var hasClass = exports.hasClass = function(className) { return _.any(this, function(elem) { var attrs = elem.attribs; return attrs && _.contains((attrs['class'] || '').split(rspace), className); }); }; var addClass = exports.addClass = function(value) { // Support functions if (_.isFunction(value)) { this.each(function(i) { var className = this.attr('class') || ''; this.addClass(value.call(this[0], i, className)); }); } // Return if no value or not a string or function if (!value || !_.isString(value)) return this; var classNames = value.split(rspace), numElements = this.length, numClasses, setClass, $elem; for (var i = 0; i < numElements; i++) { $elem = this._make(this[i]); // If selected element isnt a tag, move on if (!isTag(this[i])) continue; // If we don't already have classes if (!$elem.attr('class')) { $elem.attr('class', classNames.join(' ').trim()); } else { setClass = ' ' + $elem.attr('class') + ' '; numClasses = classNames.length; // Check if class already exists for (var j = 0; j < numClasses; j++) { if (!~setClass.indexOf(' ' + classNames[j] + ' ')) setClass += classNames[j] + ' '; } $elem.attr('class', setClass.trim()); } } return this; }; var removeClass = exports.removeClass = function(value) { var split = function(className) { return className ? className.trim().split(rspace) : []; }; var classes, removeAll; // Handle if value is a function if (_.isFunction(value)) { return this.each(function(i, el) { this.removeClass(value.call(this[0], i, el.attribs['class'] || '')); }); } classes = split(value); removeAll = arguments.length === 0; return this.each(function(i, el) { if (!isTag(el)) return; el.attribs.class = removeAll ? '' : _.difference(split(el.attribs.class), classes).join(' '); }); }; var toggleClass = exports.toggleClass = function(value, stateVal) { // Support functions if (_.isFunction(value)) { return this.each(function(i, el) { this.toggleClass(value.call(this, i, el.attribs['class'] || '', stateVal), stateVal); }); } // Return if no value or not a string or function if (!value || !_.isString(value)) return this; var classNames = value.split(rspace), numClasses = classNames.length, isBool = typeof stateVal === 'boolean', numElements = this.length, $elem, state; for (var i = 0; i < numElements; i++) { $elem = this._make(this[i]); // If selected element isnt a tag, move on if (!isTag(this[i])) continue; // Check if class already exists for (var j = 0; j < numClasses; j++) { // check each className given, space separated list state = isBool ? stateVal : !$elem.hasClass(classNames[j]); $elem[state ? 'addClass' : 'removeClass'](classNames[j]); } } return this; }; var is = exports.is = function (selector) { if (selector) { return this.filter(selector).length > 0; } return false; };