387 lines
9.0 KiB
JavaScript
387 lines
9.0 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Constants.
|
|
*/
|
|
|
|
var errorMessage;
|
|
|
|
errorMessage = 'An argument without append, prepend, ' +
|
|
'or detach methods was given to `List';
|
|
|
|
/**
|
|
* Creates a new List: A linked list is a bit like an Array, but
|
|
* knows nothing about how many items are in it, and knows only about its
|
|
* first (`head`) and last (`tail`) items. Each item (e.g. `head`, `tail`,
|
|
* &c.) knows which item comes before or after it (its more like the
|
|
* implementation of the DOM in JavaScript).
|
|
* @global
|
|
* @private
|
|
* @constructor
|
|
* @class Represents an instance of List.
|
|
*/
|
|
|
|
function List(/*items...*/) {
|
|
if (arguments.length) {
|
|
return List.from(arguments);
|
|
}
|
|
}
|
|
|
|
var ListPrototype;
|
|
|
|
ListPrototype = List.prototype;
|
|
|
|
/**
|
|
* Creates a new list from the arguments (each a list item) passed in.
|
|
* @name List.of
|
|
* @param {...ListItem} [items] - Zero or more items to attach.
|
|
* @returns {list} - A new instance of List.
|
|
*/
|
|
|
|
List.of = function (/*items...*/) {
|
|
return List.from.call(this, arguments);
|
|
};
|
|
|
|
/**
|
|
* Creates a new list from the given array-like object (each a list item)
|
|
* passed in.
|
|
* @name List.from
|
|
* @param {ListItem[]} [items] - The items to append.
|
|
* @returns {list} - A new instance of List.
|
|
*/
|
|
List.from = function (items) {
|
|
var list = new this(), length, iterator, item;
|
|
|
|
if (items && (length = items.length)) {
|
|
iterator = -1;
|
|
|
|
while (++iterator < length) {
|
|
item = items[iterator];
|
|
|
|
if (item !== null && item !== undefined) {
|
|
list.append(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
return list;
|
|
};
|
|
|
|
/**
|
|
* List#head
|
|
* Default to `null`.
|
|
*/
|
|
ListPrototype.head = null;
|
|
|
|
/**
|
|
* List#tail
|
|
* Default to `null`.
|
|
*/
|
|
ListPrototype.tail = null;
|
|
|
|
/**
|
|
* Returns the list's items as an array. This does *not* detach the items.
|
|
* @name List#toArray
|
|
* @returns {ListItem[]} - An array of (still attached) ListItems.
|
|
*/
|
|
ListPrototype.toArray = function () {
|
|
var item = this.head,
|
|
result = [];
|
|
|
|
while (item) {
|
|
result.push(item);
|
|
item = item.next;
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* Prepends the given item to the list: Item will be the new first item
|
|
* (`head`).
|
|
* @name List#prepend
|
|
* @param {ListItem} item - The item to prepend.
|
|
* @returns {ListItem} - An instance of ListItem (the given item).
|
|
*/
|
|
ListPrototype.prepend = function (item) {
|
|
if (!item) {
|
|
return false;
|
|
}
|
|
|
|
if (!item.append || !item.prepend || !item.detach) {
|
|
throw new Error(errorMessage + '#prepend`.');
|
|
}
|
|
|
|
var self, head;
|
|
|
|
// Cache self.
|
|
self = this;
|
|
|
|
// If self has a first item, defer prepend to the first items prepend
|
|
// method, and return the result.
|
|
head = self.head;
|
|
|
|
if (head) {
|
|
return head.prepend(item);
|
|
}
|
|
|
|
// ...otherwise, there is no `head` (or `tail`) item yet.
|
|
|
|
// Detach the prependee.
|
|
item.detach();
|
|
|
|
// Set the prependees parent list to reference self.
|
|
item.list = self;
|
|
|
|
// Set self's first item to the prependee, and return the item.
|
|
self.head = item;
|
|
|
|
return item;
|
|
};
|
|
|
|
/**
|
|
* Appends the given item to the list: Item will be the new last item (`tail`)
|
|
* if the list had a first item, and its first item (`head`) otherwise.
|
|
* @name List#append
|
|
* @param {ListItem} item - The item to append.
|
|
* @returns {ListItem} - An instance of ListItem (the given item).
|
|
*/
|
|
|
|
ListPrototype.append = function (item) {
|
|
if (!item) {
|
|
return false;
|
|
}
|
|
|
|
if (!item.append || !item.prepend || !item.detach) {
|
|
throw new Error(errorMessage + '#append`.');
|
|
}
|
|
|
|
var self, head, tail;
|
|
|
|
// Cache self.
|
|
self = this;
|
|
|
|
// If self has a last item, defer appending to the last items append
|
|
// method, and return the result.
|
|
tail = self.tail;
|
|
|
|
if (tail) {
|
|
return tail.append(item);
|
|
}
|
|
|
|
// If self has a first item, defer appending to the first items append
|
|
// method, and return the result.
|
|
head = self.head;
|
|
|
|
if (head) {
|
|
return head.append(item);
|
|
}
|
|
|
|
// ...otherwise, there is no `tail` or `head` item yet.
|
|
|
|
// Detach the appendee.
|
|
item.detach();
|
|
|
|
// Set the appendees parent list to reference self.
|
|
item.list = self;
|
|
|
|
// Set self's first item to the appendee, and return the item.
|
|
self.head = item;
|
|
|
|
return item;
|
|
};
|
|
|
|
/**
|
|
* Creates a new ListItem: A linked list item is a bit like DOM node:
|
|
* It knows only about its "parent" (`list`), the item before it (`prev`),
|
|
* and the item after it (`next`).
|
|
* @global
|
|
* @private
|
|
* @constructor
|
|
* @class Represents an instance of ListItem.
|
|
*/
|
|
|
|
function ListItem() {}
|
|
|
|
List.Item = ListItem;
|
|
|
|
var ListItemPrototype = ListItem.prototype;
|
|
|
|
ListItemPrototype.next = null;
|
|
|
|
ListItemPrototype.prev = null;
|
|
|
|
ListItemPrototype.list = null;
|
|
|
|
/**
|
|
* Detaches the item operated on from its parent list.
|
|
* @name ListItem#detach
|
|
* @returns {ListItem} - The item operated on.
|
|
*/
|
|
ListItemPrototype.detach = function () {
|
|
// Cache self, the parent list, and the previous and next items.
|
|
var self = this,
|
|
list = self.list,
|
|
prev = self.prev,
|
|
next = self.next;
|
|
|
|
// If the item is already detached, return self.
|
|
if (!list) {
|
|
return self;
|
|
}
|
|
|
|
// If self is the last item in the parent list, link the lists last item
|
|
// to the previous item.
|
|
if (list.tail === self) {
|
|
list.tail = prev;
|
|
}
|
|
|
|
// If self is the first item in the parent list, link the lists first item
|
|
// to the next item.
|
|
if (list.head === self) {
|
|
list.head = next;
|
|
}
|
|
|
|
// If both the last and first items in the parent list are the same,
|
|
// remove the link to the last item.
|
|
if (list.tail === list.head) {
|
|
list.tail = null;
|
|
}
|
|
|
|
// If a previous item exists, link its next item to selfs next item.
|
|
if (prev) {
|
|
prev.next = next;
|
|
}
|
|
|
|
// If a next item exists, link its previous item to selfs previous item.
|
|
if (next) {
|
|
next.prev = prev;
|
|
}
|
|
|
|
// Remove links from self to both the next and previous items, and to the
|
|
// parent list.
|
|
self.prev = self.next = self.list = null;
|
|
|
|
// Return self.
|
|
return self;
|
|
};
|
|
|
|
/**
|
|
* Prepends the given item *before* the item operated on.
|
|
* @name ListItem#prepend
|
|
* @param {ListItem} item - The item to prepend.
|
|
* @returns {ListItem} - The item operated on, or false when that item is not
|
|
* attached.
|
|
*/
|
|
ListItemPrototype.prepend = function (item) {
|
|
if (!item || !item.append || !item.prepend || !item.detach) {
|
|
throw new Error(errorMessage + 'Item#prepend`.');
|
|
}
|
|
|
|
// Cache self, the parent list, and the previous item.
|
|
var self = this,
|
|
list = self.list,
|
|
prev = self.prev;
|
|
|
|
// If self is detached, return false.
|
|
if (!list) {
|
|
return false;
|
|
}
|
|
|
|
// Detach the prependee.
|
|
item.detach();
|
|
|
|
// If self has a previous item...
|
|
if (prev) {
|
|
// ...link the prependees previous item, to selfs previous item.
|
|
item.prev = prev;
|
|
|
|
// ...link the previous items next item, to self.
|
|
prev.next = item;
|
|
}
|
|
|
|
// Set the prependees next item to self.
|
|
item.next = self;
|
|
|
|
// Set the prependees parent list to selfs parent list.
|
|
item.list = list;
|
|
|
|
// Set the previous item of self to the prependee.
|
|
self.prev = item;
|
|
|
|
// If self is the first item in the parent list, link the lists first item
|
|
// to the prependee.
|
|
if (self === list.head) {
|
|
list.head = item;
|
|
}
|
|
|
|
// If the the parent list has no last item, link the lists last item to
|
|
// self.
|
|
if (!list.tail) {
|
|
list.tail = self;
|
|
}
|
|
|
|
// Return the prependee.
|
|
return item;
|
|
};
|
|
|
|
/**
|
|
* Appends the given item *after* the item operated on.
|
|
* @name ListItem#append
|
|
* @param {ListItem} item - The item to append.
|
|
* @returns {ListItem} - The item operated on, or false when that item is not
|
|
* attached.
|
|
*/
|
|
ListItemPrototype.append = function (item) {
|
|
// If item is falsey, return false.
|
|
if (!item || !item.append || !item.prepend || !item.detach) {
|
|
throw new Error(errorMessage + 'Item#append`.');
|
|
}
|
|
|
|
// Cache self, the parent list, and the next item.
|
|
var self = this,
|
|
list = self.list,
|
|
next = self.next;
|
|
|
|
// If self is detached, return false.
|
|
if (!list) {
|
|
return false;
|
|
}
|
|
|
|
// Detach the appendee.
|
|
item.detach();
|
|
|
|
// If self has a next item...
|
|
if (next) {
|
|
// ...link the appendees next item, to selfs next item.
|
|
item.next = next;
|
|
|
|
// ...link the next items previous item, to the appendee.
|
|
next.prev = item;
|
|
}
|
|
|
|
// Set the appendees previous item to self.
|
|
item.prev = self;
|
|
|
|
// Set the appendees parent list to selfs parent list.
|
|
item.list = list;
|
|
|
|
// Set the next item of self to the appendee.
|
|
self.next = item;
|
|
|
|
// If the the parent list has no last item or if self is the parent lists
|
|
// last item, link the lists last item to the appendee.
|
|
if (self === list.tail || !list.tail) {
|
|
list.tail = item;
|
|
}
|
|
|
|
// Return the appendee.
|
|
return item;
|
|
};
|
|
|
|
/**
|
|
* Expose `List`.
|
|
*/
|
|
|
|
module.exports = List;
|