265 }); |
306 }); |
266 |
307 |
267 /** |
308 /** |
268 * A collection of observable values. |
309 * A collection of observable values. |
269 * |
310 * |
270 * @constuctor |
311 * @memberOf wp.customize |
|
312 * @alias wp.customize.Values |
|
313 * |
|
314 * @constructor |
271 * @augments wp.customize.Class |
315 * @augments wp.customize.Class |
272 * @mixes wp.customize.Events |
316 * @mixes wp.customize.Events |
273 */ |
317 */ |
274 api.Values = api.Class.extend({ |
318 api.Values = api.Class.extend(/** @lends wp.customize.Values.prototype */{ |
|
319 |
|
320 /** |
|
321 * The default constructor for items of the collection. |
|
322 * |
|
323 * @type {object} |
|
324 */ |
275 defaultConstructor: api.Value, |
325 defaultConstructor: api.Value, |
276 |
326 |
277 initialize: function( options ) { |
327 initialize: function( options ) { |
278 $.extend( this, options || {} ); |
328 $.extend( this, options || {} ); |
279 |
329 |
280 this._value = {}; |
330 this._value = {}; |
281 this._deferreds = {}; |
331 this._deferreds = {}; |
282 }, |
332 }, |
283 |
333 |
|
334 /** |
|
335 * Get the instance of an item from the collection if only ID is specified. |
|
336 * |
|
337 * If more than one argument is supplied, all are expected to be IDs and |
|
338 * the last to be a function callback that will be invoked when the requested |
|
339 * items are available. |
|
340 * |
|
341 * @see {api.Values.when} |
|
342 * |
|
343 * @param {string} id ID of the item. |
|
344 * @param {...} Zero or more IDs of items to wait for and a callback |
|
345 * function to invoke when they're available. Optional. |
|
346 * @return {mixed} The item instance if only one ID was supplied. |
|
347 * A Deferred Promise object if a callback function is supplied. |
|
348 */ |
284 instance: function( id ) { |
349 instance: function( id ) { |
285 if ( arguments.length === 1 ) |
350 if ( arguments.length === 1 ) |
286 return this.value( id ); |
351 return this.value( id ); |
287 |
352 |
288 return this.when.apply( this, arguments ); |
353 return this.when.apply( this, arguments ); |
289 }, |
354 }, |
290 |
355 |
|
356 /** |
|
357 * Get the instance of an item. |
|
358 * |
|
359 * @param {string} id The ID of the item. |
|
360 * @return {[type]} [description] |
|
361 */ |
291 value: function( id ) { |
362 value: function( id ) { |
292 return this._value[ id ]; |
363 return this._value[ id ]; |
293 }, |
364 }, |
294 |
365 |
|
366 /** |
|
367 * Whether the collection has an item with the given ID. |
|
368 * |
|
369 * @param {string} id The ID of the item to look for. |
|
370 * @return {Boolean} |
|
371 */ |
295 has: function( id ) { |
372 has: function( id ) { |
296 return typeof this._value[ id ] !== 'undefined'; |
373 return typeof this._value[ id ] !== 'undefined'; |
297 }, |
374 }, |
298 |
375 |
299 add: function( id, value ) { |
376 /** |
300 if ( this.has( id ) ) |
377 * Add an item to the collection. |
301 return this.value( id ); |
378 * |
302 |
379 * @param {string|wp.customize.Class} item - The item instance to add, or the ID for the instance to add. When an ID string is supplied, then itemObject must be provided. |
303 this._value[ id ] = value; |
380 * @param {wp.customize.Class} [itemObject] - The item instance when the first argument is a ID string. |
304 value.parent = this; |
381 * @return {wp.customize.Class} The new item's instance, or an existing instance if already added. |
305 if ( value.extended( api.Value ) ) |
382 */ |
306 value.bind( this._change ); |
383 add: function( item, itemObject ) { |
307 |
384 var collection = this, id, instance; |
308 this.trigger( 'add', value ); |
385 if ( 'string' === typeof item ) { |
309 |
386 id = item; |
310 if ( this._deferreds[ id ] ) |
387 instance = itemObject; |
311 this._deferreds[ id ].resolve(); |
388 } else { |
312 |
389 if ( 'string' !== typeof item.id ) { |
313 return this._value[ id ]; |
390 throw new Error( 'Unknown key' ); |
314 }, |
391 } |
315 |
392 id = item.id; |
|
393 instance = item; |
|
394 } |
|
395 |
|
396 if ( collection.has( id ) ) { |
|
397 return collection.value( id ); |
|
398 } |
|
399 |
|
400 collection._value[ id ] = instance; |
|
401 instance.parent = collection; |
|
402 |
|
403 // Propagate a 'change' event on an item up to the collection. |
|
404 if ( instance.extended( api.Value ) ) { |
|
405 instance.bind( collection._change ); |
|
406 } |
|
407 |
|
408 collection.trigger( 'add', instance ); |
|
409 |
|
410 // If a deferred object exists for this item, |
|
411 // resolve it. |
|
412 if ( collection._deferreds[ id ] ) { |
|
413 collection._deferreds[ id ].resolve(); |
|
414 } |
|
415 |
|
416 return collection._value[ id ]; |
|
417 }, |
|
418 |
|
419 /** |
|
420 * Create a new item of the collection using the collection's default constructor |
|
421 * and store it in the collection. |
|
422 * |
|
423 * @param {string} id The ID of the item. |
|
424 * @param {mixed} value Any extra arguments are passed into the item's initialize method. |
|
425 * @return {mixed} The new item's instance. |
|
426 */ |
316 create: function( id ) { |
427 create: function( id ) { |
317 return this.add( id, new this.defaultConstructor( api.Class.applicator, slice.call( arguments, 1 ) ) ); |
428 return this.add( id, new this.defaultConstructor( api.Class.applicator, slice.call( arguments, 1 ) ) ); |
318 }, |
429 }, |
319 |
430 |
|
431 /** |
|
432 * Iterate over all items in the collection invoking the provided callback. |
|
433 * |
|
434 * @param {Function} callback Function to invoke. |
|
435 * @param {object} context Object context to invoke the function with. Optional. |
|
436 */ |
320 each: function( callback, context ) { |
437 each: function( callback, context ) { |
321 context = typeof context === 'undefined' ? this : context; |
438 context = typeof context === 'undefined' ? this : context; |
322 |
439 |
323 $.each( this._value, function( key, obj ) { |
440 $.each( this._value, function( key, obj ) { |
324 callback.call( context, obj, key ); |
441 callback.call( context, obj, key ); |
325 }); |
442 }); |
326 }, |
443 }, |
327 |
444 |
|
445 /** |
|
446 * Remove an item from the collection. |
|
447 * |
|
448 * @param {string} id The ID of the item to remove. |
|
449 */ |
328 remove: function( id ) { |
450 remove: function( id ) { |
329 var value; |
451 var value = this.value( id ); |
330 |
452 |
331 if ( this.has( id ) ) { |
453 if ( value ) { |
332 value = this.value( id ); |
454 |
|
455 // Trigger event right before the element is removed from the collection. |
333 this.trigger( 'remove', value ); |
456 this.trigger( 'remove', value ); |
334 if ( value.extended( api.Value ) ) |
457 |
|
458 if ( value.extended( api.Value ) ) { |
335 value.unbind( this._change ); |
459 value.unbind( this._change ); |
|
460 } |
336 delete value.parent; |
461 delete value.parent; |
337 } |
462 } |
338 |
463 |
339 delete this._value[ id ]; |
464 delete this._value[ id ]; |
340 delete this._deferreds[ id ]; |
465 delete this._deferreds[ id ]; |
|
466 |
|
467 // Trigger removed event after the item has been eliminated from the collection. |
|
468 if ( value ) { |
|
469 this.trigger( 'removed', value ); |
|
470 } |
341 }, |
471 }, |
342 |
472 |
343 /** |
473 /** |
344 * Runs a callback once all requested values exist. |
474 * Runs a callback once all requested values exist. |
345 * |
475 * |
405 /** |
548 /** |
406 * An observable value that syncs with an element. |
549 * An observable value that syncs with an element. |
407 * |
550 * |
408 * Handles inputs, selects, and textareas by default. |
551 * Handles inputs, selects, and textareas by default. |
409 * |
552 * |
410 * @constuctor |
553 * @memberOf wp.customize |
|
554 * @alias wp.customize.Element |
|
555 * |
|
556 * @constructor |
411 * @augments wp.customize.Value |
557 * @augments wp.customize.Value |
412 * @augments wp.customize.Class |
558 * @augments wp.customize.Class |
413 */ |
559 */ |
414 api.Element = api.Value.extend({ |
560 api.Element = api.Value.extend(/** @lends wp.customize.Element */{ |
415 initialize: function( element, options ) { |
561 initialize: function( element, options ) { |
416 var self = this, |
562 var self = this, |
417 synchronizer = api.Element.synchronizer.html, |
563 synchronizer = api.Element.synchronizer.html, |
418 type, update, refresh; |
564 type, update, refresh; |
419 |
565 |
420 this.element = api.ensure( element ); |
566 this.element = api.ensure( element ); |
421 this.events = ''; |
567 this.events = ''; |
422 |
568 |
423 if ( this.element.is('input, select, textarea') ) { |
569 if ( this.element.is( 'input, select, textarea' ) ) { |
424 this.events += 'change'; |
570 type = this.element.prop( 'type' ); |
|
571 this.events += ' change input'; |
425 synchronizer = api.Element.synchronizer.val; |
572 synchronizer = api.Element.synchronizer.val; |
426 |
573 |
427 if ( this.element.is('input') ) { |
574 if ( this.element.is( 'input' ) && api.Element.synchronizer[ type ] ) { |
428 type = this.element.prop('type'); |
575 synchronizer = api.Element.synchronizer[ type ]; |
429 if ( api.Element.synchronizer[ type ] ) { |
|
430 synchronizer = api.Element.synchronizer[ type ]; |
|
431 } |
|
432 if ( 'text' === type || 'password' === type ) { |
|
433 this.events += ' keyup'; |
|
434 } else if ( 'range' === type ) { |
|
435 this.events += ' input propertychange'; |
|
436 } |
|
437 } else if ( this.element.is('textarea') ) { |
|
438 this.events += ' keyup'; |
|
439 } |
576 } |
440 } |
577 } |
441 |
578 |
442 api.Value.prototype.initialize.call( this, null, $.extend( options || {}, synchronizer ) ); |
579 api.Value.prototype.initialize.call( this, null, $.extend( options || {}, synchronizer ) ); |
443 this._value = this.get(); |
580 this._value = this.get(); |
444 |
581 |
445 update = this.update; |
582 update = this.update; |
446 refresh = this.refresh; |
583 refresh = this.refresh; |
447 |
584 |
448 this.update = function( to ) { |
585 this.update = function( to ) { |
449 if ( to !== refresh.call( self ) ) |
586 if ( to !== refresh.call( self ) ) { |
450 update.apply( this, arguments ); |
587 update.apply( this, arguments ); |
|
588 } |
451 }; |
589 }; |
452 this.refresh = function() { |
590 this.refresh = function() { |
453 self.set( refresh.call( self ) ); |
591 self.set( refresh.call( self ) ); |
454 }; |
592 }; |
455 |
593 |
522 }, |
663 }, |
523 |
664 |
524 /** |
665 /** |
525 * Initialize Messenger. |
666 * Initialize Messenger. |
526 * |
667 * |
527 * @param {object} params Parameters to configure the messenger. |
668 * @param {object} params - Parameters to configure the messenger. |
528 * {string} .url The URL to communicate with. |
669 * {string} params.url - The URL to communicate with. |
529 * {window} .targetWindow The window instance to communicate with. Default window.parent. |
670 * {window} params.targetWindow - The window instance to communicate with. Default window.parent. |
530 * {string} .channel If provided, will send the channel with each message and only accept messages a matching channel. |
671 * {string} params.channel - If provided, will send the channel with each message and only accept messages a matching channel. |
531 * @param {object} options Extend any instance parameter or method with this object. |
672 * @param {object} options - Extend any instance parameter or method with this object. |
532 */ |
673 */ |
533 initialize: function( params, options ) { |
674 initialize: function( params, options ) { |
534 // Target the parent frame by default, but only if a parent frame exists. |
675 // Target the parent frame by default, but only if a parent frame exists. |
535 var defaultTarget = window.parent == window ? null : window.parent; |
676 var defaultTarget = window.parent === window ? null : window.parent; |
536 |
677 |
537 $.extend( this, options || {} ); |
678 $.extend( this, options || {} ); |
538 |
679 |
539 this.add( 'channel', params.channel ); |
680 this.add( 'channel', params.channel ); |
540 this.add( 'url', params.url || '' ); |
681 this.add( 'url', params.url || '' ); |
541 this.add( 'origin', this.url() ).link( this.url ).setter( function( to ) { |
682 this.add( 'origin', this.url() ).link( this.url ).setter( function( to ) { |
542 return to.replace( /([^:]+:\/\/[^\/]+).*/, '$1' ); |
683 var urlParser = document.createElement( 'a' ); |
|
684 urlParser.href = to; |
|
685 // Port stripping needed by IE since it adds to host but not to event.origin. |
|
686 return urlParser.protocol + '//' + urlParser.host.replace( /:(80|443)$/, '' ); |
543 }); |
687 }); |
544 |
688 |
545 // first add with no value |
689 // first add with no value |
546 this.add( 'targetWindow', null ); |
690 this.add( 'targetWindow', null ); |
547 // This avoids SecurityErrors when setting a window object in x-origin iframe'd scenarios. |
691 // This avoids SecurityErrors when setting a window object in x-origin iframe'd scenarios. |
628 }); |
784 }); |
629 |
785 |
630 // Add the Events mixin to api.Messenger. |
786 // Add the Events mixin to api.Messenger. |
631 $.extend( api.Messenger.prototype, api.Events ); |
787 $.extend( api.Messenger.prototype, api.Events ); |
632 |
788 |
633 // Core customize object. |
789 /** |
|
790 * Notification. |
|
791 * |
|
792 * @class |
|
793 * @augments wp.customize.Class |
|
794 * @since 4.6.0 |
|
795 * |
|
796 * @memberOf wp.customize |
|
797 * @alias wp.customize.Notification |
|
798 * |
|
799 * @param {string} code - The error code. |
|
800 * @param {object} params - Params. |
|
801 * @param {string} params.message=null - The error message. |
|
802 * @param {string} [params.type=error] - The notification type. |
|
803 * @param {boolean} [params.fromServer=false] - Whether the notification was server-sent. |
|
804 * @param {string} [params.setting=null] - The setting ID that the notification is related to. |
|
805 * @param {*} [params.data=null] - Any additional data. |
|
806 */ |
|
807 api.Notification = api.Class.extend(/** @lends wp.customize.Notification.prototype */{ |
|
808 |
|
809 /** |
|
810 * Template function for rendering the notification. |
|
811 * |
|
812 * This will be populated with template option or else it will be populated with template from the ID. |
|
813 * |
|
814 * @since 4.9.0 |
|
815 * @var {Function} |
|
816 */ |
|
817 template: null, |
|
818 |
|
819 /** |
|
820 * ID for the template to render the notification. |
|
821 * |
|
822 * @since 4.9.0 |
|
823 * @var {string} |
|
824 */ |
|
825 templateId: 'customize-notification', |
|
826 |
|
827 /** |
|
828 * Additional class names to add to the notification container. |
|
829 * |
|
830 * @since 4.9.0 |
|
831 * @var {string} |
|
832 */ |
|
833 containerClasses: '', |
|
834 |
|
835 /** |
|
836 * Initialize notification. |
|
837 * |
|
838 * @since 4.9.0 |
|
839 * |
|
840 * @param {string} code - Notification code. |
|
841 * @param {object} params - Notification parameters. |
|
842 * @param {string} params.message - Message. |
|
843 * @param {string} [params.type=error] - Type. |
|
844 * @param {string} [params.setting] - Related setting ID. |
|
845 * @param {Function} [params.template] - Function for rendering template. If not provided, this will come from templateId. |
|
846 * @param {string} [params.templateId] - ID for template to render the notification. |
|
847 * @param {string} [params.containerClasses] - Additional class names to add to the notification container. |
|
848 * @param {boolean} [params.dismissible] - Whether the notification can be dismissed. |
|
849 */ |
|
850 initialize: function( code, params ) { |
|
851 var _params; |
|
852 this.code = code; |
|
853 _params = _.extend( |
|
854 { |
|
855 message: null, |
|
856 type: 'error', |
|
857 fromServer: false, |
|
858 data: null, |
|
859 setting: null, |
|
860 template: null, |
|
861 dismissible: false, |
|
862 containerClasses: '' |
|
863 }, |
|
864 params |
|
865 ); |
|
866 delete _params.code; |
|
867 _.extend( this, _params ); |
|
868 }, |
|
869 |
|
870 /** |
|
871 * Render the notification. |
|
872 * |
|
873 * @since 4.9.0 |
|
874 * |
|
875 * @returns {jQuery} Notification container element. |
|
876 */ |
|
877 render: function() { |
|
878 var notification = this, container, data; |
|
879 if ( ! notification.template ) { |
|
880 notification.template = wp.template( notification.templateId ); |
|
881 } |
|
882 data = _.extend( {}, notification, { |
|
883 alt: notification.parent && notification.parent.alt |
|
884 } ); |
|
885 container = $( notification.template( data ) ); |
|
886 |
|
887 if ( notification.dismissible ) { |
|
888 container.find( '.notice-dismiss' ).on( 'click keydown', function( event ) { |
|
889 if ( 'keydown' === event.type && 13 !== event.which ) { |
|
890 return; |
|
891 } |
|
892 |
|
893 if ( notification.parent ) { |
|
894 notification.parent.remove( notification.code ); |
|
895 } else { |
|
896 container.remove(); |
|
897 } |
|
898 }); |
|
899 } |
|
900 |
|
901 return container; |
|
902 } |
|
903 }); |
|
904 |
|
905 // The main API object is also a collection of all customizer settings. |
634 api = $.extend( new api.Values(), api ); |
906 api = $.extend( new api.Values(), api ); |
|
907 |
|
908 /** |
|
909 * Get all customize settings. |
|
910 * |
|
911 * @memberOf wp.customize |
|
912 * |
|
913 * @return {object} |
|
914 */ |
635 api.get = function() { |
915 api.get = function() { |
636 var result = {}; |
916 var result = {}; |
637 |
917 |
638 this.each( function( obj, key ) { |
918 this.each( function( obj, key ) { |
639 result[ key ] = obj.get(); |
919 result[ key ] = obj.get(); |
640 }); |
920 }); |
641 |
921 |
642 return result; |
922 return result; |
643 }; |
923 }; |
644 |
924 |
645 // Expose the API publicly on window.wp.customize |
925 /** |
|
926 * Utility function namespace |
|
927 * |
|
928 * @namespace wp.customize.utils |
|
929 */ |
|
930 api.utils = {}; |
|
931 |
|
932 /** |
|
933 * Parse query string. |
|
934 * |
|
935 * @since 4.7.0 |
|
936 * @access public |
|
937 * @memberOf wp.customize.utils |
|
938 * |
|
939 * @param {string} queryString Query string. |
|
940 * @returns {object} Parsed query string. |
|
941 */ |
|
942 api.utils.parseQueryString = function parseQueryString( queryString ) { |
|
943 var queryParams = {}; |
|
944 _.each( queryString.split( '&' ), function( pair ) { |
|
945 var parts, key, value; |
|
946 parts = pair.split( '=', 2 ); |
|
947 if ( ! parts[0] ) { |
|
948 return; |
|
949 } |
|
950 key = decodeURIComponent( parts[0].replace( /\+/g, ' ' ) ); |
|
951 key = key.replace( / /g, '_' ); // What PHP does. |
|
952 if ( _.isUndefined( parts[1] ) ) { |
|
953 value = null; |
|
954 } else { |
|
955 value = decodeURIComponent( parts[1].replace( /\+/g, ' ' ) ); |
|
956 } |
|
957 queryParams[ key ] = value; |
|
958 } ); |
|
959 return queryParams; |
|
960 }; |
|
961 |
|
962 /** |
|
963 * Expose the API publicly on window.wp.customize |
|
964 * |
|
965 * @namespace wp.customize |
|
966 */ |
646 exports.customize = api; |
967 exports.customize = api; |
647 })( wp, jQuery ); |
968 })( wp, jQuery ); |