customize-base.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968
  1. /** @namespace wp */
  2. window.wp = window.wp || {};
  3. (function( exports, $ ){
  4. var api = {}, ctor, inherits,
  5. slice = Array.prototype.slice;
  6. // Shared empty constructor function to aid in prototype-chain creation.
  7. ctor = function() {};
  8. /**
  9. * Helper function to correctly set up the prototype chain, for subclasses.
  10. * Similar to `goog.inherits`, but uses a hash of prototype properties and
  11. * class properties to be extended.
  12. *
  13. * @param object parent Parent class constructor to inherit from.
  14. * @param object protoProps Properties to apply to the prototype for use as class instance properties.
  15. * @param object staticProps Properties to apply directly to the class constructor.
  16. * @return child The subclassed constructor.
  17. */
  18. inherits = function( parent, protoProps, staticProps ) {
  19. var child;
  20. // The constructor function for the new subclass is either defined by you
  21. // (the "constructor" property in your `extend` definition), or defaulted
  22. // by us to simply call `super()`.
  23. if ( protoProps && protoProps.hasOwnProperty( 'constructor' ) ) {
  24. child = protoProps.constructor;
  25. } else {
  26. child = function() {
  27. // Storing the result `super()` before returning the value
  28. // prevents a bug in Opera where, if the constructor returns
  29. // a function, Opera will reject the return value in favor of
  30. // the original object. This causes all sorts of trouble.
  31. var result = parent.apply( this, arguments );
  32. return result;
  33. };
  34. }
  35. // Inherit class (static) properties from parent.
  36. $.extend( child, parent );
  37. // Set the prototype chain to inherit from `parent`, without calling
  38. // `parent`'s constructor function.
  39. ctor.prototype = parent.prototype;
  40. child.prototype = new ctor();
  41. // Add prototype properties (instance properties) to the subclass,
  42. // if supplied.
  43. if ( protoProps )
  44. $.extend( child.prototype, protoProps );
  45. // Add static properties to the constructor function, if supplied.
  46. if ( staticProps )
  47. $.extend( child, staticProps );
  48. // Correctly set child's `prototype.constructor`.
  49. child.prototype.constructor = child;
  50. // Set a convenience property in case the parent's prototype is needed later.
  51. child.__super__ = parent.prototype;
  52. return child;
  53. };
  54. /**
  55. * Base class for object inheritance.
  56. */
  57. api.Class = function( applicator, argsArray, options ) {
  58. var magic, args = arguments;
  59. if ( applicator && argsArray && api.Class.applicator === applicator ) {
  60. args = argsArray;
  61. $.extend( this, options || {} );
  62. }
  63. magic = this;
  64. /*
  65. * If the class has a method called "instance",
  66. * the return value from the class' constructor will be a function that
  67. * calls the "instance" method.
  68. *
  69. * It is also an object that has properties and methods inside it.
  70. */
  71. if ( this.instance ) {
  72. magic = function() {
  73. return magic.instance.apply( magic, arguments );
  74. };
  75. $.extend( magic, this );
  76. }
  77. magic.initialize.apply( magic, args );
  78. return magic;
  79. };
  80. /**
  81. * Creates a subclass of the class.
  82. *
  83. * @param object protoProps Properties to apply to the prototype.
  84. * @param object staticProps Properties to apply directly to the class.
  85. * @return child The subclass.
  86. */
  87. api.Class.extend = function( protoProps, classProps ) {
  88. var child = inherits( this, protoProps, classProps );
  89. child.extend = this.extend;
  90. return child;
  91. };
  92. api.Class.applicator = {};
  93. /**
  94. * Initialize a class instance.
  95. *
  96. * Override this function in a subclass as needed.
  97. */
  98. api.Class.prototype.initialize = function() {};
  99. /*
  100. * Checks whether a given instance extended a constructor.
  101. *
  102. * The magic surrounding the instance parameter causes the instanceof
  103. * keyword to return inaccurate results; it defaults to the function's
  104. * prototype instead of the constructor chain. Hence this function.
  105. */
  106. api.Class.prototype.extended = function( constructor ) {
  107. var proto = this;
  108. while ( typeof proto.constructor !== 'undefined' ) {
  109. if ( proto.constructor === constructor )
  110. return true;
  111. if ( typeof proto.constructor.__super__ === 'undefined' )
  112. return false;
  113. proto = proto.constructor.__super__;
  114. }
  115. return false;
  116. };
  117. /**
  118. * An events manager object, offering the ability to bind to and trigger events.
  119. *
  120. * Used as a mixin.
  121. */
  122. api.Events = {
  123. trigger: function( id ) {
  124. if ( this.topics && this.topics[ id ] )
  125. this.topics[ id ].fireWith( this, slice.call( arguments, 1 ) );
  126. return this;
  127. },
  128. bind: function( id ) {
  129. this.topics = this.topics || {};
  130. this.topics[ id ] = this.topics[ id ] || $.Callbacks();
  131. this.topics[ id ].add.apply( this.topics[ id ], slice.call( arguments, 1 ) );
  132. return this;
  133. },
  134. unbind: function( id ) {
  135. if ( this.topics && this.topics[ id ] )
  136. this.topics[ id ].remove.apply( this.topics[ id ], slice.call( arguments, 1 ) );
  137. return this;
  138. }
  139. };
  140. /**
  141. * Observable values that support two-way binding.
  142. *
  143. * @memberOf wp.customize
  144. * @alias wp.customize.Value
  145. *
  146. * @constructor
  147. */
  148. api.Value = api.Class.extend(/** @lends wp.customize.Value.prototype */{
  149. /**
  150. * @param {mixed} initial The initial value.
  151. * @param {object} options
  152. */
  153. initialize: function( initial, options ) {
  154. this._value = initial; // @todo: potentially change this to a this.set() call.
  155. this.callbacks = $.Callbacks();
  156. this._dirty = false;
  157. $.extend( this, options || {} );
  158. this.set = $.proxy( this.set, this );
  159. },
  160. /*
  161. * Magic. Returns a function that will become the instance.
  162. * Set to null to prevent the instance from extending a function.
  163. */
  164. instance: function() {
  165. return arguments.length ? this.set.apply( this, arguments ) : this.get();
  166. },
  167. /**
  168. * Get the value.
  169. *
  170. * @return {mixed}
  171. */
  172. get: function() {
  173. return this._value;
  174. },
  175. /**
  176. * Set the value and trigger all bound callbacks.
  177. *
  178. * @param {object} to New value.
  179. */
  180. set: function( to ) {
  181. var from = this._value;
  182. to = this._setter.apply( this, arguments );
  183. to = this.validate( to );
  184. // Bail if the sanitized value is null or unchanged.
  185. if ( null === to || _.isEqual( from, to ) ) {
  186. return this;
  187. }
  188. this._value = to;
  189. this._dirty = true;
  190. this.callbacks.fireWith( this, [ to, from ] );
  191. return this;
  192. },
  193. _setter: function( to ) {
  194. return to;
  195. },
  196. setter: function( callback ) {
  197. var from = this.get();
  198. this._setter = callback;
  199. // Temporarily clear value so setter can decide if it's valid.
  200. this._value = null;
  201. this.set( from );
  202. return this;
  203. },
  204. resetSetter: function() {
  205. this._setter = this.constructor.prototype._setter;
  206. this.set( this.get() );
  207. return this;
  208. },
  209. validate: function( value ) {
  210. return value;
  211. },
  212. /**
  213. * Bind a function to be invoked whenever the value changes.
  214. *
  215. * @param {...Function} A function, or multiple functions, to add to the callback stack.
  216. */
  217. bind: function() {
  218. this.callbacks.add.apply( this.callbacks, arguments );
  219. return this;
  220. },
  221. /**
  222. * Unbind a previously bound function.
  223. *
  224. * @param {...Function} A function, or multiple functions, to remove from the callback stack.
  225. */
  226. unbind: function() {
  227. this.callbacks.remove.apply( this.callbacks, arguments );
  228. return this;
  229. },
  230. link: function() { // values*
  231. var set = this.set;
  232. $.each( arguments, function() {
  233. this.bind( set );
  234. });
  235. return this;
  236. },
  237. unlink: function() { // values*
  238. var set = this.set;
  239. $.each( arguments, function() {
  240. this.unbind( set );
  241. });
  242. return this;
  243. },
  244. sync: function() { // values*
  245. var that = this;
  246. $.each( arguments, function() {
  247. that.link( this );
  248. this.link( that );
  249. });
  250. return this;
  251. },
  252. unsync: function() { // values*
  253. var that = this;
  254. $.each( arguments, function() {
  255. that.unlink( this );
  256. this.unlink( that );
  257. });
  258. return this;
  259. }
  260. });
  261. /**
  262. * A collection of observable values.
  263. *
  264. * @memberOf wp.customize
  265. * @alias wp.customize.Values
  266. *
  267. * @constructor
  268. * @augments wp.customize.Class
  269. * @mixes wp.customize.Events
  270. */
  271. api.Values = api.Class.extend(/** @lends wp.customize.Values.prototype */{
  272. /**
  273. * The default constructor for items of the collection.
  274. *
  275. * @type {object}
  276. */
  277. defaultConstructor: api.Value,
  278. initialize: function( options ) {
  279. $.extend( this, options || {} );
  280. this._value = {};
  281. this._deferreds = {};
  282. },
  283. /**
  284. * Get the instance of an item from the collection if only ID is specified.
  285. *
  286. * If more than one argument is supplied, all are expected to be IDs and
  287. * the last to be a function callback that will be invoked when the requested
  288. * items are available.
  289. *
  290. * @see {api.Values.when}
  291. *
  292. * @param {string} id ID of the item.
  293. * @param {...} Zero or more IDs of items to wait for and a callback
  294. * function to invoke when they're available. Optional.
  295. * @return {mixed} The item instance if only one ID was supplied.
  296. * A Deferred Promise object if a callback function is supplied.
  297. */
  298. instance: function( id ) {
  299. if ( arguments.length === 1 )
  300. return this.value( id );
  301. return this.when.apply( this, arguments );
  302. },
  303. /**
  304. * Get the instance of an item.
  305. *
  306. * @param {string} id The ID of the item.
  307. * @return {[type]} [description]
  308. */
  309. value: function( id ) {
  310. return this._value[ id ];
  311. },
  312. /**
  313. * Whether the collection has an item with the given ID.
  314. *
  315. * @param {string} id The ID of the item to look for.
  316. * @return {Boolean}
  317. */
  318. has: function( id ) {
  319. return typeof this._value[ id ] !== 'undefined';
  320. },
  321. /**
  322. * Add an item to the collection.
  323. *
  324. * @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.
  325. * @param {wp.customize.Class} [itemObject] - The item instance when the first argument is a ID string.
  326. * @return {wp.customize.Class} The new item's instance, or an existing instance if already added.
  327. */
  328. add: function( item, itemObject ) {
  329. var collection = this, id, instance;
  330. if ( 'string' === typeof item ) {
  331. id = item;
  332. instance = itemObject;
  333. } else {
  334. if ( 'string' !== typeof item.id ) {
  335. throw new Error( 'Unknown key' );
  336. }
  337. id = item.id;
  338. instance = item;
  339. }
  340. if ( collection.has( id ) ) {
  341. return collection.value( id );
  342. }
  343. collection._value[ id ] = instance;
  344. instance.parent = collection;
  345. // Propagate a 'change' event on an item up to the collection.
  346. if ( instance.extended( api.Value ) ) {
  347. instance.bind( collection._change );
  348. }
  349. collection.trigger( 'add', instance );
  350. // If a deferred object exists for this item,
  351. // resolve it.
  352. if ( collection._deferreds[ id ] ) {
  353. collection._deferreds[ id ].resolve();
  354. }
  355. return collection._value[ id ];
  356. },
  357. /**
  358. * Create a new item of the collection using the collection's default constructor
  359. * and store it in the collection.
  360. *
  361. * @param {string} id The ID of the item.
  362. * @param {mixed} value Any extra arguments are passed into the item's initialize method.
  363. * @return {mixed} The new item's instance.
  364. */
  365. create: function( id ) {
  366. return this.add( id, new this.defaultConstructor( api.Class.applicator, slice.call( arguments, 1 ) ) );
  367. },
  368. /**
  369. * Iterate over all items in the collection invoking the provided callback.
  370. *
  371. * @param {Function} callback Function to invoke.
  372. * @param {object} context Object context to invoke the function with. Optional.
  373. */
  374. each: function( callback, context ) {
  375. context = typeof context === 'undefined' ? this : context;
  376. $.each( this._value, function( key, obj ) {
  377. callback.call( context, obj, key );
  378. });
  379. },
  380. /**
  381. * Remove an item from the collection.
  382. *
  383. * @param {string} id The ID of the item to remove.
  384. */
  385. remove: function( id ) {
  386. var value = this.value( id );
  387. if ( value ) {
  388. // Trigger event right before the element is removed from the collection.
  389. this.trigger( 'remove', value );
  390. if ( value.extended( api.Value ) ) {
  391. value.unbind( this._change );
  392. }
  393. delete value.parent;
  394. }
  395. delete this._value[ id ];
  396. delete this._deferreds[ id ];
  397. // Trigger removed event after the item has been eliminated from the collection.
  398. if ( value ) {
  399. this.trigger( 'removed', value );
  400. }
  401. },
  402. /**
  403. * Runs a callback once all requested values exist.
  404. *
  405. * when( ids*, [callback] );
  406. *
  407. * For example:
  408. * when( id1, id2, id3, function( value1, value2, value3 ) {} );
  409. *
  410. * @returns $.Deferred.promise();
  411. */
  412. when: function() {
  413. var self = this,
  414. ids = slice.call( arguments ),
  415. dfd = $.Deferred();
  416. // If the last argument is a callback, bind it to .done()
  417. if ( $.isFunction( ids[ ids.length - 1 ] ) )
  418. dfd.done( ids.pop() );
  419. /*
  420. * Create a stack of deferred objects for each item that is not
  421. * yet available, and invoke the supplied callback when they are.
  422. */
  423. $.when.apply( $, $.map( ids, function( id ) {
  424. if ( self.has( id ) )
  425. return;
  426. /*
  427. * The requested item is not available yet, create a deferred
  428. * object to resolve when it becomes available.
  429. */
  430. return self._deferreds[ id ] = self._deferreds[ id ] || $.Deferred();
  431. })).done( function() {
  432. var values = $.map( ids, function( id ) {
  433. return self( id );
  434. });
  435. // If a value is missing, we've used at least one expired deferred.
  436. // Call Values.when again to generate a new deferred.
  437. if ( values.length !== ids.length ) {
  438. // ids.push( callback );
  439. self.when.apply( self, ids ).done( function() {
  440. dfd.resolveWith( self, values );
  441. });
  442. return;
  443. }
  444. dfd.resolveWith( self, values );
  445. });
  446. return dfd.promise();
  447. },
  448. /**
  449. * A helper function to propagate a 'change' event from an item
  450. * to the collection itself.
  451. */
  452. _change: function() {
  453. this.parent.trigger( 'change', this );
  454. }
  455. });
  456. // Create a global events bus on the Customizer.
  457. $.extend( api.Values.prototype, api.Events );
  458. /**
  459. * Cast a string to a jQuery collection if it isn't already.
  460. *
  461. * @param {string|jQuery collection} element
  462. */
  463. api.ensure = function( element ) {
  464. return typeof element == 'string' ? $( element ) : element;
  465. };
  466. /**
  467. * An observable value that syncs with an element.
  468. *
  469. * Handles inputs, selects, and textareas by default.
  470. *
  471. * @memberOf wp.customize
  472. * @alias wp.customize.Element
  473. *
  474. * @constructor
  475. * @augments wp.customize.Value
  476. * @augments wp.customize.Class
  477. */
  478. api.Element = api.Value.extend(/** @lends wp.customize.Element */{
  479. initialize: function( element, options ) {
  480. var self = this,
  481. synchronizer = api.Element.synchronizer.html,
  482. type, update, refresh;
  483. this.element = api.ensure( element );
  484. this.events = '';
  485. if ( this.element.is( 'input, select, textarea' ) ) {
  486. type = this.element.prop( 'type' );
  487. this.events += ' change input';
  488. synchronizer = api.Element.synchronizer.val;
  489. if ( this.element.is( 'input' ) && api.Element.synchronizer[ type ] ) {
  490. synchronizer = api.Element.synchronizer[ type ];
  491. }
  492. }
  493. api.Value.prototype.initialize.call( this, null, $.extend( options || {}, synchronizer ) );
  494. this._value = this.get();
  495. update = this.update;
  496. refresh = this.refresh;
  497. this.update = function( to ) {
  498. if ( to !== refresh.call( self ) ) {
  499. update.apply( this, arguments );
  500. }
  501. };
  502. this.refresh = function() {
  503. self.set( refresh.call( self ) );
  504. };
  505. this.bind( this.update );
  506. this.element.bind( this.events, this.refresh );
  507. },
  508. find: function( selector ) {
  509. return $( selector, this.element );
  510. },
  511. refresh: function() {},
  512. update: function() {}
  513. });
  514. api.Element.synchronizer = {};
  515. $.each( [ 'html', 'val' ], function( index, method ) {
  516. api.Element.synchronizer[ method ] = {
  517. update: function( to ) {
  518. this.element[ method ]( to );
  519. },
  520. refresh: function() {
  521. return this.element[ method ]();
  522. }
  523. };
  524. });
  525. api.Element.synchronizer.checkbox = {
  526. update: function( to ) {
  527. this.element.prop( 'checked', to );
  528. },
  529. refresh: function() {
  530. return this.element.prop( 'checked' );
  531. }
  532. };
  533. api.Element.synchronizer.radio = {
  534. update: function( to ) {
  535. this.element.filter( function() {
  536. return this.value === to;
  537. }).prop( 'checked', true );
  538. },
  539. refresh: function() {
  540. return this.element.filter( ':checked' ).val();
  541. }
  542. };
  543. $.support.postMessage = !! window.postMessage;
  544. /**
  545. * A communicator for sending data from one window to another over postMessage.
  546. *
  547. * @memberOf wp.customize
  548. * @alias wp.customize.Messenger
  549. *
  550. * @constructor
  551. * @augments wp.customize.Class
  552. * @mixes wp.customize.Events
  553. */
  554. api.Messenger = api.Class.extend(/** @lends wp.customize.Messenger.prototype */{
  555. /**
  556. * Create a new Value.
  557. *
  558. * @param {string} key Unique identifier.
  559. * @param {mixed} initial Initial value.
  560. * @param {mixed} options Options hash. Optional.
  561. * @return {Value} Class instance of the Value.
  562. */
  563. add: function( key, initial, options ) {
  564. return this[ key ] = new api.Value( initial, options );
  565. },
  566. /**
  567. * Initialize Messenger.
  568. *
  569. * @param {object} params - Parameters to configure the messenger.
  570. * {string} params.url - The URL to communicate with.
  571. * {window} params.targetWindow - The window instance to communicate with. Default window.parent.
  572. * {string} params.channel - If provided, will send the channel with each message and only accept messages a matching channel.
  573. * @param {object} options - Extend any instance parameter or method with this object.
  574. */
  575. initialize: function( params, options ) {
  576. // Target the parent frame by default, but only if a parent frame exists.
  577. var defaultTarget = window.parent === window ? null : window.parent;
  578. $.extend( this, options || {} );
  579. this.add( 'channel', params.channel );
  580. this.add( 'url', params.url || '' );
  581. this.add( 'origin', this.url() ).link( this.url ).setter( function( to ) {
  582. var urlParser = document.createElement( 'a' );
  583. urlParser.href = to;
  584. // Port stripping needed by IE since it adds to host but not to event.origin.
  585. return urlParser.protocol + '//' + urlParser.host.replace( /:(80|443)$/, '' );
  586. });
  587. // first add with no value
  588. this.add( 'targetWindow', null );
  589. // This avoids SecurityErrors when setting a window object in x-origin iframe'd scenarios.
  590. this.targetWindow.set = function( to ) {
  591. var from = this._value;
  592. to = this._setter.apply( this, arguments );
  593. to = this.validate( to );
  594. if ( null === to || from === to ) {
  595. return this;
  596. }
  597. this._value = to;
  598. this._dirty = true;
  599. this.callbacks.fireWith( this, [ to, from ] );
  600. return this;
  601. };
  602. // now set it
  603. this.targetWindow( params.targetWindow || defaultTarget );
  604. // Since we want jQuery to treat the receive function as unique
  605. // to this instance, we give the function a new guid.
  606. //
  607. // This will prevent every Messenger's receive function from being
  608. // unbound when calling $.off( 'message', this.receive );
  609. this.receive = $.proxy( this.receive, this );
  610. this.receive.guid = $.guid++;
  611. $( window ).on( 'message', this.receive );
  612. },
  613. destroy: function() {
  614. $( window ).off( 'message', this.receive );
  615. },
  616. /**
  617. * Receive data from the other window.
  618. *
  619. * @param {jQuery.Event} event Event with embedded data.
  620. */
  621. receive: function( event ) {
  622. var message;
  623. event = event.originalEvent;
  624. if ( ! this.targetWindow || ! this.targetWindow() ) {
  625. return;
  626. }
  627. // Check to make sure the origin is valid.
  628. if ( this.origin() && event.origin !== this.origin() )
  629. return;
  630. // Ensure we have a string that's JSON.parse-able
  631. if ( typeof event.data !== 'string' || event.data[0] !== '{' ) {
  632. return;
  633. }
  634. message = JSON.parse( event.data );
  635. // Check required message properties.
  636. if ( ! message || ! message.id || typeof message.data === 'undefined' )
  637. return;
  638. // Check if channel names match.
  639. if ( ( message.channel || this.channel() ) && this.channel() !== message.channel )
  640. return;
  641. this.trigger( message.id, message.data );
  642. },
  643. /**
  644. * Send data to the other window.
  645. *
  646. * @param {string} id The event name.
  647. * @param {object} data Data.
  648. */
  649. send: function( id, data ) {
  650. var message;
  651. data = typeof data === 'undefined' ? null : data;
  652. if ( ! this.url() || ! this.targetWindow() )
  653. return;
  654. message = { id: id, data: data };
  655. if ( this.channel() )
  656. message.channel = this.channel();
  657. this.targetWindow().postMessage( JSON.stringify( message ), this.origin() );
  658. }
  659. });
  660. // Add the Events mixin to api.Messenger.
  661. $.extend( api.Messenger.prototype, api.Events );
  662. /**
  663. * Notification.
  664. *
  665. * @class
  666. * @augments wp.customize.Class
  667. * @since 4.6.0
  668. *
  669. * @memberOf wp.customize
  670. * @alias wp.customize.Notification
  671. *
  672. * @param {string} code - The error code.
  673. * @param {object} params - Params.
  674. * @param {string} params.message=null - The error message.
  675. * @param {string} [params.type=error] - The notification type.
  676. * @param {boolean} [params.fromServer=false] - Whether the notification was server-sent.
  677. * @param {string} [params.setting=null] - The setting ID that the notification is related to.
  678. * @param {*} [params.data=null] - Any additional data.
  679. */
  680. api.Notification = api.Class.extend(/** @lends wp.customize.Notification.prototype */{
  681. /**
  682. * Template function for rendering the notification.
  683. *
  684. * This will be populated with template option or else it will be populated with template from the ID.
  685. *
  686. * @since 4.9.0
  687. * @var {Function}
  688. */
  689. template: null,
  690. /**
  691. * ID for the template to render the notification.
  692. *
  693. * @since 4.9.0
  694. * @var {string}
  695. */
  696. templateId: 'customize-notification',
  697. /**
  698. * Additional class names to add to the notification container.
  699. *
  700. * @since 4.9.0
  701. * @var {string}
  702. */
  703. containerClasses: '',
  704. /**
  705. * Initialize notification.
  706. *
  707. * @since 4.9.0
  708. *
  709. * @param {string} code - Notification code.
  710. * @param {object} params - Notification parameters.
  711. * @param {string} params.message - Message.
  712. * @param {string} [params.type=error] - Type.
  713. * @param {string} [params.setting] - Related setting ID.
  714. * @param {Function} [params.template] - Function for rendering template. If not provided, this will come from templateId.
  715. * @param {string} [params.templateId] - ID for template to render the notification.
  716. * @param {string} [params.containerClasses] - Additional class names to add to the notification container.
  717. * @param {boolean} [params.dismissible] - Whether the notification can be dismissed.
  718. */
  719. initialize: function( code, params ) {
  720. var _params;
  721. this.code = code;
  722. _params = _.extend(
  723. {
  724. message: null,
  725. type: 'error',
  726. fromServer: false,
  727. data: null,
  728. setting: null,
  729. template: null,
  730. dismissible: false,
  731. containerClasses: ''
  732. },
  733. params
  734. );
  735. delete _params.code;
  736. _.extend( this, _params );
  737. },
  738. /**
  739. * Render the notification.
  740. *
  741. * @since 4.9.0
  742. *
  743. * @returns {jQuery} Notification container element.
  744. */
  745. render: function() {
  746. var notification = this, container, data;
  747. if ( ! notification.template ) {
  748. notification.template = wp.template( notification.templateId );
  749. }
  750. data = _.extend( {}, notification, {
  751. alt: notification.parent && notification.parent.alt
  752. } );
  753. container = $( notification.template( data ) );
  754. if ( notification.dismissible ) {
  755. container.find( '.notice-dismiss' ).on( 'click keydown', function( event ) {
  756. if ( 'keydown' === event.type && 13 !== event.which ) {
  757. return;
  758. }
  759. if ( notification.parent ) {
  760. notification.parent.remove( notification.code );
  761. } else {
  762. container.remove();
  763. }
  764. });
  765. }
  766. return container;
  767. }
  768. });
  769. // The main API object is also a collection of all customizer settings.
  770. api = $.extend( new api.Values(), api );
  771. /**
  772. * Get all customize settings.
  773. *
  774. * @memberOf wp.customize
  775. *
  776. * @return {object}
  777. */
  778. api.get = function() {
  779. var result = {};
  780. this.each( function( obj, key ) {
  781. result[ key ] = obj.get();
  782. });
  783. return result;
  784. };
  785. /**
  786. * Utility function namespace
  787. *
  788. * @namespace wp.customize.utils
  789. */
  790. api.utils = {};
  791. /**
  792. * Parse query string.
  793. *
  794. * @since 4.7.0
  795. * @access public
  796. * @memberOf wp.customize.utils
  797. *
  798. * @param {string} queryString Query string.
  799. * @returns {object} Parsed query string.
  800. */
  801. api.utils.parseQueryString = function parseQueryString( queryString ) {
  802. var queryParams = {};
  803. _.each( queryString.split( '&' ), function( pair ) {
  804. var parts, key, value;
  805. parts = pair.split( '=', 2 );
  806. if ( ! parts[0] ) {
  807. return;
  808. }
  809. key = decodeURIComponent( parts[0].replace( /\+/g, ' ' ) );
  810. key = key.replace( / /g, '_' ); // What PHP does.
  811. if ( _.isUndefined( parts[1] ) ) {
  812. value = null;
  813. } else {
  814. value = decodeURIComponent( parts[1].replace( /\+/g, ' ' ) );
  815. }
  816. queryParams[ key ] = value;
  817. } );
  818. return queryParams;
  819. };
  820. /**
  821. * Expose the API publicly on window.wp.customize
  822. *
  823. * @namespace wp.customize
  824. */
  825. exports.customize = api;
  826. })( wp, jQuery );