jenkins-bot has submitted this change and it was merged.
Change subject: ve.ui.LookupWidget
......................................................................
ve.ui.LookupWidget
Refactoring of externally sourced suggestions for text inputs.
*.php
* Added links to new file
ve.ui.InputLabelWidget.js
* Changed to focus input element, not wrapper div
ve.ui.InputWidget.js
* Fixed incorrect documentation
ve.ui.LookupInputWidget.js
* New mixing that abstracts placing a menu of options below a text input
and filling it with data from an external source
ve.ui.MenuWidget.js
* Fixed to get reference to input element, no wrapper div
ve.ui.MWLinkTargetInputWidget.js
* Moved pending and lookup functionality to mixing
* Implemented menu population using only matching pages, rather than a
combination of that and page existence checks (fewer API calls)
ve.ui.TextInputMenuWidget.js
* Added configurable container to render underneath, rather than assuming
this.input.$
* Added auto-position-on-window-resize functionality
* Fixed frame position correction to ensure that it only is used when the
overlay is in a different frame from the container to position
underneath
ve.ui.TextInputWidget.js
* Added placeholder text feature
Change-Id: If5ed1b64fd15982807691ce8bb0362970633108a
---
M VisualEditor.php
M demos/ve/index.php
M modules/ve/test/index.php
M modules/ve/ui/widgets/ve.ui.InputLabelWidget.js
M modules/ve/ui/widgets/ve.ui.InputWidget.js
A modules/ve/ui/widgets/ve.ui.LookupInputWidget.js
M modules/ve/ui/widgets/ve.ui.MWLinkTargetInputWidget.js
M modules/ve/ui/widgets/ve.ui.MenuWidget.js
M modules/ve/ui/widgets/ve.ui.TextInputMenuWidget.js
M modules/ve/ui/widgets/ve.ui.TextInputWidget.js
10 files changed, 395 insertions(+), 278 deletions(-)
Approvals:
Catrope: Looks good to me, approved
jenkins-bot: Verified
diff --git a/VisualEditor.php b/VisualEditor.php
index 744a721..f7a5f16 100644
--- a/VisualEditor.php
+++ b/VisualEditor.php
@@ -377,6 +377,7 @@
've/ui/widgets/ve.ui.MenuSectionItemWidget.js',
've/ui/widgets/ve.ui.MenuWidget.js',
've/ui/widgets/ve.ui.PendingInputWidget.js',
+ 've/ui/widgets/ve.ui.LookupInputWidget.js',
've/ui/widgets/ve.ui.TextInputMenuWidget.js',
've/ui/widgets/ve.ui.LinkTargetInputWidget.js',
've/ui/widgets/ve.ui.MWLinkTargetInputWidget.js',
diff --git a/demos/ve/index.php b/demos/ve/index.php
index 43218f4..9207468 100644
--- a/demos/ve/index.php
+++ b/demos/ve/index.php
@@ -262,6 +262,7 @@
<script
src="../../modules/ve/ui/widgets/ve.ui.MenuSectionItemWidget.js"></script>
<script
src="../../modules/ve/ui/widgets/ve.ui.MenuWidget.js"></script>
<script
src="../../modules/ve/ui/widgets/ve.ui.PendingInputWidget.js"></script>
+ <script
src="../../modules/ve/ui/widgets/ve.ui.LookupInputWidget.js"></script>
<script
src="../../modules/ve/ui/widgets/ve.ui.TextInputMenuWidget.js"></script>
<script
src="../../modules/ve/ui/widgets/ve.ui.LinkTargetInputWidget.js"></script>
<script
src="../../modules/ve/ui/widgets/ve.ui.MWLinkTargetInputWidget.js"></script>
diff --git a/modules/ve/test/index.php b/modules/ve/test/index.php
index 6fdc970..91e4151 100644
--- a/modules/ve/test/index.php
+++ b/modules/ve/test/index.php
@@ -205,6 +205,7 @@
<script
src="../../ve/ui/widgets/ve.ui.MenuSectionItemWidget.js"></script>
<script src="../../ve/ui/widgets/ve.ui.MenuWidget.js"></script>
<script
src="../../ve/ui/widgets/ve.ui.PendingInputWidget.js"></script>
+ <script
src="../../ve/ui/widgets/ve.ui.LookupInputWidget.js"></script>
<script
src="../../ve/ui/widgets/ve.ui.TextInputMenuWidget.js"></script>
<script
src="../../ve/ui/widgets/ve.ui.LinkTargetInputWidget.js"></script>
<script
src="../../ve/ui/widgets/ve.ui.MWLinkTargetInputWidget.js"></script>
diff --git a/modules/ve/ui/widgets/ve.ui.InputLabelWidget.js
b/modules/ve/ui/widgets/ve.ui.InputLabelWidget.js
index 31107ad..0393317 100644
--- a/modules/ve/ui/widgets/ve.ui.InputLabelWidget.js
+++ b/modules/ve/ui/widgets/ve.ui.InputLabelWidget.js
@@ -58,7 +58,7 @@
*/
ve.ui.InputLabelWidget.prototype.onClick = function () {
if ( !this.disabled && this.input ) {
- this.input.$.focus();
+ this.input.$input.focus();
}
return false;
};
diff --git a/modules/ve/ui/widgets/ve.ui.InputWidget.js
b/modules/ve/ui/widgets/ve.ui.InputWidget.js
index 85d949e..e6ca910 100644
--- a/modules/ve/ui/widgets/ve.ui.InputWidget.js
+++ b/modules/ve/ui/widgets/ve.ui.InputWidget.js
@@ -52,7 +52,6 @@
/**
* @event change
* @param value
- * @param origin
*/
/**
diff --git a/modules/ve/ui/widgets/ve.ui.LookupInputWidget.js
b/modules/ve/ui/widgets/ve.ui.LookupInputWidget.js
new file mode 100644
index 0000000..c72354d
--- /dev/null
+++ b/modules/ve/ui/widgets/ve.ui.LookupInputWidget.js
@@ -0,0 +1,226 @@
+/*!
+ * VisualEditor UserInterface LookupInputWidget class.
+ *
+ * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+
+/**
+ * Lookup input widget.
+ *
+ * Mixin that adds a menu showing suggested values to a text input. Subclasses
must handle `select`
+ * events on #lookupMenu to make use of selections.
+ *
+ * @class
+ * @abstract
+ *
+ * @constructor
+ * @param {ve.ui.TextInputWidget} input Input widget
+ * @param {Object} [config] Config options
+ * @cfg {jQuery} [$overlay=this.$$( 'body' )] Element to append menu to
+ */
+ve.ui.LookupInputWidget = function VeUiLookupInputWidget( input, config ) {
+ // Config intialization
+ config = config || {};
+
+ // Properties
+ this.lookupInput = input;
+ this.$overlay = config.$overlay || this.$$( 'body' );
+ this.lookupMenu = new ve.ui.TextInputMenuWidget( this, {
+ '$$': ve.ui.get$$( this.$overlay ),
+ 'input': this.lookupInput,
+ '$container': config.$container
+ } );
+ this.lookupCache = {};
+ this.lookupQuery = null;
+ this.lookupRequest = null;
+
+ // Events
+ this.$overlay.append( this.lookupMenu.$ );
+
+ this.lookupInput.$input.on( {
+ 'focus': ve.bind( this.onLookupInputFocus, this ),
+ 'blur': ve.bind( this.onLookupInputBlur, this ),
+ 'mousedown': ve.bind( this.onLookupInputMouseDown, this )
+ } );
+ this.lookupInput.addListenerMethod( this, 'change',
'onLookupInputChange' );
+
+ // Initialization
+ this.$.addClass( 've-ui-lookupWidget' );
+ this.lookupMenu.$.addClass( 've-ui-lookupWidget-menu' );
+};
+
+/* Methods */
+
+/**
+ * Handle input focus event.
+ *
+ * @method
+ * @param {jQuery.Event} e Input focus event
+ */
+ve.ui.LookupInputWidget.prototype.onLookupInputFocus = function () {
+ this.openLookupMenu();
+};
+
+/**
+ * Handle input blur event.
+ *
+ * @method
+ * @param {jQuery.Event} e Input blur event
+ */
+ve.ui.LookupInputWidget.prototype.onLookupInputBlur = function () {
+ this.lookupMenu.hide();
+};
+
+/**
+ * Handle input mouse down event.
+ *
+ * @method
+ * @param {jQuery.Event} e Input mouse down event
+ */
+ve.ui.LookupInputWidget.prototype.onLookupInputMouseDown = function () {
+ this.openLookupMenu();
+};
+
+/**
+ * Handle input change event.
+ *
+ * @method
+ * @param {string} value New input value
+ */
+ve.ui.LookupInputWidget.prototype.onLookupInputChange = function () {
+ this.openLookupMenu();
+};
+
+/**
+ * Open the menu.
+ *
+ * @method
+ * @chainable
+ */
+ve.ui.LookupInputWidget.prototype.openLookupMenu = function () {
+ var value = this.lookupInput.getValue();
+
+ if ( value.length && $.trim( value ) !== '' ) {
+ this.populateLookupMenu();
+ if ( !this.lookupMenu.isVisible() ) {
+ this.lookupMenu.show();
+ }
+ } else {
+ this.lookupMenu.hide();
+ }
+
+ return this;
+};
+
+/**
+ * Populate lookup menu with current information.
+ *
+ * @method
+ * @chainable
+ */
+ve.ui.LookupInputWidget.prototype.populateLookupMenu = function () {
+ var items = this.getLookupMenuItems();
+
+ this.lookupMenu.clearItems();
+
+ if ( items.length ) {
+ this.lookupMenu.addItems( items );
+ this.initializeLookupMenuSelection();
+ }
+
+ return this;
+};
+
+/**
+ * Set selection in the lookup menu with current information.
+ *
+ * @method
+ * @chainable
+ */
+ve.ui.LookupInputWidget.prototype.initializeLookupMenuSelection = function () {
+ if ( !this.lookupMenu.getSelectedItem() ) {
+ this.lookupMenu.selectItem(
this.lookupMenu.getClosestSelectableItem( 0 ), true );
+ }
+ this.lookupMenu.highlightItem( this.lookupMenu.getSelectedItem() );
+};
+
+/**
+ * Get lookup menu items for the current query.
+ *
+ * @method
+ * @returns {ve.ui.MenuItemWidget[]} Menu items
+ */
+ve.ui.LookupInputWidget.prototype.getLookupMenuItems = function () {
+ var value = this.lookupInput.getValue();
+
+ if ( value && value !== this.lookupQuery ) {
+ // Abort current request if query has changed
+ if ( this.lookupRequest ) {
+ this.lookupRequest.abort();
+ this.lookupQuery = null;
+ this.lookupRequest = null;
+ }
+ if ( value in this.lookupCache ) {
+ return this.getLookupMenuItemsFromData(
this.lookupCache[value] );
+ } else {
+ this.lookupQuery = value;
+ this.lookupRequest = this.getLookupRequest()
+ .always( ve.bind( function () {
+ this.lookupQuery = null;
+ this.lookupRequest = null;
+ }, this ) )
+ .done( ve.bind( function ( data ) {
+ this.lookupCache[value] =
this.getLookupCacheItemFromData( data );
+ this.openLookupMenu();
+ }, this ) );
+ // Support pending input widgets
+ if ( ve.isMixedIn( this.lookupInput,
ve.ui.PendingInputWidget ) ) {
+ this.pushPending();
+ this.lookupRequest.always( ve.bind( function ()
{
+ this.popPending();
+ }, this ) );
+ }
+ }
+ }
+ return [];
+};
+
+/**
+ * Get a new request object of the current lookup query value.
+ *
+ * @method
+ * @abstract
+ * @returns {jQuery.Deferred} Deferred object
+ */
+ve.ui.LookupInputWidget.prototype.getLookupRequest = function () {
+ // Stub, implemented in subclass
+ return null;
+};
+
+/**
+ * Handle successful lookup request.
+ *
+ * Overriding methods should call #populateLookupMenu when results are
available and cache results
+ * for future lookups in #lookupCache as an array of #ve.ui.MenuItemWidget
objects.
+ *
+ * @method
+ * @abstract
+ * @param {Mixed} data Response from server
+ */
+ve.ui.LookupInputWidget.prototype.onLookupRequestDone = function () {
+ // Stub, implemented in subclass
+};
+
+/**
+ * Get a list of menu item widgets from the data stored by the lookup
request's done handler.
+ *
+ * @method
+ * @abstract
+ * @param {Mixed} data Cached result data, usually an array
+ * @returns {ve.ui.MenuItemWidget[]} Menu items
+ */
+ve.ui.LookupInputWidget.prototype.getLookupMenuItemsFromData = function () {
+ // Stub, implemented in subclass
+ return [];
+};
diff --git a/modules/ve/ui/widgets/ve.ui.MWLinkTargetInputWidget.js
b/modules/ve/ui/widgets/ve.ui.MWLinkTargetInputWidget.js
index b404f7e..955d4af 100644
--- a/modules/ve/ui/widgets/ve.ui.MWLinkTargetInputWidget.js
+++ b/modules/ve/ui/widgets/ve.ui.MWLinkTargetInputWidget.js
@@ -12,6 +12,8 @@
*
* @class
* @extends ve.ui.LinkTargetInputWidget
+ * @mixins ve.ui.PendingInputWidget
+ * @mixins ve.ui.LookupInputWidget
*
* @constructor
* @param {Object} [config] Config options
@@ -24,32 +26,16 @@
// Parent constructor
ve.ui.LinkTargetInputWidget.call( this, config );
- // Properties
- this.$overlay = config.$overlay || this.$$( 'body' );
- this.menu = new ve.ui.TextInputMenuWidget(
- this, { '$$': ve.ui.get$$( this.$overlay ), 'input': this }
- );
- this.annotation = null;
- this.existingPages = {};
- this.matchingPages = {};
- this.existingPagesQuery = null;
- this.existingPagesRequest = null;
- this.matchingPagesQuery = null;
- this.matchingPagesRequest = null;
- this.previousMatches = null;
+ // Mixin constructors
+ ve.ui.PendingInputWidget.call( this );
+ ve.ui.LookupInputWidget.call( this, this, config );
// Events
- this.$overlay.append( this.menu.$ );
- this.$input.on( {
- 'click': ve.bind( this.onClick, this ),
- 'focus': ve.bind( this.onFocus, this ),
- 'blur': ve.bind( this.onBlur, this )
- } );
- this.menu.on( 'select', ve.bind( this.onMenuItemSelect, this ) );
- this.addListenerMethods( this, {'change': 'onChange'} );
+ this.lookupMenu.addListenerMethod( this, 'select',
'onLookupMenuItemSelect' );
+
// Initialization
this.$.addClass( 've-ui-mwLinkTargetInputWidget' );
- this.menu.$.addClass( 've-ui-mwLinkTargetInputWidget-menu' );
+ this.lookupMenu.$.addClass( 've-ui-mwLinkTargetInputWidget-menu' );
};
/* Inheritance */
@@ -57,64 +43,117 @@
ve.inheritClass( ve.ui.MWLinkTargetInputWidget, ve.ui.LinkTargetInputWidget );
ve.mixinClass( ve.ui.MWLinkTargetInputWidget, ve.ui.PendingInputWidget );
+ve.mixinClass( ve.ui.MWLinkTargetInputWidget, ve.ui.LookupInputWidget );
/* Methods */
/**
- * Handles click events.
+ * Handle menu item select event.
*
* @method
- * @param {jQuery.Event} e Mouse click event
+ * @param {ve.ui.MenuItemWidget} item Selected item
*/
-ve.ui.MWLinkTargetInputWidget.prototype.onClick = function () {
- if ( !this.disabled ) {
- this.openMenu();
- }
-};
-
-/**
- * Handles focus events.
- *
- * @method
- * @param {jQuery.Event} e Input focus event
- */
-ve.ui.MWLinkTargetInputWidget.prototype.onFocus = function () {
- if ( !this.disabled ) {
- this.openMenu();
- }
-};
-
-/**
- * Handles blur events.
- *
- * @method
- * @param {jQuery.Event} e Input blur
- */
-ve.ui.MWLinkTargetInputWidget.prototype.onBlur = function () {
- this.menu.hide();
-};
-
-/**
- * Handles change events.
- *
- * @method
- * @param {ve.ui.MenuItemWidget} item Menu item
- */
-ve.ui.MWLinkTargetInputWidget.prototype.onMenuItemSelect = function ( item ) {
+ve.ui.MWLinkTargetInputWidget.prototype.onLookupMenuItemSelect = function (
item ) {
if ( item ) {
this.setAnnotation( item.getData() );
}
};
/**
- * Opens the suggestion menu on input change.
- *
+ * Gets a new request object of the current lookup query value.
*
* @method
- * @param {string} value New value
+ * @returns {jQuery.Deferred} Deferred object with success and fail handlers
already attached
*/
-ve.ui.MWLinkTargetInputWidget.prototype.onChange = function () {
- this.openMenu();
+ve.ui.MWLinkTargetInputWidget.prototype.getLookupRequest = function () {
+ return $.ajax( {
+ 'url': mw.util.wikiScript( 'api' ),
+ 'data': {
+ 'format': 'json',
+ 'action': 'opensearch',
+ 'search': this.value,
+ 'namespace': 0,
+ 'suggest': ''
+ },
+ 'dataType': 'json'
+ } );
+};
+
+/**
+ * Get lookup cache item from server response data.
+ *
+ * @method
+ * @param {Mixed} data Response from server
+ */
+ve.ui.MWLinkTargetInputWidget.prototype.getLookupCacheItemFromData = function
( data ) {
+ return ve.isArray( data ) && data.length ? data[1] : [];
+};
+
+/**
+ * Get list of menu items from a server response.
+ *
+ * @param {Object} data Query result
+ * @returns {ve.ui.MenuItemWidget[]} Menu items
+ */
+ve.ui.MWLinkTargetInputWidget.prototype.getLookupMenuItemsFromData = function
( data ) {
+ var i, len,
+ menu$$ = this.lookupMenu.$$,
+ items = [],
+ matchingPages = data,
+ pageExists = this.value in matchingPages;
+
+ // External link
+ if ( ve.init.platform.getExternalLinkUrlProtocolsRegExp().test(
this.value ) ) {
+ items.push( new ve.ui.MenuSectionItemWidget(
+ 'externalLink',
+ { '$$': menu$$, 'label': ve.msg(
'visualeditor-linkinspector-suggest-external-link' ) }
+ ) );
+ items.push( new ve.ui.MenuItemWidget(
+ this.getExternalLinkAnnotationFromUrl( this.value ),
+ { '$$': menu$$, 'rel': 'externalLink', 'label':
this.value }
+ ) );
+ }
+
+ // Internal link
+ if ( !pageExists && ( !matchingPages || matchingPages.indexOf(
this.value ) === -1 ) ) {
+ items.push( new ve.ui.MenuSectionItemWidget(
+ 'newPage',
+ { '$$': menu$$, 'label': ve.msg(
'visualeditor-linkinspector-suggest-new-page' ) }
+ ) );
+ items.push( new ve.ui.MenuItemWidget(
+ this.getInternalLinkAnnotationFromTitle( this.value ),
+ { '$$': menu$$, 'rel': 'newPage', 'label': this.value }
+ ) );
+ }
+
+ // Matching pages
+ if ( matchingPages && matchingPages.length ) {
+ items.push( new ve.ui.MenuSectionItemWidget(
+ 'matchingPages',
+ { '$$': menu$$, 'label': ve.msg(
'visualeditor-linkinspector-suggest-matching-page' ) }
+ ) );
+ for ( i = 0, len = matchingPages.length; i < len; i++ ) {
+ items.push( new ve.ui.MenuItemWidget(
+ this.getInternalLinkAnnotationFromTitle(
matchingPages[i] ),
+ { '$$': menu$$, 'rel': 'matchingPage', 'label':
matchingPages[i] }
+ ) );
+ }
+ }
+
+ return items;
+};
+
+/**
+ * Set selection in the lookup menu with current information.
+ *
+ * @method
+ * @chainable
+ */
+ve.ui.MWLinkTargetInputWidget.prototype.initializeLookupMenuSelection =
function () {
+ // Attempt to maintain selection on current annotation
+ this.lookupMenu.selectItem( this.lookupMenu.getItemFromData(
this.annotation ), true );
+ // Parent method
+ ve.ui.LookupInputWidget.prototype.initializeLookupMenuSelection.call(
this );
};
/**
@@ -128,93 +167,6 @@
ve.ui.MWLinkTargetInputWidget.prototype.setValue = function ( value ) {
// Keep annotation in sync with value, call parent method.
ve.ui.TextInputWidget.prototype.setValue.call( this, value );
-};
-
-/**
- * Opens the menu.
- *
- * @method
- * @chainable
- */
-ve.ui.MWLinkTargetInputWidget.prototype.openMenu = function () {
- this.populateMenu();
- this.queryPageExistence();
- this.queryMatchingPages();
- if ( this.value.length && $.trim( this.value ) !== '' &&
!this.menu.isVisible() ) {
- this.menu.show();
- }
- return this;
-};
-
-/**
- * Populates the menu.
- *
- * @method
- * @chainable
- */
-ve.ui.MWLinkTargetInputWidget.prototype.populateMenu = function () {
- var i, len,
- menu$$ = this.menu.$$,
- items = [],
- pageExists = this.existingPages[this.value],
- matchingPages = this.matchingPages[this.value];
-
- // Reset
- this.menu.clearItems();
-
- // Hide on empty target
- if ( !this.value.length ) {
- this.menu.hide();
- return this;
- }
-
- // External link
- if ( ve.init.platform.getExternalLinkUrlProtocolsRegExp().test(
this.value ) ) {
- items.push( new ve.ui.MenuSectionItemWidget(
- 'externalLink', { '$$': menu$$, 'label': ve.msg(
'visualeditor-linkinspector-suggest-external-link' ) }
- ) );
- items.push( new ve.ui.MenuItemWidget(
- this.getExternalLinkAnnotationFromUrl( this.value ),
- { '$$': menu$$, 'rel': 'externalLink', 'label':
this.value }
- ) );
- }
-
- // Internal link
- if ( !pageExists && ( !matchingPages || matchingPages.indexOf(
this.value ) === -1 ) ) {
- items.push( new ve.ui.MenuSectionItemWidget(
- 'newPage', { '$$': menu$$, 'label': ve.msg(
'visualeditor-linkinspector-suggest-new-page' ) }
- ) );
- items.push( new ve.ui.MenuItemWidget(
- this.getInternalLinkAnnotationFromTitle( this.value ),
- { '$$': menu$$, 'rel': 'newPage', 'label': this.value }
- ) );
- }
-
- // Matching pages
- if ( matchingPages && matchingPages.length ) {
- items.push( new ve.ui.MenuSectionItemWidget(
- 'matchingPages', { '$$': menu$$, 'label': ve.msg(
'visualeditor-linkinspector-suggest-matching-page' ) }
- ) );
- for ( i = 0, len = matchingPages.length; i < len; i++ ) {
- items.push( new ve.ui.MenuItemWidget(
- this.getInternalLinkAnnotationFromTitle(
matchingPages[i] ),
- { '$$': menu$$, 'rel': 'matchingPage', 'label':
matchingPages[i] }
- ) );
- }
- this.previousMatches = matchingPages;
- }
-
- // Add items
- this.menu.addItems( items );
-
- // Auto-select
- this.menu.selectItem( this.menu.getItemFromData( this.annotation ),
true );
- if ( !this.menu.getSelectedItem() ) {
- this.menu.selectItem( this.menu.getClosestSelectableItem( 0 ),
true );
- }
- this.menu.highlightItem( this.menu.getSelectedItem() );
-
- return this;
};
/**
@@ -273,115 +225,4 @@
return annotation.getAttribute( 'title' );
}
return '';
-};
-
-/**
- * Checks page existence for the current value.
- *
- * {ve.ui.MWLinkTargetInputWidget.populateMenu} will be called immediately if
the page existence has
- * been cached, or as soon as the API returns a result.
- *
- * @method
- * @chainable
- */
-ve.ui.MWLinkTargetInputWidget.prototype.queryPageExistence = function () {
- if ( this.existingPagesQuery === this.value ) {
- // Ignore duplicate requests
- return;
- }
- if ( this.existingPagesRequest ) {
- this.existingPagesRequest.abort();
- this.existingPagesQuery = null;
- this.existingPagesRequest = null;
- }
- if ( this.value in this.existingPages ) {
- this.populateMenu();
- } else {
- this.pushPending();
- this.existingPagesQuery = this.value;
- this.existingPagesRequest = $.ajax( {
- 'url': mw.util.wikiScript( 'api' ),
- 'data': {
- 'format': 'json',
- 'action': 'query',
- 'indexpageids': '',
- 'titles': this.value,
- 'converttitles': ''
- },
- 'dataType': 'json',
- 'success': ve.bind( function ( data ) {
- this.existingPagesQuery = null;
- this.existingPagesRequest = null;
- var page,
- exists = false;
- if ( data.query ) {
- page =
data.query.pages[data.query.pageids[0]];
- exists = ( page.missing === undefined
&& page.invalid === undefined );
- // Cache result for normalized title
- this.existingPages[page.title] = exists;
- }
- // Cache result for original input
- this.existingPages[this.value] = exists;
- this.populateMenu();
- }, this ),
- 'complete': ve.bind( function () {
- this.popPending();
- }, this )
- } );
- }
- return this;
-};
-
-/**
- * Checks matching pages for the current value.
- *
- * {ve.ui.MWLinkTargetInputWidget.populateMenu} will be called immediately if
matching pages have
- * been cached, or as soon as the API returns a result.
- *
- * @method
- * @chainable
- */
-ve.ui.MWLinkTargetInputWidget.prototype.queryMatchingPages = function () {
- if ( this.matchingPagesQuery === this.value ) {
- // Ignore duplicate requests
- return;
- }
- if ( this.matchingPagesRequest ) {
- this.matchingPagesRequest.abort();
- this.matchingPagesQuery = null;
- this.matchingPagesRequest = null;
- }
- if ( this.value in this.matchingPages ) {
- this.populateMenu();
- } else {
- this.pushPending();
- this.matchingPagesQuery = this.value;
- this.matchingPagesRequest = $.ajax( {
- 'url': mw.util.wikiScript( 'api' ),
- 'data': {
- 'format': 'json',
- 'action': 'opensearch',
- 'search': this.value,
- 'namespace': 0,
- 'suggest': ''
- },
- 'dataType': 'json',
- 'success': ve.bind( function ( data ) {
- this.matchingPagesQuery = null;
- this.matchingPagesRequest = null;
- if ( ve.isArray( data ) && data.length ) {
- // Cache the matches to the query
- this.matchingPages[this.value] =
data[1];
- this.populateMenu();
- } else {
- // Don't repeat queries that resulted
in invalid responses
- this.matchingPages[this.value] = [];
- }
- }, this ),
- 'complete': ve.bind( function () {
- this.popPending();
- }, this )
- } );
- }
- return this;
};
diff --git a/modules/ve/ui/widgets/ve.ui.MenuWidget.js
b/modules/ve/ui/widgets/ve.ui.MenuWidget.js
index 2be85d7..25332e8 100644
--- a/modules/ve/ui/widgets/ve.ui.MenuWidget.js
+++ b/modules/ve/ui/widgets/ve.ui.MenuWidget.js
@@ -24,7 +24,7 @@
// Properties
this.newItems = [];
- this.$input = config.input ? config.input.$ : this.$$( '<input>' );
+ this.$input = config.input ? config.input.$input : this.$$( '<input>' );
this.$previousFocus = null;
this.isolated = !config.input;
this.visible = false;
diff --git a/modules/ve/ui/widgets/ve.ui.TextInputMenuWidget.js
b/modules/ve/ui/widgets/ve.ui.TextInputMenuWidget.js
index 62ce685..2ee7a91 100644
--- a/modules/ve/ui/widgets/ve.ui.TextInputMenuWidget.js
+++ b/modules/ve/ui/widgets/ve.ui.TextInputMenuWidget.js
@@ -14,6 +14,7 @@
* @constructor
* @param {ve.ui.TextInputWidget} input Text input widget to provide menu for
* @param {Object} [config] Config options
+ * @cfg {jQuery} [$container=input.$] Element to render menu under
*/
ve.ui.TextInputMenuWidget = function VeUiTextInputMenuWidget( input, config ) {
// Parent constructor
@@ -21,6 +22,8 @@
// Properties
this.input = input;
+ this.$container = config.$container || this.input.$;
+ this.onWindowResizeHandler = ve.bind( this.onWindowResize, this );
// Initialization
this.$.addClass( 've-ui-textInputMenuWidget' );
@@ -30,6 +33,18 @@
ve.inheritClass( ve.ui.TextInputMenuWidget, ve.ui.MenuWidget );
+/* Methods */
+
+/**
+ * Handle window resize event.
+ *
+ * @method
+ * @param {jQuery.Event} e Window resize event
+ */
+ve.ui.TextInputMenuWidget.prototype.onWindowResize = function () {
+ this.position();
+};
+
/**
* Shows the menu.
*
@@ -37,21 +52,50 @@
* @chainable
*/
ve.ui.TextInputMenuWidget.prototype.show = function () {
- var dim, offset,
- $input = this.input.$input;
-
- // Call parent method
+ // Parent method
ve.ui.MenuWidget.prototype.show.call( this );
- // Position under input
- dim = $input.offset();
- dim.top += $input.outerHeight( true );
- if ( this.input.$$.frame ) {
- offset = this.input.$$.frame.$.offset();
- dim.left += offset.left;
- dim.top += offset.top;
- }
- this.$.css( dim );
+ this.position();
+ $( window ).on( 'resize', this.onWindowResizeHandler );
+ return this;
+};
+/**
+ * Hides the menu.
+ *
+ * @method
+ * @chainable
+ */
+ve.ui.TextInputMenuWidget.prototype.hide = function () {
+ // Parent method
+ ve.ui.MenuWidget.prototype.hide.call( this );
+
+ $( window ).off( 'resize', this.onWindowResizeHandler );
+ return this;
+};
+
+/**
+ * Positions the menu.
+ *
+ * @method
+ * @chainable
+ */
+ve.ui.TextInputMenuWidget.prototype.position = function () {
+ var frameOffset,
+ $container = this.$container,
+ dimensions = $container.offset();
+
+ // Position under input
+ dimensions.top += $container.outerHeight( true );
+ dimensions.width = $container.outerWidth( true );
+
+ // Compensate for frame position if in a differnt frame
+ if ( this.input.$$.frame && this.input.$$.context !==
this.$[0].ownerDocument ) {
+ frameOffset = this.input.$$.frame.$.offset();
+ dimensions.left += frameOffset.left;
+ dimensions.top += frameOffset.top;
+ }
+
+ this.$.css( dimensions );
return this;
};
diff --git a/modules/ve/ui/widgets/ve.ui.TextInputWidget.js
b/modules/ve/ui/widgets/ve.ui.TextInputWidget.js
index 7169e16..c1f7692 100644
--- a/modules/ve/ui/widgets/ve.ui.TextInputWidget.js
+++ b/modules/ve/ui/widgets/ve.ui.TextInputWidget.js
@@ -13,6 +13,7 @@
*
* @constructor
* @param {Object} [config] Config options
+ * @cfg {string} [placeholder] Placeholder text
*/
ve.ui.TextInputWidget = function VeUiTextInputWidget( config ) {
// Parent constructor
@@ -20,6 +21,9 @@
// Initialization
this.$.addClass( 've-ui-textInputWidget' );
+ if ( config.placeholder ) {
+ this.$input.attr( 'placeholder', config.placeholder );
+ }
};
/* Inheritance */
--
To view, visit https://gerrit.wikimedia.org/r/61470
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: If5ed1b64fd15982807691ce8bb0362970633108a
Gerrit-PatchSet: 5
Gerrit-Project: mediawiki/extensions/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Trevor Parscal <[email protected]>
Gerrit-Reviewer: Catrope <[email protected]>
Gerrit-Reviewer: Robmoen <[email protected]>
Gerrit-Reviewer: Trevor Parscal <[email protected]>
Gerrit-Reviewer: jenkins-bot
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits