customize-base.js 25 KB

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