Media: Detect when views are added to the DOM and fire a ready event. see #21390.

git-svn-id: http://core.svn.wordpress.org/trunk@22692 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Daryl Koopersmith 2012-11-20 00:53:02 +00:00
parent 48293c9384
commit 05db7000c4

View File

@ -632,7 +632,7 @@
// > When adding, to insert `views` at a specific index, use // > When adding, to insert `views` at a specific index, use
// `options.at`. By default, `views` are added to the end of the array. // `options.at`. By default, `views` are added to the end of the array.
set: function( selector, views, options ) { set: function( selector, views, options ) {
var $selector, els, existing, method; var existing, next;
if ( ! _.isString( selector ) ) { if ( ! _.isString( selector ) ) {
options = views; options = views;
@ -644,7 +644,6 @@
views = _.isArray( views ) ? views : [ views ]; views = _.isArray( views ) ? views : [ views ];
existing = this.get( selector ); existing = this.get( selector );
next = views; next = views;
method = options.add ? 'insert' : 'replace';
if ( existing ) { if ( existing ) {
if ( options.add ) { if ( options.add ) {
@ -672,9 +671,6 @@
this._views[ selector ] = next; this._views[ selector ] = next;
$selector = selector ? this.view.$( selector ) : this.view.$el;
els = _.pluck( views, 'el' );
_.each( views, function( subview ) { _.each( views, function( subview ) {
var constructor = subview.Views || media.Views, var constructor = subview.Views || media.Views,
subviews = subview.views = subview.views || new constructor( subview ); subviews = subview.views = subview.views || new constructor( subview );
@ -682,10 +678,8 @@
subviews.selector = selector; subviews.selector = selector;
}, this ); }, this );
if ( ! options.silent ) { if ( ! options.silent )
_.each( views, this._maybeRender, this ); this._attach( selector, views, _.extend({ ready: this._isReady() }, options ) );
this[ method ]( $selector, els, options );
}
return this; return this;
}, },
@ -755,16 +749,12 @@
// //
// Renders all subviews. Used in conjunction with `Views.detach()`. // Renders all subviews. Used in conjunction with `Views.detach()`.
render: function() { render: function() {
var root = this._views['']; var options = {
ready: this._isReady()
_.each( this.all(), this._maybeRender, this ); };
if ( root )
this.replace( this.view.$el, _.pluck( root, 'el' ) );
_.each( this._views, function( views, selector ) { _.each( this._views, function( views, selector ) {
if ( selector ) this._attach( selector, views, options );
this.replace( this.view.$( selector ), _.pluck( views, 'el' ) );
}, this ); }, this );
this.rendered = true; this.rendered = true;
@ -820,14 +810,71 @@
return this; return this;
}, },
// ### Trigger the ready event
//
// **Only use this method if you know what you're doing.**
// For performance reasons, this method does not check if the view is
// actually attached to the DOM. It's taking your word for it.
//
// Fires the ready event on the current view and all attached subviews.
ready: function() {
this.view.trigger('ready');
// #### Internal. Maybe render a view. // Find all attached subviews, and call ready on them.
_maybeRender: function( view ) { _.chain( this.all() ).map( function( view ) {
if ( ! view.views || view.views.rendered ) return view.views;
}).flatten().where({ attached: true }).invoke('ready');
},
// #### Internal. Attaches a series of views to a selector.
//
// Checks to see if a matching selector exists, renders the views,
// performs the proper DOM operation, and then checks if the view is
// attached to the document.
_attach: function( selector, views, options ) {
var $selector = selector ? this.view.$( selector ) : this.view.$el,
managers;
// Check if we found a location to attach the views.
if ( ! $selector.length )
return this;
managers = _.chain( views ).pluck('views').flatten().value();
// Render the views if necessary.
_.each( managers, function( manager ) {
if ( manager.rendered )
return; return;
view.render(); manager.view.render();
view.views.rendered = true; manager.rendered = true;
}, this );
// Insert or replace the views.
this[ options.add ? 'insert' : 'replace' ]( $selector, _.pluck( views, 'el' ), options );
// Set attached and trigger ready if the current view is already
// attached to the DOM.
_.each( managers, function( manager ) {
manager.attached = true;
if ( options.ready )
manager.ready();
}, this );
return this;
},
// #### Internal. Checks if the current view is in the DOM.
_isReady: function() {
var node = this.view.el;
while ( node ) {
if ( node === document.body )
return true;
node = node.parentNode;
}
return false;
} }
}); });
@ -841,6 +888,7 @@
constructor: function() { constructor: function() {
this.views = new this.Views( this, this.views ); this.views = new this.Views( this, this.views );
this.on( 'ready', this.ready, this );
Backbone.View.apply( this, arguments ); Backbone.View.apply( this, arguments );
}, },
@ -885,7 +933,9 @@
prepare: function() { prepare: function() {
return this.options; return this.options;
} },
ready: function() {}
}); });
/** /**
@ -967,6 +1017,8 @@
} }
}); });
} }
this.on( 'attach', _.bind( this.views.ready, this.views ), this );
}, },
render: function() { render: function() {
@ -1060,9 +1112,9 @@
// Map some of the modal's methods to the frame. // Map some of the modal's methods to the frame.
_.each(['open','close','attach','detach'], function( method ) { _.each(['open','close','attach','detach'], function( method ) {
media.view.MediaFrame.prototype[ method ] = function( view ) { media.view.MediaFrame.prototype[ method ] = function( view ) {
this.trigger( method );
if ( this.modal ) if ( this.modal )
this.modal[ method ].apply( this.modal, arguments ); this.modal[ method ].apply( this.modal, arguments );
this.trigger( method );
return this; return this;
}; };
}); });
@ -1592,25 +1644,25 @@
attach: function() { attach: function() {
this.$el.appendTo( this.options.container ); this.$el.appendTo( this.options.container );
this.controller.trigger( 'attach', this.controller ); this.trigger('attach');
return this; return this;
}, },
detach: function() { detach: function() {
this.$el.detach(); this.$el.detach();
this.controller.trigger( 'detach', this.controller ); this.trigger('detach');
return this; return this;
}, },
open: function() { open: function() {
this.$el.show(); this.$el.show();
this.controller.trigger( 'open', this.controller ); this.trigger('open');
return this; return this;
}, },
close: function() { close: function() {
this.$el.hide(); this.$el.hide();
this.controller.trigger( 'close', this.controller ); this.trigger('close');
return this; return this;
}, },