Category UI improvements

Objectives:
* Ensure items don't get moved to the end when their sort-key is edited
* Add placeholder text and pending styling to input
* Auto-expand input to the end of the line
* Make the minimum input width smaller

Changes:

ve.ui.MWMetaDialog.js
* Added calls to fitInput on initialize
* Fixed sort key update and insert handlers to maintain item position when updating

ve.ui.GroupElement.js
* Added index argument to addItems, allowing items to be inserted at a specific location

ve.ui.PagePanelLayout.js
* Fixed CSS class name

ve.ui.StackPanelLayout.js, ve.ui.MenuWidget.js, ve.ui.SelectWidget.js
* Passed index argument through to group element

ve.ui.PanelLayout.js
* Fixed overflow direction for scrolling option

ve.ui.Inspector.css
* Moved border-box properties to text input widget class
* Set input widget within inspectors to be 100% by default

ve.ui.Layout.css
* Updated CSS class name
* Whitespace fixes

ve.ui.Widget.css
* Made text input widgets's wrapper default to 20em wide and the input inside it be 100%, using border-box to ensure proper sizing
* Adjusted category list item and input styles to make input appear more like a category item
* Whitespace fixes

ve.ui.MWCategoryInputWidget.js
* Made category input widget inherit text input widget, rather than just input widget

ve.ui.MWCategoryWidget.js
* Replaced group functionality by mixing in group element
* Added fitInput, which automatically make the input fill the rest of the line or take up the entire next line depending on how much space is left

VisualEditor.i18n.php
* Adjusted placeholder text for category input

Change-Id: I79a18a7b849804027473084a42c36133fdacad57
This commit is contained in:
Trevor Parscal 2013-05-03 11:30:33 -07:00 committed by Gerrit Code Review
parent 2cbc5045b6
commit e888d7b985
13 changed files with 165 additions and 82 deletions

View file

@ -23,7 +23,7 @@ $messages['en'] = array(
'visualeditor-ca-editsource' => 'Edit source', 'visualeditor-ca-editsource' => 'Edit source',
'visualeditor-ca-ve-edit' => 'VisualEditor', 'visualeditor-ca-ve-edit' => 'VisualEditor',
'visualeditor-ca-ve-create' => 'VisualEditor', 'visualeditor-ca-ve-create' => 'VisualEditor',
'visualeditor-category-input-placeholder' => 'Category name', 'visualeditor-category-input-placeholder' => 'Add category',
'visualeditor-category-settings-label' => 'Category settings', 'visualeditor-category-settings-label' => 'Category settings',
'visualeditor-dialog-meta-title' => 'Page settings', 'visualeditor-dialog-meta-title' => 'Page settings',
'visualeditor-dialog-content-title' => 'Content settings', 'visualeditor-dialog-content-title' => 'Content settings',

View file

@ -46,7 +46,8 @@ ve.ui.MWMetaDialog.static.icon = 'settings';
* @method * @method
*/ */
ve.ui.MWMetaDialog.prototype.onOpen = function () { ve.ui.MWMetaDialog.prototype.onOpen = function () {
var surfaceModel = this.surface.getModel(); var surfaceModel = this.surface.getModel(),
categoryWidget = this.categoryWidget;
// Force all previous transactions to be separate from this history state // Force all previous transactions to be separate from this history state
surfaceModel.breakpoint(); surfaceModel.breakpoint();
@ -54,6 +55,11 @@ ve.ui.MWMetaDialog.prototype.onOpen = function () {
// Parent method // Parent method
ve.ui.PagedDialog.prototype.onOpen.call( this ); ve.ui.PagedDialog.prototype.onOpen.call( this );
// Update input position once visible
setTimeout( function () {
categoryWidget.fitInput();
} );
}; };
/** /**
@ -175,15 +181,11 @@ ve.ui.MWMetaDialog.prototype.onNewCategory = function ( item ) {
* @param {Object} item * @param {Object} item
*/ */
ve.ui.MWMetaDialog.prototype.onUpdateSortKey = function ( item ) { ve.ui.MWMetaDialog.prototype.onUpdateSortKey = function ( item ) {
// Store the offset and index before removing var offset = item.metaItem.getOffset(),
var offset = item.metaItem.offset, index = item.metaItem.getIndex();
index = item.metaItem.index;
// Replace meta item with updated one
item.metaItem.remove(); item.metaItem.remove();
// It would seem as if insertItem happens before the onRemove event is sent to CategoryWidget,
// Remove the reference there so it doesn't try to get removed again onMetaListInsert
delete this.categoryWidget.categories[item.name];
// Insert updated meta item at same offset and index
this.metaList.insertMeta( this.getCategoryItemForInsertion( item ), offset, index ); this.metaList.insertMeta( this.getCategoryItemForInsertion( item ), offset, index );
}; };
@ -194,9 +196,12 @@ ve.ui.MWMetaDialog.prototype.onUpdateSortKey = function ( item ) {
* @param {Object} ve.dm.MetaItem * @param {Object} ve.dm.MetaItem
*/ */
ve.ui.MWMetaDialog.prototype.onMetaListInsert = function ( metaItem ) { ve.ui.MWMetaDialog.prototype.onMetaListInsert = function ( metaItem ) {
// Responsible for adding UI components. // Responsible for adding UI components
if ( metaItem.element.type === 'MWcategory' ) { if ( metaItem.element.type === 'MWcategory' ) {
this.categoryWidget.addItems( [ this.getCategoryItemFromMetaListItem( metaItem ) ] ); this.categoryWidget.addItems(
[ this.getCategoryItemFromMetaListItem( metaItem ) ],
this.metaList.findItem( metaItem.getOffset(), metaItem.getIndex(), 'MWcategory' )
);
} }
}; };

View file

@ -38,24 +38,37 @@ ve.ui.GroupElement.prototype.getItems = function () {
* *
* @method * @method
* @param {ve.ui.Element[]} items Item * @param {ve.ui.Element[]} items Item
* @param {number} [index] Index to insert items after
* @chainable * @chainable
*/ */
ve.ui.GroupElement.prototype.addItems = function ( items ) { ve.ui.GroupElement.prototype.addItems = function ( items, index ) {
var i, len, item; var i, len, item,
$items = $( [] );
for ( i = 0, len = items.length; i < len; i++ ) { for ( i = 0, len = items.length; i < len; i++ ) {
item = items[i]; item = items[i];
// Check if item exists then remove it first, effectively "moving" it // Check if item exists then remove it first, effectively "moving" it
if ( this.items.indexOf( item ) !== -1 ) { if ( this.items.indexOf( item ) !== -1 ) {
this.removeItems( [item] ); this.removeItems( [ item ] );
} }
// Add the item // Add the item
this.items.push( item ); $items = $items.add( item.$ );
this.$.append( item.$ );
this.$items = this.$items.add( item.$ );
} }
if ( index === undefined || index < 0 || index >= this.items.length ) {
this.$group.append( $items );
this.items.push.apply( this.items, items );
} else if ( index === 0 ) {
this.$group.prepend( $items );
this.items.unshift.apply( this.items, items );
} else {
this.$items.eq( index ).before( $items );
this.items.splice.apply( this.items, [ index, 0 ].concat( items ) );
}
this.$items = this.$items.add( $items );
return this; return this;
}; };

View file

@ -31,7 +31,7 @@ ve.ui.PagePanelLayout = function VeUiPagePanelLayout( config ) {
// Initialization // Initialization
this.$label.addClass( 've-ui-icon-' + config.icon + '-big' ); this.$label.addClass( 've-ui-icon-' + config.icon + '-big' );
this.$.append( this.$label ).addClass( 've-ui-editorPanelLayout' ); this.$.append( this.$label ).addClass( 've-ui-pagedPanelLayout' );
}; };
/* Inheritance */ /* Inheritance */

View file

@ -48,9 +48,10 @@ ve.mixinClass( ve.ui.StackPanelLayout, ve.ui.GroupElement );
* *
* @method * @method
* @param {ve.ui.PanelLayout[]} items Items to add * @param {ve.ui.PanelLayout[]} items Items to add
* @param {number} [index] Index to insert items after
* @chainable * @chainable
*/ */
ve.ui.StackPanelLayout.prototype.addItems = function ( items ) { ve.ui.StackPanelLayout.prototype.addItems = function ( items, index ) {
var i, len; var i, len;
for ( i = 0, len = items.length; i < len; i++ ) { for ( i = 0, len = items.length; i < len; i++ ) {
@ -60,7 +61,7 @@ ve.ui.StackPanelLayout.prototype.addItems = function ( items ) {
items[i].$.hide(); items[i].$.hide();
} }
} }
ve.ui.GroupElement.prototype.addItems.call( this, items ); ve.ui.GroupElement.prototype.addItems.call( this, items, index );
return this; return this;
}; };

View file

@ -25,7 +25,7 @@ ve.ui.PanelLayout = function VeUiPanelLayout( config ) {
// Initialization // Initialization
this.$.addClass( 've-ui-panelLayout' ); this.$.addClass( 've-ui-panelLayout' );
if ( config.scroll ) { if ( config.scroll ) {
this.$.css( 'overflow-x', 'auto' ); this.$.css( 'overflow-y', 'auto' );
} }
}; };

View file

@ -43,10 +43,7 @@
white-space: nowrap; white-space: nowrap;
} }
.ve-ui-window-body .ve-ui-textInputWidget input { .ve-ui-window-body .ve-ui-textInputWidget {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
width: 100%; width: 100%;
} }

View file

@ -23,15 +23,16 @@
/* ve.ui.EditorPanelLayout */ /* ve.ui.EditorPanelLayout */
.ve-ui-editorPanelLayout { .ve-ui-pagedPanelLayout {
padding: 1.5em; padding: 1.5em;
width: 100%; width: 100%;
-webkit-box-sizing:border-box; -webkit-box-sizing: border-box;
-moz-box-sizing:border-box; -moz-box-sizing: border-box;
box-sizing:border-box; box-sizing: border-box;
overflow: hidden;
} }
.ve-ui-editorPanelLayout > .ve-ui-labeledElement-label { .ve-ui-pagedPanelLayout > .ve-ui-labeledElement-label {
font-size: 1.5em; font-size: 1.5em;
padding-left: 1.75em; padding-left: 1.75em;
margin-bottom: 1em; margin-bottom: 1em;

View file

@ -194,6 +194,14 @@
/* ve.ui.TextInputWidget */ /* ve.ui.TextInputWidget */
.ve-ui-textInputWidget {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
width: 20em;
position: relative;
}
.ve-ui-textInputWidget input, .ve-ui-textInputWidget input,
.ve-ui-textInputWidget input:focus[readonly], .ve-ui-textInputWidget input:focus[readonly],
.ve-ui-widget-disabled.ve-ui-textInputWidget input:focus { .ve-ui-widget-disabled.ve-ui-textInputWidget input:focus {
@ -205,6 +213,10 @@
box-shadow: 0 0 0 white, inset 0 0.1em 0.2em #ddd; box-shadow: 0 0 0 white, inset 0 0.1em 0.2em #ddd;
padding: 0.5em; padding: 0.5em;
border-radius: 0.25em; border-radius: 0.25em;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
width: 100%;
/* Animation */ /* Animation */
-webkit-transition: border-color 200ms, box-shadow 200ms, background-color 200ms; -webkit-transition: border-color 200ms, box-shadow 200ms, background-color 200ms;
@ -354,7 +366,8 @@
.ve-ui-mwCategoryListItemWidget { .ve-ui-mwCategoryListItemWidget {
position: relative; position: relative;
float: left; float: left;
margin: .25em; margin-right: 0.5em;
margin-top: 0.5em;
} }
.ve-ui-mwCategoryListItemButton { .ve-ui-mwCategoryListItemButton {
@ -425,17 +438,17 @@
.ve-ui-mwCategoryInputWidget { .ve-ui-mwCategoryInputWidget {
float: left; float: left;
width: 15em;
margin-top: 0.5em;
} }
.ve-ui-mwCategoryInputWidget input { .ve-ui-mwCategoryInputWidget input {
display: inline-block; border-radius: 2em;
font-size: 1.1em; font-size: 1.05em;
font-family: sans-serif; margin-top: 0;
background: none; padding-left: 0.75em;
/* HACK: to match the categoryListItem height */ padding-right: 0.75em;
border: 1px solid #fff; background-color: #fff;
outline: none;
padding: 0.5em;
} }
.ve-ui-mwCategoryPopupMenu { .ve-ui-mwCategoryPopupMenu {
@ -445,7 +458,7 @@
} }
.ve-ui-mwCategoryPopupMenu .ve-ui-iconButtonWidget { .ve-ui-mwCategoryPopupMenu .ve-ui-iconButtonWidget {
display:block; display: block;
float: left; float: left;
} }
@ -491,4 +504,4 @@
/* @noflip */ /* @noflip */
.ve-ui-ltr { .ve-ui-ltr {
direction: ltr; direction: ltr;
} }

View file

@ -11,7 +11,7 @@
* Creates an ve.ui.MWCategoryInputWidget object. * Creates an ve.ui.MWCategoryInputWidget object.
* *
* @class * @class
* @extends ve.ui.InputWidget * @extends ve.ui.TextInputWidget
* @mixins ve.ui.PendingInputWidget * @mixins ve.ui.PendingInputWidget
* @mixins ve.ui.LookupInputWidget * @mixins ve.ui.LookupInputWidget
* *
@ -25,7 +25,7 @@ ve.ui.MWCategoryInputWidget = function VeUiMWCategoryInputWidget( categoryWidget
}, config ); }, config );
// Parent constructor // Parent constructor
ve.ui.InputWidget.call( this, config ); ve.ui.TextInputWidget.call( this, config );
// Mixin constructors // Mixin constructors
ve.ui.PendingInputWidget.call( this ); ve.ui.PendingInputWidget.call( this );
@ -43,15 +43,11 @@ ve.ui.MWCategoryInputWidget = function VeUiMWCategoryInputWidget( categoryWidget
/* Inheritance */ /* Inheritance */
ve.inheritClass( ve.ui.MWCategoryInputWidget, ve.ui.InputWidget ); ve.inheritClass( ve.ui.MWCategoryInputWidget, ve.ui.TextInputWidget );
ve.mixinClass( ve.ui.MWCategoryInputWidget, ve.ui.PendingInputWidget ); ve.mixinClass( ve.ui.MWCategoryInputWidget, ve.ui.PendingInputWidget );
ve.mixinClass( ve.ui.MWCategoryInputWidget, ve.ui.LookupInputWidget ); ve.mixinClass( ve.ui.MWCategoryInputWidget, ve.ui.LookupInputWidget );
/* Static Properties */
ve.ui.MWCategoryInputWidget.static.inputType = 'text';
/* Methods */ /* Methods */
/** /**

View file

@ -11,6 +11,7 @@
* @class * @class
* @abstract * @abstract
* @extends ve.ui.Widget * @extends ve.ui.Widget
* @mixin ve.ui.GroupElement
* *
* @constructor * @constructor
* @param {Object} [config] Config options * @param {Object} [config] Config options
@ -22,21 +23,23 @@ ve.ui.MWCategoryWidget = function VeUiMWCategoryWidget( config ) {
// Parent constructor // Parent constructor
ve.ui.Widget.call( this, config ); ve.ui.Widget.call( this, config );
// Mixin constructors
ve.ui.GroupElement.call( this, this.$$( '<div>' ), config );
// Properties // Properties
this.categories = {}; this.categories = {};
this.$categories = this.$$( '<div>' );
this.popupState = false; this.popupState = false;
this.savedPopupState = false; this.savedPopupState = false;
this.popup = new ve.ui.MWCategoryPopupWidget( { this.popup = new ve.ui.MWCategoryPopupWidget( {
'$$': this.$$, 'align': 'right', '$overlay': config.$overlay '$$': this.$$, 'align': 'right', '$overlay': config.$overlay
} ); } );
this.categoryInput = new ve.ui.MWCategoryInputWidget( this, { this.input = new ve.ui.MWCategoryInputWidget( this, {
'$$': this.$$, '$overlay': config.$overlay, '$container': this.$ '$$': this.$$, '$overlay': config.$overlay, '$container': this.$
} ); } );
// Events // Events
this.categoryInput.$input.on( 'keydown', ve.bind( this.onLookupInputKeyDown, this ) ); this.input.$input.on( 'keydown', ve.bind( this.onLookupInputKeyDown, this ) );
this.categoryInput.lookupMenu.connect( this, { 'select': 'onLookupMenuItemSelect' } ); this.input.lookupMenu.connect( this, { 'select': 'onLookupMenuItemSelect' } );
this.popup.connect( this, { this.popup.connect( this, {
'removeCategory': 'onRemoveCategory', 'removeCategory': 'onRemoveCategory',
'updateSortkey': 'onUpdateSortkey', 'updateSortkey': 'onUpdateSortkey',
@ -46,8 +49,8 @@ ve.ui.MWCategoryWidget = function VeUiMWCategoryWidget( config ) {
// Initialization // Initialization
this.$.addClass( 've-ui-mwCategoryListWidget' ) this.$.addClass( 've-ui-mwCategoryListWidget' )
.append( .append(
this.$categories, this.$group,
this.categoryInput.$, this.input.$,
this.$$( '<div>' ).css( 'clear', 'both' ) this.$$( '<div>' ).css( 'clear', 'both' )
); );
}; };
@ -77,12 +80,12 @@ ve.inheritClass( ve.ui.MWCategoryWidget, ve.ui.Widget );
* @param {jQuery.Event} e Input key down event * @param {jQuery.Event} e Input key down event
*/ */
ve.ui.MWCategoryWidget.prototype.onLookupInputKeyDown = function ( e ) { ve.ui.MWCategoryWidget.prototype.onLookupInputKeyDown = function ( e ) {
if ( this.categoryInput.getValue() !== '' && e.which === 13 ) { if ( this.input.getValue() !== '' && e.which === 13 ) {
this.emit( this.emit(
'newCategory', 'newCategory',
this.categoryInput.getCategoryItemFromValue( this.categoryInput.getValue() ) this.input.getCategoryItemFromValue( this.input.getValue() )
); );
this.categoryInput.setValue( '' ); this.input.setValue( '' );
} }
}; };
@ -94,8 +97,8 @@ ve.ui.MWCategoryWidget.prototype.onLookupInputKeyDown = function ( e ) {
*/ */
ve.ui.MWCategoryWidget.prototype.onLookupMenuItemSelect = function ( item ) { ve.ui.MWCategoryWidget.prototype.onLookupMenuItemSelect = function ( item ) {
if ( item && item.getData() !== '' ) { if ( item && item.getData() !== '' ) {
this.emit( 'newCategory', this.categoryInput.getCategoryItemFromValue( item.getData() ) ); this.emit( 'newCategory', this.input.getCategoryItemFromValue( item.getData() ) );
this.categoryInput.setValue( '' ); this.input.setValue( '' );
} }
}; };
@ -161,37 +164,53 @@ ve.ui.MWCategoryWidget.prototype.getCategories = function () {
* Adds category items. * Adds category items.
* *
* @method * @method
* @param {Object[]} items [description] * @param {Object[]} items Items to add
* @param {number} [index] Index to insert items after
* @chainable * @chainable
*/ */
ve.ui.MWCategoryWidget.prototype.addItems = function ( items ) { ve.ui.MWCategoryWidget.prototype.addItems = function ( items, index ) {
var i, len, item, categoryGroupItem, var i, len, item, categoryItem,
existingCategoryGroupItem = null; categoryItems = [],
existingCategoryItem = null;
for ( i = 0, len = items.length; i < len; i++ ) { for ( i = 0, len = items.length; i < len; i++ ) {
item = items[i]; item = items[i];
// Filter out categories derived from aliens. // HACK: Filter out categories derived from aliens
// TODO: Remove the block below once aliens no longer add items to metalist. // TODO: Remove this bit once aliens no longer add items to metalist
if( 'html/0/about' in item.metaItem.element.attributes ) { if ( 'html/0/about' in item.metaItem.element.attributes ) {
return this; continue;
} }
categoryGroupItem = new ve.ui.MWCategoryItemWidget( { '$$': this.$$, 'item': item } );
// Bind category item events. // Create a widget using the item data
categoryGroupItem.connect( this, { categoryItem = new ve.ui.MWCategoryItemWidget( { '$$': this.$$, 'item': item } );
categoryItem.connect( this, {
'savePopupState': 'onSavePopupState', 'savePopupState': 'onSavePopupState',
'togglePopupMenu': 'onTogglePoupupMenu' 'togglePopupMenu': 'onTogglePoupupMenu'
} ); } );
// Auto-remove existing items by value
if ( item.value in this.categories ) { if ( item.value in this.categories ) {
existingCategoryGroupItem = this.categories[item.value]; // Save reference to item
this.categories[item.value].metaItem.remove(); existingCategoryItem = this.categories[item.value];
// Removal in model will trigger #removeItems in widget
existingCategoryItem.metaItem.remove();
// Adjust index to compensate for removal
index = Math.max( index - 1, 0 );
} }
this.categories[item.value] = categoryGroupItem; // Index item by value
if ( existingCategoryGroupItem ) { this.categories[item.value] = categoryItem;
categoryGroupItem.sortKey = existingCategoryGroupItem.sortKey; // Copy sortKey from old item when "moving"
if ( existingCategoryItem ) {
categoryItem.sortKey = existingCategoryItem.sortKey;
} }
this.$categories.append( categoryGroupItem.$ );
categoryItems.push( categoryItem );
} }
ve.ui.GroupElement.prototype.addItems.call( this, categoryItems, index );
this.fitInput();
return this; return this;
}; };
@ -202,10 +221,46 @@ ve.ui.MWCategoryWidget.prototype.addItems = function ( items ) {
* @param {string[]} names Names of categories to remove * @param {string[]} names Names of categories to remove
*/ */
ve.ui.MWCategoryWidget.prototype.removeItems = function ( names ) { ve.ui.MWCategoryWidget.prototype.removeItems = function ( names ) {
var i, len; var i, len, categoryItem,
items = [];
for ( i = 0, len = names.length; i < len; i++ ) { for ( i = 0, len = names.length; i < len; i++ ) {
this.categories[names[i]].$.remove(); categoryItem = this.categories[names[i]];
categoryItem.disconnect( this );
items.push( categoryItem );
delete this.categories[names[i]]; delete this.categories[names[i]];
} }
ve.ui.GroupElement.prototype.removeItems.call( this, items );
this.fitInput();
};
/**
* Auto-fit the input.
*
* @method
*/
ve.ui.MWCategoryWidget.prototype.fitInput = function () {
var gap, min, margin, $lastItem,
$input = this.input.$;
if ( !$input.is( ':visible') ) {
return;
}
$input.css( { 'width': 'inherit' } );
min = $input.outerWidth();
$input.css( { 'width': '100%' } );
$lastItem = this.$.find( '.ve-ui-mwCategoryListItemWidget:last' );
if ( $lastItem.length ) {
margin = $input.offset().left - this.$.offset().left;
// Try to fit to the right of the last item
gap = ( $input.offset().left + $input.outerWidth() ) -
( $lastItem.offset().left + $lastItem.outerWidth() );
if ( gap >= min ) {
$input.css( { 'width': Math.round( gap - ( ( margin ) * 2 ) ) + 'px' } );
}
}
}; };

View file

@ -132,12 +132,13 @@ ve.ui.MenuWidget.prototype.selectItem = function ( item, silent ) {
* *
* @method * @method
* @param {ve.ui.MenuItemWidget[]} items Items to add * @param {ve.ui.MenuItemWidget[]} items Items to add
* @param {number} [index] Index to insert items after
* @chainable * @chainable
*/ */
ve.ui.MenuWidget.prototype.addItems = function ( items ) { ve.ui.MenuWidget.prototype.addItems = function ( items, index ) {
var i, len, item; var i, len, item;
ve.ui.SelectWidget.prototype.addItems.call( this, items ); ve.ui.SelectWidget.prototype.addItems.call( this, items, index );
for ( i = 0, len = items.length; i < len; i++ ) { for ( i = 0, len = items.length; i < len; i++ ) {
item = items[i]; item = items[i];

View file

@ -328,9 +328,10 @@ ve.ui.SelectWidget.prototype.getClosestSelectableItem = function ( index ) {
* *
* @method * @method
* @param {ve.ui.OptionWidget[]} items Items to add * @param {ve.ui.OptionWidget[]} items Items to add
* @param {number} [index] Index to insert items after
* @chainable * @chainable
*/ */
ve.ui.SelectWidget.prototype.addItems = function ( items ) { ve.ui.SelectWidget.prototype.addItems = function ( items, index ) {
var i, len, item, hash; var i, len, item, hash;
for ( i = 0, len = items.length; i < len; i++ ) { for ( i = 0, len = items.length; i < len; i++ ) {
@ -344,7 +345,7 @@ ve.ui.SelectWidget.prototype.addItems = function ( items ) {
this.hashes[hash] = item; this.hashes[hash] = item;
} }
} }
ve.ui.GroupElement.prototype.addItems.call( this, items ); ve.ui.GroupElement.prototype.addItems.call( this, items, index );
return this; return this;
}; };