Trevor Parscal has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/71161


Change subject: Improve async template data loading
......................................................................

Improve async template data loading

Objective:

* Fix issue where async behavior of addTemplate caused templates to be added in 
the wrong order

Bonus:

* Get rid of special adders for transclusion parts, just construct objects 
outside and use addPart

Change-Id: Ibe579f033873446376d72d3bd1b9f92d9f361de5
---
M modules/ve/dm/models/ve.dm.MWTemplateModel.js
M modules/ve/dm/models/ve.dm.MWTemplateParameterModel.js
M modules/ve/dm/models/ve.dm.MWTransclusionModel.js
M modules/ve/ui/dialogs/ve.ui.MWTransclusionDialog.js
4 files changed, 115 insertions(+), 172 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/VisualEditor 
refs/changes/61/71161/1

diff --git a/modules/ve/dm/models/ve.dm.MWTemplateModel.js 
b/modules/ve/dm/models/ve.dm.MWTemplateModel.js
index bea88e6..52acc5a 100644
--- a/modules/ve/dm/models/ve.dm.MWTemplateModel.js
+++ b/modules/ve/dm/models/ve.dm.MWTemplateModel.js
@@ -135,32 +135,30 @@
  * Add a parameter to template.
  *
  * @method
+ * @param {ve.dm.MWTemplateParameterModel} param Parameter to add
  * @param {string} name Parameter name
  * @param {string} value Parameter value
- * @returns {ve.dm.MWTemplateParameterModel} Added param
  * @emits add
  */
-ve.dm.MWTemplateModel.prototype.addParameter = function ( name, value ) {
-       var param = new ve.dm.MWTemplateParameterModel( this, name, value );
+ve.dm.MWTemplateModel.prototype.addParameter = function ( param ) {
+       var name = param.getName();
        this.sequence = null;
        this.params[name] = param;
        this.spec.fill();
        this.emit( 'add', param );
-       return param;
 };
 
 /**
  * Remove parameter from template.
  *
  * @method
- * @param {string} name Parameter name
+ * @param {ve.dm.MWTemplateParameterModel} param Parameter to remove
  * @emits remove
  */
-ve.dm.MWTemplateModel.prototype.removeParameter = function ( name ) {
-       var param = this.params[name];
+ve.dm.MWTemplateModel.prototype.removeParameter = function ( param ) {
        if ( param ) {
                this.sequence = null;
-               delete this.params[name];
+               delete this.params[param.getName()];
                this.emit( 'remove', param );
        }
 };
diff --git a/modules/ve/dm/models/ve.dm.MWTemplateParameterModel.js 
b/modules/ve/dm/models/ve.dm.MWTemplateParameterModel.js
index 6b1c389..ed5836e 100644
--- a/modules/ve/dm/models/ve.dm.MWTemplateParameterModel.js
+++ b/modules/ve/dm/models/ve.dm.MWTemplateParameterModel.js
@@ -80,5 +80,5 @@
  * @method
  */
 ve.dm.MWTemplateParameterModel.prototype.remove = function () {
-       this.template.removeParameter( this.name );
+       this.template.removeParameter( this );
 };
diff --git a/modules/ve/dm/models/ve.dm.MWTransclusionModel.js 
b/modules/ve/dm/models/ve.dm.MWTransclusionModel.js
index de0d845..1599b1f 100644
--- a/modules/ve/dm/models/ve.dm.MWTransclusionModel.js
+++ b/modules/ve/dm/models/ve.dm.MWTransclusionModel.js
@@ -27,6 +27,7 @@
        this.parts = [];
        this.uid = 0;
        this.requests = [];
+       this.queue = [];
 };
 
 /* Inheritance */
@@ -55,8 +56,7 @@
  * @returns {jQuery.Promise} Promise, resolved when spec is loaded
  */
 ve.dm.MWTransclusionModel.prototype.load = function ( data ) {
-       var i, len, key, part, template,
-               templates = [];
+       var i, len, key, part, template;
 
        // Convert single part format to multi-part format
        if ( data.params && data.target ) {
@@ -67,50 +67,85 @@
                for ( i = 0, len = data.parts.length; i < len; i++ ) {
                        part = data.parts[i];
                        if ( part.template ) {
-                               template = this.addTemplate( 
part.template.target );
+                               template = new ve.dm.MWTemplateModel( this, 
part.template.target );
                                for ( key in part.template.params ) {
-                                       template.addParameter( key, 
part.template.params[key].wt );
+                                       template.addParameter(
+                                               new 
ve.dm.MWTemplateParameterModel(
+                                                       template, key, 
part.template.params[key].wt
+                                               )
+                                       );
                                }
-                               // Don't load specs for templates that don't 
have a resolvable target
-                               if ( part.template.target.href ) {
-                                       templates.push( template );
-                               }
+                               this.queue.push( { 'part': template } );
                        } else if ( typeof part === 'string' ) {
-                               this.addContent( part );
+                               this.queue.push( { 'part': new 
ve.dm.MWTransclusionContentModel( this, part ) } );
                        }
                }
+               setTimeout( ve.bind( this.fetch, this ) );
        }
-       // Add fetched specs to #specs store when the promise is resolved
-       return this.fetchSpecs( templates ).done( function ( specs ) {
-               ve.extendObject( specCache, specs );
-       } );
 };
 
 /**
- * Fetch template specifications from server.
+ * Process one or more queue items.
  *
- * @param {ve.dm.MWTransclusionModel[]} templates List of templates to load 
data for
- * @returns {jQuery.Promise} Promise, resolved when spec is loaded
+ * @method
+ * @param {Object[]} queue List of objects containing parts to add and 
optionally indexes to add
+ *   them at, if no index is given parts will be added at the end
+ * @emits add For each item added
  */
-ve.dm.MWTransclusionModel.prototype.fetchSpecs = function ( templates ) {
-       var i, len, title, request,
-               requests = this.requests,
-               deferred = $.Deferred(),
-               specs = {},
-               titles = [];
+ve.dm.MWTransclusionModel.prototype.process = function ( queue ) {
+       var i, len, item, title, index;
 
-       // Get unique list of titles that aren't already loaded
-       for ( i = 0, len = templates.length; i < len; i++ ) {
-               title = templates[i].getTitle();
-               if ( !specCache[title] && ve.indexOf( title, titles ) === -1 ) {
-                       titles.push( title );
+       for ( i = 0, len = queue.length; i < len; i++ ) {
+               item = queue[i];
+               if ( item.part instanceof ve.dm.MWTemplateModel ) {
+                       title = item.part.getTitle();
+                       if ( hasOwn.call( specCache, title ) && 
specCache[title] ) {
+                               item.part.getSpec().extend( specCache[title] );
+                       }
+               }
+               index = item.index === undefined ? this.parts.length : 
item.index;
+               this.parts.splice( index, 0, item.part );
+               this.emit( 'add', item.part );
+       }
+};
+
+ve.dm.MWTransclusionModel.prototype.fetch = function () {
+       if ( !this.queue.length ) {
+               return;
+       }
+
+       var i, len, item, title, request,
+               titles = [],
+               specs = {},
+               queue = this.queue.slice();
+
+       // Clear shared queue for future calls
+       this.queue.length = 0;
+
+       // Get unique list of template titles that aren't already loaded
+       for ( i = 0, len = queue.length; i < len; i++ ) {
+               item = queue[i];
+               if ( item.part instanceof ve.dm.MWTemplateModel ) {
+                       title = item.part.getTitle();
+                       if (
+                               // Skip titles that don't have a resolvable href
+                               title &&
+                               // Skip titles outside the template namespace
+                               title.charAt( 0 ) !== ':' &&
+                               // Skip already cached data
+                               !hasOwn.call( specCache, title ) &&
+                               // Skip duplicate titles in the same batch
+                               ve.indexOf( title, titles ) === -1
+                       ) {
+                               titles.push( title );
+                       }
                }
        }
 
        // Bypass server for empty lists
        if ( !titles.length ) {
-               setTimeout( deferred.reject );
-               return deferred.promise();
+               setTimeout( ve.bind( this.process, this, queue ) );
+               return;
        }
 
        // Request template specs from server
@@ -124,7 +159,7 @@
                }
        } )
                .done( function ( data ) {
-                       var i, len, id, title;
+                       var i, len, id;
 
                        if ( data && data.pages ) {
                                // Keep spec data on hand for future use
@@ -141,31 +176,28 @@
                                                }
                                        }
                                }
-                               // Load into existing templates
-                               for ( i = 0, len = templates.length; i < len; 
i++ ) {
-                                       title = templates[i].getTitle();
-                                       if ( hasOwn.call( specs, title ) ) {
-                                               templates[i].getSpec().extend( 
specs[title] );
+                               // Prevent asking again for templates that have 
no specs
+                               for ( i = 0, len = titles.length; i < len; i++ 
) {
+                                       title = titles[i];
+                                       if ( !specs[title] ) {
+                                               specs[title] = null;
                                        }
                                }
-                               deferred.resolve( specs );
-                       } else {
-                               deferred.reject( 'unavailable', arguments );
-                       }
-               } )
-               .fail( function () {
-                       deferred.reject( 'http', arguments );
-               } )
-               .always( function () {
-                       // Prune requests when complete
-                       var index = requests.indexOf( request );
-                       if ( index !== -1 ) {
-                               requests.splice( index, 1 );
-                       }
-               } );
-       requests.push( request );
 
-       return deferred.promise();
+                               ve.extendObject( specCache, specs );
+                       }
+               } )
+               .always( ve.bind( function () {
+                       // Prune completed request
+                       var index = ve.indexOf( request, this.requests );
+                       if ( index !== -1 ) {
+                               this.requests.splice( index, 1 );
+                       }
+                       // Actually add queued items
+                       this.process( queue );
+               }, this ) );
+
+       this.requests.push( request );
 };
 
 /**
@@ -231,78 +263,23 @@
 };
 
 /**
- * Add content part.
- *
- * @method
- * @param {string} value Content value
- * @param {number} [index] Specific index to add content at
- * @returns {ve.dm.MWTransclusionContentModel} Added content part
- * @emits add
- */
-ve.dm.MWTransclusionModel.prototype.addContent = function ( value, index ) {
-       var part = new ve.dm.MWTransclusionContentModel( this, value );
-       this.addPart( part, index );
-       return part;
-};
-
-/**
- * Add template part.
- *
- * Templates are added asynchronously.
- *
- * @method
- * @param {Object} target Template target
- * @param {string} target.wt Original wikitext of target
- * @param {string} [target.href] Hypertext reference to target
- * @param {number} [index] Specific index to add template at
- * @returns {ve.dm.MWTemplateModel} Added template part
- * @emits add
- */
-ve.dm.MWTransclusionModel.prototype.addTemplate = function ( target, index ) {
-       var part = new ve.dm.MWTemplateModel( this, target ),
-               title = part.getTitle(),
-               finish = ve.bind( this.addPart, this, part, index );
-
-       if ( hasOwn.call( specCache, title ) ) {
-               part.getSpec().extend( specCache[title] );
-               setTimeout( finish );
-       } else {
-               // Add fetched specs to #specs store when the promise is 
resolved
-               this.fetchSpecs( [ part ] )
-                       .done( function ( specs ) {
-                               ve.extendObject( specCache, specs );
-                       } )
-                       .always( finish );
-       }
-       return part;
-};
-
-/**
- * Add template placeholder part.
- *
- * @method
- * @param {number} [index] Specific index to add placeholder at
- * @returns {ve.dm.MWTransclusionModel} Added template part
- * @emits add
- */
-ve.dm.MWTransclusionModel.prototype.addPlaceholder = function ( index ) {
-       var part = new ve.dm.MWTemplatePlaceholderModel( this );
-
-       this.addPart( part, index );
-       return part;
-};
-
-/**
  * Add part.
+ * 
+ * Added asynchronously, but order is preserved.
  *
  * @method
  * @param {ve.dm.MWTransclusionPartModel} part Part to add
- * @param {number} [index] Specific index to add content at
- * @emits add
+ * @param {number} [index] Specific index to add content at, defaults to the 
end
+ * @throws {Error} If part is not valid
  */
 ve.dm.MWTransclusionModel.prototype.addPart = function ( part, index ) {
-       this.parts.splice( index === undefined ? this.parts.length : index, 0, 
part );
-       this.emit( 'add', part );
+       if ( !( part instanceof ve.dm.MWTransclusionPartModel ) ) {
+               throw new Error( 'Invalid transclusion part' );
+       }
+       this.queue.push( { 'part': part, 'index': index } );
+       // Fetch on next yeild to process items in the queue together, 
subsequent calls to fetch will
+       // have no effect because the queue will be clear
+       setTimeout( ve.bind( this.fetch, this ) );
 };
 
 /**
diff --git a/modules/ve/ui/dialogs/ve.ui.MWTransclusionDialog.js 
b/modules/ve/ui/dialogs/ve.ui.MWTransclusionDialog.js
index 1583a78..2b3b813 100644
--- a/modules/ve/ui/dialogs/ve.ui.MWTransclusionDialog.js
+++ b/modules/ve/ui/dialogs/ve.ui.MWTransclusionDialog.js
@@ -80,13 +80,14 @@
        // Properties
        this.transclusion = new ve.dm.MWTransclusionModel();
 
+       // Events
+       this.transclusion.connect( this, { 'add': 'onAddPart', 'remove': 
'onRemovePart' } );
+
        // Initialization
        if ( this.node instanceof ve.ce.MWTransclusionNode ) {
-               this.transclusion.load( ve.copyObject( 
this.node.getModel().getAttribute( 'mw' ) ) )
-                       .always( ve.bind( this.setupPages, this ) );
+               this.transclusion.load( ve.copyObject( 
this.node.getModel().getAttribute( 'mw' ) ) );
        } else {
-               this.transclusion.addPlaceholder();
-               this.setupPages();
+               this.transclusion.addPart( new 
ve.dm.MWTemplatePlaceholderModel( this.transclusion ) );
        }
 };
 
@@ -249,11 +250,13 @@
 
        switch ( type ) {
                case 'content':
-                       part = this.transclusion.addContent( '', 
this.getPartInsertionIndex() );
+                       part = new ve.dm.MWTransclusionContentModel( 
this.transclusion, '' );
+                       this.transclusion.addPart( part, 
this.getPartInsertionIndex() );
                        this.setPageByName( part.getId() );
                        break;
                case 'template':
-                       part = this.transclusion.addPlaceholder( 
this.getPartInsertionIndex() );
+                       part = new ve.dm.MWTemplatePlaceholderModel( 
this.transclusion );
+                       this.transclusion.addPart( part, 
this.getPartInsertionIndex() );
                        this.setPageByName( part.getId() );
                        break;
        }
@@ -321,43 +324,6 @@
 };
 
 /**
- * Synchronize pages with transclusion.
- *
- * @method
- */
-ve.ui.MWTransclusionDialog.prototype.setupPages = function () {
-       // Build pages from parts
-       var i, iLen, j, jLen, part, param, names,
-               parts = this.transclusion.getParts();
-
-       // Populate pages
-       for ( i = 0, iLen = parts.length; i < iLen; i++ ) {
-               part = parts[i];
-               if ( part instanceof ve.dm.MWTemplateModel ) {
-                       // Add template page
-                       this.addPage( part.getId(), this.getTemplatePage( part 
) );
-                       // Listen for changes to parameters
-                       part.connect( this, { 'add': 'onAddParameter', 
'remove': 'onRemoveParameter' } );
-                       // Add parameter pages
-                       names = part.getParameterNames();
-                       for ( j = 0, jLen = names.length; j < jLen; j++ ) {
-                               param = part.getParameter( names[j] );
-                               this.addPage( param.getId(), 
this.getParameterPage( param ) );
-                       }
-               } else if ( part instanceof ve.dm.MWTransclusionContentModel ) {
-                       // Add wikitext page
-                       this.addPage( part.getId(), this.getContentPage( part ) 
);
-               } else if ( part instanceof ve.dm.MWTemplatePlaceholderModel ) {
-                       // Add template placeholder page
-                       this.addPage( part.getId(), this.getPlaceholderPage( 
part ) );
-               }
-       }
-
-       // Listen for changes to parts
-       this.transclusion.connect( this, { 'add': 'onAddPart', 'remove': 
'onRemovePart' } );
-};
-
-/**
  * Get page for transclusion content.
  *
  * @method
@@ -418,7 +384,8 @@
                description = spec.getDescription();
 
        function addParameter() {
-               var param = template.addParameter( addParameterInput.getValue() 
);
+               var param = new ve.dm.MWTemplateParameterModel( template, 
addParameterInput.getValue() );
+               template.addParameter( param );
                addParameterInput.setValue();
                this.setPageByName( param.getId() );
        }
@@ -568,7 +535,8 @@
                }
 
                target = { 'href': new mw.Title( href ).getPrefixedText(), 
'wt': value };
-               part = this.transclusion.addTemplate( target, ve.indexOf( 
placeholder, parts ) );
+               part = new ve.dm.MWTemplateModel( this.transclusion, target );
+               this.transclusion.addPart( part, ve.indexOf( placeholder, parts 
) );
                this.pending.push( { 'part': part, 'placeholder': placeholder } 
);
                addTemplateInput.pushPending();
                addTemplateButton.setDisabled( true );

-- 
To view, visit https://gerrit.wikimedia.org/r/71161
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ibe579f033873446376d72d3bd1b9f92d9f361de5
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Trevor Parscal <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to