mediawiki-extensions-Visual.../modules/ve/ui/widgets/ve.ui.MWCategoryWidget.js
Trevor Parscal e888d7b985 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
2013-05-10 00:19:46 +00:00

267 lines
6.4 KiB
JavaScript

/*!
* VisualEditor UserInterface MWCategoryWidget class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* Creates an ve.ui.MWCategoryWidget object.
*
* @class
* @abstract
* @extends ve.ui.Widget
* @mixin ve.ui.GroupElement
*
* @constructor
* @param {Object} [config] Config options
*/
ve.ui.MWCategoryWidget = function VeUiMWCategoryWidget( config ) {
// Config intialization
config = config || {};
// Parent constructor
ve.ui.Widget.call( this, config );
// Mixin constructors
ve.ui.GroupElement.call( this, this.$$( '<div>' ), config );
// Properties
this.categories = {};
this.popupState = false;
this.savedPopupState = false;
this.popup = new ve.ui.MWCategoryPopupWidget( {
'$$': this.$$, 'align': 'right', '$overlay': config.$overlay
} );
this.input = new ve.ui.MWCategoryInputWidget( this, {
'$$': this.$$, '$overlay': config.$overlay, '$container': this.$
} );
// Events
this.input.$input.on( 'keydown', ve.bind( this.onLookupInputKeyDown, this ) );
this.input.lookupMenu.connect( this, { 'select': 'onLookupMenuItemSelect' } );
this.popup.connect( this, {
'removeCategory': 'onRemoveCategory',
'updateSortkey': 'onUpdateSortkey',
'hide': 'onPopupHide'
} );
// Initialization
this.$.addClass( 've-ui-mwCategoryListWidget' )
.append(
this.$group,
this.input.$,
this.$$( '<div>' ).css( 'clear', 'both' )
);
};
/* Inheritance */
ve.inheritClass( ve.ui.MWCategoryWidget, ve.ui.Widget );
/* Events */
/**
* @event newCategory
* @param {Object} item category group item
*/
/**
* @event updateSortkey
* * @param {Object} item category group item
*/
/* Methods */
/**
* Handle input key down event.
*
* @method
* @param {jQuery.Event} e Input key down event
*/
ve.ui.MWCategoryWidget.prototype.onLookupInputKeyDown = function ( e ) {
if ( this.input.getValue() !== '' && e.which === 13 ) {
this.emit(
'newCategory',
this.input.getCategoryItemFromValue( this.input.getValue() )
);
this.input.setValue( '' );
}
};
/**
* Handle menu item select event.
*
* @method
* @param {ve.ui.MenuItemWidget} item Selected item
*/
ve.ui.MWCategoryWidget.prototype.onLookupMenuItemSelect = function ( item ) {
if ( item && item.getData() !== '' ) {
this.emit( 'newCategory', this.input.getCategoryItemFromValue( item.getData() ) );
this.input.setValue( '' );
}
};
/**
* Calls metaItem remove method
*
* @param {string} name category name
*/
ve.ui.MWCategoryWidget.prototype.onRemoveCategory = function ( name ) {
this.categories[name].metaItem.remove();
};
/**
* Update sortkey value, emit upsteSortkey event
*
* @param {string} name
* @param {string} value
*/
ve.ui.MWCategoryWidget.prototype.onUpdateSortkey = function ( name, value ) {
this.categories[name].sortKey = value;
this.emit( 'updateSortkey', this.categories[name] );
};
/**
* Sets popup state when popup is hidden
*/
ve.ui.MWCategoryWidget.prototype.onPopupHide = function () {
this.popupState = false;
};
/**
* Saves current popup state
*/
ve.ui.MWCategoryWidget.prototype.onSavePopupState = function () {
this.savedPopupState = this.popupState;
};
/**
* Toggles popup menu per category item
* @param {Object} item
*/
ve.ui.MWCategoryWidget.prototype.onTogglePoupupMenu = function ( item ) {
// Close open popup.
if ( this.savedPopupState === false || item.value !== this.popup.category ) {
this.popup.openPopup( item );
} else {
// Handle toggle
this.popup.closePopup();
}
};
/**
* Get list of category names.
*
* @method
* @param {string[]} List of category names
*/
ve.ui.MWCategoryWidget.prototype.getCategories = function () {
return ve.getObjectKeys( this.categories );
};
/**
* Adds category items.
*
* @method
* @param {Object[]} items Items to add
* @param {number} [index] Index to insert items after
* @chainable
*/
ve.ui.MWCategoryWidget.prototype.addItems = function ( items, index ) {
var i, len, item, categoryItem,
categoryItems = [],
existingCategoryItem = null;
for ( i = 0, len = items.length; i < len; i++ ) {
item = items[i];
// HACK: Filter out categories derived from aliens
// TODO: Remove this bit once aliens no longer add items to metalist
if ( 'html/0/about' in item.metaItem.element.attributes ) {
continue;
}
// Create a widget using the item data
categoryItem = new ve.ui.MWCategoryItemWidget( { '$$': this.$$, 'item': item } );
categoryItem.connect( this, {
'savePopupState': 'onSavePopupState',
'togglePopupMenu': 'onTogglePoupupMenu'
} );
// Auto-remove existing items by value
if ( item.value in this.categories ) {
// Save reference to item
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 );
}
// Index item by value
this.categories[item.value] = categoryItem;
// Copy sortKey from old item when "moving"
if ( existingCategoryItem ) {
categoryItem.sortKey = existingCategoryItem.sortKey;
}
categoryItems.push( categoryItem );
}
ve.ui.GroupElement.prototype.addItems.call( this, categoryItems, index );
this.fitInput();
return this;
};
/**
* Remove category items.
*
* @method
* @param {string[]} names Names of categories to remove
*/
ve.ui.MWCategoryWidget.prototype.removeItems = function ( names ) {
var i, len, categoryItem,
items = [];
for ( i = 0, len = names.length; i < len; i++ ) {
categoryItem = this.categories[names[i]];
categoryItem.disconnect( this );
items.push( categoryItem );
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' } );
}
}
};