balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

wp-backbone.js (15240B)


      1 /**
      2  * @output wp-includes/js/wp-backbone.js
      3  */
      4 
      5 /** @namespace wp */
      6 window.wp = window.wp || {};
      7 
      8 (function ($) {
      9 	/**
     10 	 * Create the WordPress Backbone namespace.
     11 	 *
     12 	 * @namespace wp.Backbone
     13 	 */
     14 	wp.Backbone = {};
     15 
     16 	/**
     17 	 * A backbone subview manager.
     18 	 *
     19 	 * @since 3.5.0
     20 	 * @since 3.6.0 Moved wp.media.Views to wp.Backbone.Subviews.
     21 	 *
     22 	 * @memberOf wp.Backbone
     23 	 *
     24 	 * @class
     25 	 *
     26 	 * @param {wp.Backbone.View} view  The main view.
     27 	 * @param {Array|Object}     views The subviews for the main view.
     28 	 */
     29 	wp.Backbone.Subviews = function( view, views ) {
     30 		this.view = view;
     31 		this._views = _.isArray( views ) ? { '': views } : views || {};
     32 	};
     33 
     34 	wp.Backbone.Subviews.extend = Backbone.Model.extend;
     35 
     36 	_.extend( wp.Backbone.Subviews.prototype, {
     37 		/**
     38 		 * Fetches all of the subviews.
     39 		 *
     40 		 * @since 3.5.0
     41 		 *
     42 		 * @return {Array} All the subviews.
     43 		 */
     44 		all: function() {
     45 			return _.flatten( _.values( this._views ) );
     46 		},
     47 
     48 		/**
     49 		 * Fetches all subviews that match a given `selector`.
     50 		 *
     51 		 * If no `selector` is provided, it will grab all subviews attached
     52 		 * to the view's root.
     53 		 *
     54 		 * @since 3.5.0
     55 		 *
     56 		 * @param {string} selector A jQuery selector.
     57 		 *
     58 		 * @return {Array} All the subviews that match the selector.
     59 		 */
     60 		get: function( selector ) {
     61 			selector = selector || '';
     62 			return this._views[ selector ];
     63 		},
     64 
     65 		/**
     66 		 * Fetches the first subview that matches a given `selector`.
     67 		 *
     68 		 * If no `selector` is provided, it will grab the first subview attached to the
     69 		 * view's root.
     70 		 *
     71 		 * Useful when a selector only has one subview at a time.
     72 		 *
     73 		 * @since 3.5.0
     74 		 *
     75 		 * @param {string} selector A jQuery selector.
     76 		 *
     77 		 * @return {Backbone.View} The view.
     78 		 */
     79 		first: function( selector ) {
     80 			var views = this.get( selector );
     81 			return views && views.length ? views[0] : null;
     82 		},
     83 
     84 		/**
     85 		 * Registers subview(s).
     86 		 *
     87 		 * Registers any number of `views` to a `selector`.
     88 		 *
     89 		 * When no `selector` is provided, the root selector (the empty string)
     90 		 * is used. `views` accepts a `Backbone.View` instance or an array of
     91 		 * `Backbone.View` instances.
     92 		 *
     93 		 * ---
     94 		 *
     95 		 * Accepts an `options` object, which has a significant effect on the
     96 		 * resulting behavior.
     97 		 *
     98 		 * `options.silent` - *boolean, `false`*
     99 		 * If `options.silent` is true, no DOM modifications will be made.
    100 		 *
    101 		 * `options.add` - *boolean, `false`*
    102 		 * Use `Views.add()` as a shortcut for setting `options.add` to true.
    103 		 *
    104 		 * By default, the provided `views` will replace any existing views
    105 		 * associated with the selector. If `options.add` is true, the provided
    106 		 * `views` will be added to the existing views.
    107 		 *
    108 		 * `options.at` - *integer, `undefined`*
    109 		 * When adding, to insert `views` at a specific index, use `options.at`.
    110 		 * By default, `views` are added to the end of the array.
    111 		 *
    112 		 * @since 3.5.0
    113 		 *
    114 		 * @param {string}       selector A jQuery selector.
    115 		 * @param {Array|Object} views    The subviews for the main view.
    116 		 * @param {Object}       options  Options for call. If `options.silent` is true,
    117 		 *                                no DOM  modifications will be made. Use
    118 		 *                                `Views.add()` as a shortcut for setting
    119 		 *                                `options.add` to true. If `options.add` is
    120 		 *                                true, the provided `views` will be added to
    121 		 *                                the existing views. When adding, to insert
    122 		 *                                `views` at a specific index, use `options.at`.
    123 		 *
    124 		 * @return {wp.Backbone.Subviews} The current Subviews instance.
    125 		 */
    126 		set: function( selector, views, options ) {
    127 			var existing, next;
    128 
    129 			if ( ! _.isString( selector ) ) {
    130 				options  = views;
    131 				views    = selector;
    132 				selector = '';
    133 			}
    134 
    135 			options  = options || {};
    136 			views    = _.isArray( views ) ? views : [ views ];
    137 			existing = this.get( selector );
    138 			next     = views;
    139 
    140 			if ( existing ) {
    141 				if ( options.add ) {
    142 					if ( _.isUndefined( options.at ) ) {
    143 						next = existing.concat( views );
    144 					} else {
    145 						next = existing;
    146 						next.splice.apply( next, [ options.at, 0 ].concat( views ) );
    147 					}
    148 				} else {
    149 					_.each( next, function( view ) {
    150 						view.__detach = true;
    151 					});
    152 
    153 					_.each( existing, function( view ) {
    154 						if ( view.__detach )
    155 							view.$el.detach();
    156 						else
    157 							view.remove();
    158 					});
    159 
    160 					_.each( next, function( view ) {
    161 						delete view.__detach;
    162 					});
    163 				}
    164 			}
    165 
    166 			this._views[ selector ] = next;
    167 
    168 			_.each( views, function( subview ) {
    169 				var constructor = subview.Views || wp.Backbone.Subviews,
    170 					subviews = subview.views = subview.views || new constructor( subview );
    171 				subviews.parent   = this.view;
    172 				subviews.selector = selector;
    173 			}, this );
    174 
    175 			if ( ! options.silent )
    176 				this._attach( selector, views, _.extend({ ready: this._isReady() }, options ) );
    177 
    178 			return this;
    179 		},
    180 
    181 		/**
    182 		 * Add subview(s) to existing subviews.
    183 		 *
    184 		 * An alias to `Views.set()`, which defaults `options.add` to true.
    185 		 *
    186 		 * Adds any number of `views` to a `selector`.
    187 		 *
    188 		 * When no `selector` is provided, the root selector (the empty string)
    189 		 * is used. `views` accepts a `Backbone.View` instance or an array of
    190 		 * `Backbone.View` instances.
    191 		 *
    192 		 * Uses `Views.set()` when setting `options.add` to `false`.
    193 		 *
    194 		 * Accepts an `options` object. By default, provided `views` will be
    195 		 * inserted at the end of the array of existing views. To insert
    196 		 * `views` at a specific index, use `options.at`. If `options.silent`
    197 		 * is true, no DOM modifications will be made.
    198 		 *
    199 		 * For more information on the `options` object, see `Views.set()`.
    200 		 *
    201 		 * @since 3.5.0
    202 		 *
    203 		 * @param {string}       selector A jQuery selector.
    204 		 * @param {Array|Object} views    The subviews for the main view.
    205 		 * @param {Object}       options  Options for call.  To insert `views` at a
    206 		 *                                specific index, use `options.at`. If
    207 		 *                                `options.silent` is true, no DOM modifications
    208 		 *                                will be made.
    209 		 *
    210 		 * @return {wp.Backbone.Subviews} The current subviews instance.
    211 		 */
    212 		add: function( selector, views, options ) {
    213 			if ( ! _.isString( selector ) ) {
    214 				options  = views;
    215 				views    = selector;
    216 				selector = '';
    217 			}
    218 
    219 			return this.set( selector, views, _.extend({ add: true }, options ) );
    220 		},
    221 
    222 		/**
    223 		 * Removes an added subview.
    224 		 *
    225 		 * Stops tracking `views` registered to a `selector`. If no `views` are
    226 		 * set, then all of the `selector`'s subviews will be unregistered and
    227 		 * removed.
    228 		 *
    229 		 * Accepts an `options` object. If `options.silent` is set, `remove`
    230 		 * will *not* be triggered on the unregistered views.
    231 		 *
    232 		 * @since 3.5.0
    233 		 *
    234 		 * @param {string}       selector A jQuery selector.
    235 		 * @param {Array|Object} views    The subviews for the main view.
    236 		 * @param {Object}       options  Options for call. If `options.silent` is set,
    237 		 *                                `remove` will *not* be triggered on the
    238 		 *                                unregistered views.
    239 		 *
    240 		 * @return {wp.Backbone.Subviews} The current Subviews instance.
    241 		 */
    242 		unset: function( selector, views, options ) {
    243 			var existing;
    244 
    245 			if ( ! _.isString( selector ) ) {
    246 				options = views;
    247 				views = selector;
    248 				selector = '';
    249 			}
    250 
    251 			views = views || [];
    252 
    253 			if ( existing = this.get( selector ) ) {
    254 				views = _.isArray( views ) ? views : [ views ];
    255 				this._views[ selector ] = views.length ? _.difference( existing, views ) : [];
    256 			}
    257 
    258 			if ( ! options || ! options.silent )
    259 				_.invoke( views, 'remove' );
    260 
    261 			return this;
    262 		},
    263 
    264 		/**
    265 		 * Detaches all subviews.
    266 		 *
    267 		 * Helps to preserve all subview events when re-rendering the master
    268 		 * view. Used in conjunction with `Views.render()`.
    269 		 *
    270 		 * @since 3.5.0
    271 		 *
    272 		 * @return {wp.Backbone.Subviews} The current Subviews instance.
    273 		 */
    274 		detach: function() {
    275 			$( _.pluck( this.all(), 'el' ) ).detach();
    276 			return this;
    277 		},
    278 
    279 		/**
    280 		 * Renders all subviews.
    281 		 *
    282 		 * Used in conjunction with `Views.detach()`.
    283 		 *
    284 		 * @since 3.5.0
    285 		 *
    286 		 * @return {wp.Backbone.Subviews} The current Subviews instance.
    287 		*/
    288 		render: function() {
    289 			var options = {
    290 					ready: this._isReady()
    291 				};
    292 
    293 			_.each( this._views, function( views, selector ) {
    294 				this._attach( selector, views, options );
    295 			}, this );
    296 
    297 			this.rendered = true;
    298 			return this;
    299 		},
    300 
    301 		/**
    302 		 * Removes all subviews.
    303 		 *
    304 		 * Triggers the `remove()` method on all subviews. Detaches the master
    305 		 * view from its parent. Resets the internals of the views manager.
    306 		 *
    307 		 * Accepts an `options` object. If `options.silent` is set, `unset`
    308 		 * will *not* be triggered on the master view's parent.
    309 		 *
    310 		 * @since 3.6.0
    311 		 *
    312 		 * @param {Object}  options        Options for call.
    313 		 * @param {boolean} options.silent If true, `unset` wil *not* be triggered on
    314 		 *                                 the master views' parent.
    315 		 *
    316 		 * @return {wp.Backbone.Subviews} The current Subviews instance.
    317 		*/
    318 		remove: function( options ) {
    319 			if ( ! options || ! options.silent ) {
    320 				if ( this.parent && this.parent.views )
    321 					this.parent.views.unset( this.selector, this.view, { silent: true });
    322 				delete this.parent;
    323 				delete this.selector;
    324 			}
    325 
    326 			_.invoke( this.all(), 'remove' );
    327 			this._views = [];
    328 			return this;
    329 		},
    330 
    331 		/**
    332 		 * Replaces a selector's subviews
    333 		 *
    334 		 * By default, sets the `$target` selector's html to the subview `els`.
    335 		 *
    336 		 * Can be overridden in subclasses.
    337 		 *
    338 		 * @since 3.5.0
    339 		 *
    340 		 * @param {string} $target Selector where to put the elements.
    341 		 * @param {*} els HTML or elements to put into the selector's HTML.
    342 		 *
    343 		 * @return {wp.Backbone.Subviews} The current Subviews instance.
    344 		 */
    345 		replace: function( $target, els ) {
    346 			$target.html( els );
    347 			return this;
    348 		},
    349 
    350 		/**
    351 		 * Insert subviews into a selector.
    352 		 *
    353 		 * By default, appends the subview `els` to the end of the `$target`
    354 		 * selector. If `options.at` is set, inserts the subview `els` at the
    355 		 * provided index.
    356 		 *
    357 		 * Can be overridden in subclasses.
    358 		 *
    359 		 * @since 3.5.0
    360 		 *
    361 		 * @param {string}  $target    Selector where to put the elements.
    362 		 * @param {*}       els        HTML or elements to put at the end of the
    363 		 *                             $target.
    364 		 * @param {?Object} options    Options for call.
    365 		 * @param {?number} options.at At which index to put the elements.
    366 		 *
    367 		 * @return {wp.Backbone.Subviews} The current Subviews instance.
    368 		 */
    369 		insert: function( $target, els, options ) {
    370 			var at = options && options.at,
    371 				$children;
    372 
    373 			if ( _.isNumber( at ) && ($children = $target.children()).length > at )
    374 				$children.eq( at ).before( els );
    375 			else
    376 				$target.append( els );
    377 
    378 			return this;
    379 		},
    380 
    381 		/**
    382 		 * Triggers the ready event.
    383 		 *
    384 		 * Only use this method if you know what you're doing. For performance reasons,
    385 		 * this method does not check if the view is actually attached to the DOM. It's
    386 		 * taking your word for it.
    387 		 *
    388 		 * Fires the ready event on the current view and all attached subviews.
    389 		 *
    390 		 * @since 3.5.0
    391 		 */
    392 		ready: function() {
    393 			this.view.trigger('ready');
    394 
    395 			// Find all attached subviews, and call ready on them.
    396 			_.chain( this.all() ).map( function( view ) {
    397 				return view.views;
    398 			}).flatten().where({ attached: true }).invoke('ready');
    399 		},
    400 		/**
    401 		 * Attaches a series of views to a selector. Internal.
    402 		 *
    403 		 * Checks to see if a matching selector exists, renders the views,
    404 		 * performs the proper DOM operation, and then checks if the view is
    405 		 * attached to the document.
    406 		 *
    407 		 * @since 3.5.0
    408 		 *
    409 		 * @private
    410 		 *
    411 		 * @param {string}       selector    A jQuery selector.
    412 		 * @param {Array|Object} views       The subviews for the main view.
    413 		 * @param {Object}       options     Options for call.
    414 		 * @param {boolean}      options.add If true the provided views will be added.
    415 		 *
    416 		 * @return {wp.Backbone.Subviews} The current Subviews instance.
    417 		 */
    418 		_attach: function( selector, views, options ) {
    419 			var $selector = selector ? this.view.$( selector ) : this.view.$el,
    420 				managers;
    421 
    422 			// Check if we found a location to attach the views.
    423 			if ( ! $selector.length )
    424 				return this;
    425 
    426 			managers = _.chain( views ).pluck('views').flatten().value();
    427 
    428 			// Render the views if necessary.
    429 			_.each( managers, function( manager ) {
    430 				if ( manager.rendered )
    431 					return;
    432 
    433 				manager.view.render();
    434 				manager.rendered = true;
    435 			}, this );
    436 
    437 			// Insert or replace the views.
    438 			this[ options.add ? 'insert' : 'replace' ]( $selector, _.pluck( views, 'el' ), options );
    439 
    440 			/*
    441 			 * Set attached and trigger ready if the current view is already
    442 			 * attached to the DOM.
    443 			 */
    444 			_.each( managers, function( manager ) {
    445 				manager.attached = true;
    446 
    447 				if ( options.ready )
    448 					manager.ready();
    449 			}, this );
    450 
    451 			return this;
    452 		},
    453 
    454 		/**
    455 		 * Determines whether or not the current view is in the DOM.
    456 		 *
    457 		 * @since 3.5.0
    458 		 *
    459 		 * @private
    460 		 *
    461 		 * @return {boolean} Whether or not the current view is in the DOM.
    462 		 */
    463 		_isReady: function() {
    464 			var node = this.view.el;
    465 			while ( node ) {
    466 				if ( node === document.body )
    467 					return true;
    468 				node = node.parentNode;
    469 			}
    470 
    471 			return false;
    472 		}
    473 	});
    474 
    475 	wp.Backbone.View = Backbone.View.extend({
    476 
    477 		// The constructor for the `Views` manager.
    478 		Subviews: wp.Backbone.Subviews,
    479 
    480 		/**
    481 		 * The base view class.
    482 		 *
    483 		 * This extends the backbone view to have a build-in way to use subviews. This
    484 		 * makes it easier to have nested views.
    485 		 *
    486 		 * @since 3.5.0
    487 		 * @since 3.6.0 Moved wp.media.View to wp.Backbone.View
    488 		 *
    489 		 * @constructs
    490 		 * @augments Backbone.View
    491 		 *
    492 		 * @memberOf wp.Backbone
    493 		 *
    494 		 *
    495 		 * @param {Object} options The options for this view.
    496 		 */
    497 		constructor: function( options ) {
    498 			this.views = new this.Subviews( this, this.views );
    499 			this.on( 'ready', this.ready, this );
    500 
    501 			this.options = options || {};
    502 
    503 			Backbone.View.apply( this, arguments );
    504 		},
    505 
    506 		/**
    507 		 * Removes this view and all subviews.
    508 		 *
    509 		 * @since 3.5.0
    510 		 *
    511 		 * @return {wp.Backbone.Subviews} The current Subviews instance.
    512 		 */
    513 		remove: function() {
    514 			var result = Backbone.View.prototype.remove.apply( this, arguments );
    515 
    516 			// Recursively remove child views.
    517 			if ( this.views )
    518 				this.views.remove();
    519 
    520 			return result;
    521 		},
    522 
    523 		/**
    524 		 * Renders this view and all subviews.
    525 		 *
    526 		 * @since 3.5.0
    527 		 *
    528 		 * @return {wp.Backbone.View} The current instance of the view.
    529 		 */
    530 		render: function() {
    531 			var options;
    532 
    533 			if ( this.prepare )
    534 				options = this.prepare();
    535 
    536 			this.views.detach();
    537 
    538 			if ( this.template ) {
    539 				options = options || {};
    540 				this.trigger( 'prepare', options );
    541 				this.$el.html( this.template( options ) );
    542 			}
    543 
    544 			this.views.render();
    545 			return this;
    546 		},
    547 
    548 		/**
    549 		 * Returns the options for this view.
    550 		 *
    551 		 * @since 3.5.0
    552 		 *
    553 		 * @return {Object} The options for this view.
    554 		 */
    555 		prepare: function() {
    556 			return this.options;
    557 		},
    558 
    559 		/**
    560 		 * Method that is called when the ready event is triggered.
    561 		 *
    562 		 * @since 3.5.0
    563 		 */
    564 		ready: function() {}
    565 	});
    566 }(jQuery));