187 lines
4.8 KiB
JavaScript
187 lines
4.8 KiB
JavaScript
|
"use strict";
|
||
|
// DOMTokenList implementation based on https://github.com/Raynos/DOM-shim
|
||
|
var utils = require('./utils');
|
||
|
|
||
|
module.exports = DOMTokenList;
|
||
|
|
||
|
function DOMTokenList(getter, setter) {
|
||
|
this._getString = getter;
|
||
|
this._setString = setter;
|
||
|
this._length = 0;
|
||
|
this._lastStringValue = '';
|
||
|
this._update();
|
||
|
}
|
||
|
|
||
|
Object.defineProperties(DOMTokenList.prototype, {
|
||
|
length: { get: function() { return this._length; } },
|
||
|
item: { value: function(index) {
|
||
|
var list = getList(this);
|
||
|
if (index < 0 || index >= list.length) {
|
||
|
return null;
|
||
|
}
|
||
|
return list[index];
|
||
|
}},
|
||
|
|
||
|
contains: { value: function(token) {
|
||
|
token = String(token); // no error checking for contains()
|
||
|
var list = getList(this);
|
||
|
return list.indexOf(token) > -1;
|
||
|
}},
|
||
|
|
||
|
add: { value: function() {
|
||
|
var list = getList(this);
|
||
|
for (var i = 0, len = arguments.length; i < len; i++) {
|
||
|
var token = handleErrors(arguments[i]);
|
||
|
if (list.indexOf(token) < 0) {
|
||
|
list.push(token);
|
||
|
}
|
||
|
}
|
||
|
// Note: as per spec, if handleErrors() throws any errors, we never
|
||
|
// make it here and none of the changes take effect.
|
||
|
// Also per spec: we run the "update steps" even if no change was
|
||
|
// made (ie, if the token already existed)
|
||
|
this._update(list);
|
||
|
}},
|
||
|
|
||
|
remove: { value: function() {
|
||
|
var list = getList(this);
|
||
|
for (var i = 0, len = arguments.length; i < len; i++) {
|
||
|
var token = handleErrors(arguments[i]);
|
||
|
var index = list.indexOf(token);
|
||
|
if (index > -1) {
|
||
|
list.splice(index, 1);
|
||
|
}
|
||
|
}
|
||
|
// Note: as per spec, if handleErrors() throws any errors, we never
|
||
|
// make it here and none of the changes take effect.
|
||
|
// Also per spec: we run the "update steps" even if no change was
|
||
|
// made (ie, if the token wasn't previously present)
|
||
|
this._update(list);
|
||
|
}},
|
||
|
|
||
|
toggle: { value: function toggle(token, force) {
|
||
|
token = handleErrors(token);
|
||
|
if (this.contains(token)) {
|
||
|
if (force === undefined || force === false) {
|
||
|
this.remove(token);
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
} else {
|
||
|
if (force === undefined || force === true) {
|
||
|
this.add(token);
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
}},
|
||
|
|
||
|
replace: { value: function replace(token, newToken) {
|
||
|
// weird corner case of spec: if `token` contains whitespace, but
|
||
|
// `newToken` is the empty string, we must throw SyntaxError not
|
||
|
// InvalidCharacterError (sigh)
|
||
|
if (String(newToken)==='') { utils.SyntaxError(); }
|
||
|
token = handleErrors(token);
|
||
|
newToken = handleErrors(newToken);
|
||
|
var list = getList(this);
|
||
|
var idx = list.indexOf(token);
|
||
|
if (idx < 0) {
|
||
|
// Note that, per spec, we do not run the update steps on this path.
|
||
|
return false;
|
||
|
}
|
||
|
var idx2 = list.indexOf(newToken);
|
||
|
if (idx2 < 0) {
|
||
|
list[idx] = newToken;
|
||
|
} else {
|
||
|
// "replace the first instance of either `token` or `newToken` with
|
||
|
// `newToken` and remove all other instances"
|
||
|
if (idx < idx2) {
|
||
|
list[idx] = newToken;
|
||
|
list.splice(idx2, 1);
|
||
|
} else {
|
||
|
// idx2 is already `newToken`
|
||
|
list.splice(idx, 1);
|
||
|
}
|
||
|
}
|
||
|
this._update(list);
|
||
|
return true;
|
||
|
}},
|
||
|
|
||
|
toString: { value: function() {
|
||
|
return this._getString();
|
||
|
}},
|
||
|
|
||
|
value: {
|
||
|
get: function() {
|
||
|
return this._getString();
|
||
|
},
|
||
|
set: function(v) {
|
||
|
this._setString(v);
|
||
|
this._update();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
// Called when the setter is called from outside this interface.
|
||
|
_update: { value: function(list) {
|
||
|
if (list) {
|
||
|
fixIndex(this, list);
|
||
|
this._setString(list.join(" ").trim());
|
||
|
} else {
|
||
|
fixIndex(this, getList(this));
|
||
|
}
|
||
|
this._lastStringValue = this._getString();
|
||
|
} },
|
||
|
});
|
||
|
|
||
|
function fixIndex(clist, list) {
|
||
|
var oldLength = clist._length;
|
||
|
var i;
|
||
|
clist._length = list.length;
|
||
|
for (i = 0; i < list.length; i++) {
|
||
|
clist[i] = list[i];
|
||
|
}
|
||
|
// Clear/free old entries.
|
||
|
for (; i < oldLength; i++) {
|
||
|
clist[i] = undefined;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function handleErrors(token) {
|
||
|
token = String(token);
|
||
|
if (token === "") {
|
||
|
utils.SyntaxError();
|
||
|
}
|
||
|
if (/[ \t\r\n\f]/.test(token)) {
|
||
|
utils.InvalidCharacterError();
|
||
|
}
|
||
|
return token;
|
||
|
}
|
||
|
|
||
|
function toArray(clist) {
|
||
|
var length = clist._length;
|
||
|
var arr = Array(length);
|
||
|
for (var i = 0; i < length; i++) {
|
||
|
arr[i] = clist[i];
|
||
|
}
|
||
|
return arr;
|
||
|
}
|
||
|
|
||
|
function getList(clist) {
|
||
|
var strProp = clist._getString();
|
||
|
if (strProp === clist._lastStringValue) {
|
||
|
return toArray(clist);
|
||
|
}
|
||
|
var str = strProp.replace(/(^[ \t\r\n\f]+)|([ \t\r\n\f]+$)/g, '');
|
||
|
if (str === "") {
|
||
|
return [];
|
||
|
} else {
|
||
|
var seen = Object.create(null);
|
||
|
return str.split(/[ \t\r\n\f]+/g).filter(function(n) {
|
||
|
var key = '$' + n;
|
||
|
if (seen[key]) { return false; }
|
||
|
seen[key] = true;
|
||
|
return true;
|
||
|
});
|
||
|
}
|
||
|
}
|