/*! * VisualEditor DataModel InternalList class. * * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt * @license The MIT License (MIT); see LICENSE.txt */ /** * DataModel meta item. * * @class * @mixins ve.EventEmitter * * @constructor * @param {ve.dm.Document} doc Document model */ ve.dm.InternalList = function VeDmInternalList( doc ) { // Mixin constructors ve.EventEmitter.call( this ); // Properties this.document = doc; this.itemHtmlQueue = []; this.listNode = null; this.nodes = {}; this.groupsChanged = []; this.keyIndexes = {}; this.keys = []; this.nextUniqueNumber = 0; // Event handlers if ( doc ) { doc.connect( this, { 'transact': 'onTransact' } ); } }; /* Inheritance */ ve.mixinClass( ve.dm.InternalList, ve.EventEmitter ); /* Events */ /** * @event update * @param {string[]} groupsChanged List of groups changed since the last transaction */ /* Methods */ /** * Queues up an item's html for parsing later. * * If an item with the specified group and key already exists it will be ignored, unless * the data already stored is an empty string. * * @method * @param {string} groupName Item group * @param {string} key Item key * @param {string} html Item contents * @returns {Object} Object containing index of the item in the index-value store * (and also its index in the internal list node), and a flag indicating if it is a new item. */ ve.dm.InternalList.prototype.queueItemHtml = function ( groupName, key, html ) { var isNew = false, index = this.getKeyIndex( groupName, key ); if ( index === undefined ) { index = this.itemHtmlQueue.length; this.keyIndexes[groupName + '/' + key] = index; this.itemHtmlQueue.push( html ); isNew = true; } else if ( this.itemHtmlQueue[index] === '' ) { // Previous value with this key was empty, overwrite value in queue this.itemHtmlQueue[index] = html; isNew = true; } return { 'index': index, 'isNew': isNew }; }; /** * Gets all the item's HTML strings * @method * @returns {Object} Name-indexed object containing HTMLElements */ ve.dm.InternalList.prototype.getItemHtmlQueue = function () { return this.itemHtmlQueue; }; /** * Gets the internal list's document model * @method * @returns {ve.dm.Document} Document model */ ve.dm.InternalList.prototype.getDocument = function () { return this.document; }; /** * Get the list node * @method * @returns {ve.dm.InternalListNode} List node */ ve.dm.InternalList.prototype.getListNode = function () { var i, nodes; // find listNode if not set, or unattached if ( !this.listNode || !this.listNode.doc ) { nodes = this.getDocument().documentNode.children; for ( i = nodes.length; i >= 0; i-- ) { if ( nodes[i] instanceof ve.dm.InternalListNode ) { this.listNode = nodes[i]; break; } } } return this.listNode; }; /** * Get the number it internal items in the internal list. * * @method * @returns {number} */ ve.dm.InternalList.prototype.getItemNodeCount = function () { return this.getListNode().children.length; }; /** * Get the item node from a specific index. * * @method * @param {number} index Item index * @returns {ve.dm.InternalItemNode} Item node */ ve.dm.InternalList.prototype.getItemNode = function ( index ) { return this.getListNode().children[index]; }; /** * Get all node groups. * * @method * @returns {Object} Node groups, keyed by group name */ ve.dm.InternalList.prototype.getNodeGroups = function () { return this.nodes; }; /** * Get the node group object for a specified group name. * * @method * @param {string} groupName Name of the group * @returns {Object} Node group object, containing nodes and key order array */ ve.dm.InternalList.prototype.getNodeGroup = function ( groupName ) { return this.nodes[groupName]; }; /** * Get a unique list key for a given group. * * The returned list key is added to the list of unique list keys used in this group so that it * won't be allocated again. It will also be associated to oldListKey so that if the same oldListKey * is passed in again later, the previously allocated name will be returned. * * @method * @param {string} groupName Name of the group * @param {string} oldListKey Current list key to associate the generated list key with * @param {string} prefix Prefix to distinguish generated keys from non-generated ones * @returns {string} Generated unique list key, or existing unique key associated with oldListKey */ ve.dm.InternalList.prototype.getUniqueListKey = function ( groupName, oldListKey, prefix ) { var group = this.getNodeGroup( groupName ), num = 0; if ( group.uniqueListKeys[oldListKey] !== undefined ) { return group.uniqueListKeys[oldListKey]; } while ( group.keyedNodes[prefix + num] || group.uniqueListKeysInUse[prefix + num] ) { num++; } group.uniqueListKeys[oldListKey] = prefix + num; group.uniqueListKeysInUse[prefix + num] = true; return prefix + num; }; /** * Get the next number in a monotonically increasing series. * @returns {number} One higher than the return value of the previous call, or 0 on the first call */ ve.dm.InternalList.prototype.getNextUniqueNumber = function () { return this.nextUniqueNumber++; }; /** * Converts stored item HTML into linear data. * * Each item is an InternalItem, and they are wrapped in an InternalList. * If there are no items an empty array is returned. * * Stored HTML is deleted after conversion. * * @method * @param {ve.dm.Converter} converter Converter object * @returns {Array} Linear model data */ ve.dm.InternalList.prototype.convertToData = function ( converter ) { var i, length, itemData, itemHtmlQueue = this.getItemHtmlQueue(), list = []; list.push( { 'type': 'internalList' } ); for ( i = 0, length = itemHtmlQueue.length; i < length; i++ ) { if ( itemHtmlQueue[i] !== '' ) { itemData = converter.getDataFromDomRecursion( $( '