PageRenderTime 1037ms CodeModel.GetById 24ms RepoModel.GetById 5ms app.codeStats 1ms

/wp-content/plugins/siteorigin-panels/js/siteorigin-panels-24.js

https://gitlab.com/mostafame/team_website
JavaScript | 1845 lines | 1246 code | 327 blank | 272 comment | 149 complexity | 3d6385e1e6f0502ba112b2d995ad6f43 MD5 | raw file
  1. (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
  2. var panels = window.panels;
  3. module.exports = Backbone.Collection.extend( {
  4. model: panels.model.cell,
  5. initialize: function () {
  6. },
  7. /**
  8. * Get the total weight for the cells in this collection.
  9. * @returns {number}
  10. */
  11. totalWeight: function () {
  12. var totalWeight = 0;
  13. this.each( function ( cell ) {
  14. totalWeight += cell.get( 'weight' );
  15. } );
  16. return totalWeight;
  17. },
  18. visualSortComparator: function ( item ) {
  19. if ( ! _.isNull( item.indexes ) ) {
  20. return item.indexes.builder;
  21. } else {
  22. return null;
  23. }
  24. },
  25. visualSort: function(){
  26. var oldComparator = this.comparator;
  27. this.comparator = this.visualSortComparator;
  28. this.sort();
  29. this.comparator = oldComparator;
  30. }
  31. } );
  32. },{}],2:[function(require,module,exports){
  33. var panels = window.panels;
  34. module.exports = Backbone.Collection.extend( {
  35. model: panels.model.historyEntry,
  36. /**
  37. * The builder model
  38. */
  39. builder: null,
  40. /**
  41. * The maximum number of items in the history
  42. */
  43. maxSize: 12,
  44. initialize: function () {
  45. this.on( 'add', this.onAddEntry, this );
  46. },
  47. /**
  48. * Add an entry to the collection.
  49. *
  50. * @param text The text that defines the action taken to get to this
  51. * @param data
  52. */
  53. addEntry: function ( text, data ) {
  54. if ( _.isEmpty( data ) ) {
  55. data = this.builder.getPanelsData();
  56. }
  57. var entry = new panels.model.historyEntry( {
  58. text: text,
  59. data: JSON.stringify( data ),
  60. time: parseInt( new Date().getTime() / 1000 ),
  61. collection: this
  62. } );
  63. this.add( entry );
  64. },
  65. /**
  66. * Resize the collection so it's not bigger than this.maxSize
  67. */
  68. onAddEntry: function ( entry ) {
  69. if ( this.models.length > 1 ) {
  70. var lastEntry = this.at( this.models.length - 2 );
  71. if (
  72. (
  73. entry.get( 'text' ) === lastEntry.get( 'text' ) && entry.get( 'time' ) - lastEntry.get( 'time' ) < 15
  74. ) ||
  75. (
  76. entry.get( 'data' ) === lastEntry.get( 'data' )
  77. )
  78. ) {
  79. // If both entries have the same text and are within 20 seconds of each other, or have the same data, then remove most recent
  80. this.remove( entry );
  81. lastEntry.set( 'count', lastEntry.get( 'count' ) + 1 );
  82. }
  83. }
  84. // Make sure that there are not to many entries in this collection
  85. while ( this.models.length > this.maxSize ) {
  86. this.shift();
  87. }
  88. }
  89. } );
  90. },{}],3:[function(require,module,exports){
  91. var panels = window.panels;
  92. module.exports = Backbone.Collection.extend( {
  93. model: panels.model.row,
  94. /**
  95. * Destroy all the rows in this collection
  96. */
  97. empty: function () {
  98. var model;
  99. do {
  100. model = this.collection.first();
  101. if ( ! model ) {
  102. break;
  103. }
  104. model.destroy();
  105. } while ( true );
  106. },
  107. visualSortComparator: function ( item ) {
  108. if ( ! _.isNull( item.indexes ) ) {
  109. return item.indexes.builder;
  110. } else {
  111. return null;
  112. }
  113. },
  114. visualSort: function(){
  115. var oldComparator = this.comparator;
  116. this.comparator = this.visualSortComparator;
  117. this.sort();
  118. this.comparator = oldComparator;
  119. }
  120. } );
  121. },{}],4:[function(require,module,exports){
  122. var panels = window.panels;
  123. module.exports = Backbone.Collection.extend( {
  124. model: panels.model.widget,
  125. initialize: function () {
  126. },
  127. visualSortComparator: function ( item ) {
  128. if ( ! _.isNull( item.indexes ) ) {
  129. return item.indexes.builder;
  130. } else {
  131. return null;
  132. }
  133. },
  134. visualSort: function(){
  135. var oldComparator = this.comparator;
  136. this.comparator = this.visualSortComparator;
  137. this.sort();
  138. this.comparator = oldComparator;
  139. }
  140. } );
  141. },{}],5:[function(require,module,exports){
  142. var panels = window.panels, $ = jQuery;
  143. module.exports = panels.view.dialog.extend( {
  144. dialogClass: 'so-panels-dialog-add-builder',
  145. render: function () {
  146. // Render the dialog and attach it to the builder interface
  147. this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-builder' ).html(), {} ) );
  148. this.$( '.so-content .siteorigin-panels-builder' ).append( this.builder.$el );
  149. },
  150. initializeDialog: function () {
  151. var thisView = this;
  152. this.once( 'open_dialog_complete', function () {
  153. thisView.builder.initSortable();
  154. } );
  155. this.on( 'open_dialog_complete', function () {
  156. thisView.builder.trigger( 'builder_resize' );
  157. } );
  158. }
  159. } );
  160. },{}],6:[function(require,module,exports){
  161. var panels = window.panels, $ = jQuery;
  162. module.exports = panels.view.dialog.extend( {
  163. historyEntryTemplate: _.template( $( '#siteorigin-panels-dialog-history-entry' ).html().panelsProcessTemplate() ),
  164. entries: {},
  165. currentEntry: null,
  166. revertEntry: null,
  167. selectedEntry: null,
  168. previewScrollTop: null,
  169. dialogClass: 'so-panels-dialog-history',
  170. events: {
  171. 'click .so-close': 'closeDialog',
  172. 'click .so-restore': 'restoreSelectedEntry'
  173. },
  174. initializeDialog: function () {
  175. this.entries = new panels.collection.historyEntries();
  176. this.on( 'open_dialog', this.setCurrentEntry, this );
  177. this.on( 'open_dialog', this.renderHistoryEntries, this );
  178. },
  179. render: function () {
  180. var thisView = this;
  181. // Render the dialog and attach it to the builder interface
  182. this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-history' ).html(), {} ) );
  183. this.$( 'iframe.siteorigin-panels-history-iframe' ).load( function () {
  184. var $$ = $( this );
  185. $$.show();
  186. $$.contents().scrollTop( thisView.previewScrollTop );
  187. } );
  188. },
  189. /**
  190. * Set the original entry. This should be set when creating the dialog.
  191. *
  192. * @param {panels.model.builder} builder
  193. */
  194. setRevertEntry: function ( builder ) {
  195. this.revertEntry = new panels.model.historyEntry( {
  196. data: JSON.stringify( builder.getPanelsData() ),
  197. time: parseInt( new Date().getTime() / 1000 )
  198. } );
  199. },
  200. /**
  201. * This is triggered when the dialog is opened.
  202. */
  203. setCurrentEntry: function () {
  204. this.currentEntry = new panels.model.historyEntry( {
  205. data: JSON.stringify( this.builder.model.getPanelsData() ),
  206. time: parseInt( new Date().getTime() / 1000 )
  207. } );
  208. this.selectedEntry = this.currentEntry;
  209. this.previewEntry( this.currentEntry );
  210. this.$( '.so-buttons .so-restore' ).addClass( 'disabled' );
  211. },
  212. /**
  213. * Render the history entries in the sidebar
  214. */
  215. renderHistoryEntries: function () {
  216. // Set up an interval that will display the time since every 10 seconds
  217. var thisView = this;
  218. var c = this.$( '.history-entries' ).empty();
  219. if ( this.currentEntry.get( 'data' ) !== this.revertEntry.get( 'data' ) || ! _.isEmpty( this.entries.models ) ) {
  220. $( this.historyEntryTemplate( {title: panelsOptions.loc.history.revert, count: 1} ) )
  221. .data( 'historyEntry', this.revertEntry )
  222. .prependTo( c );
  223. }
  224. // Now load all the entries in this.entries
  225. this.entries.each( function ( entry ) {
  226. var html = thisView.historyEntryTemplate( {
  227. title: panelsOptions.loc.history[entry.get( 'text' )],
  228. count: entry.get( 'count' )
  229. } );
  230. $( html )
  231. .data( 'historyEntry', entry )
  232. .prependTo( c );
  233. } );
  234. $( this.historyEntryTemplate( {title: panelsOptions.loc.history['current'], count: 1} ) )
  235. .data( 'historyEntry', this.currentEntry )
  236. .addClass( 'so-selected' )
  237. .prependTo( c );
  238. // Handle loading and selecting
  239. c.find( '.history-entry' ).click( function () {
  240. var $$ = jQuery( this );
  241. c.find( '.history-entry' ).not( $$ ).removeClass( 'so-selected' );
  242. $$.addClass( 'so-selected' );
  243. var entry = $$.data( 'historyEntry' );
  244. thisView.selectedEntry = entry;
  245. if ( thisView.selectedEntry.cid !== thisView.currentEntry.cid ) {
  246. thisView.$( '.so-buttons .so-restore' ).removeClass( 'disabled' );
  247. } else {
  248. thisView.$( '.so-buttons .so-restore' ).addClass( 'disabled' );
  249. }
  250. thisView.previewEntry( entry );
  251. } );
  252. this.updateEntryTimes();
  253. },
  254. /**
  255. * Preview an entry
  256. *
  257. * @param entry
  258. */
  259. previewEntry: function ( entry ) {
  260. var iframe = this.$( 'iframe.siteorigin-panels-history-iframe' );
  261. iframe.hide();
  262. this.previewScrollTop = iframe.contents().scrollTop();
  263. this.$( 'form.history-form input[name="live_editor_panels_data"]' ).val( entry.get( 'data' ) );
  264. this.$( 'form.history-form' ).submit();
  265. },
  266. /**
  267. * Restore the current entry
  268. */
  269. restoreSelectedEntry: function () {
  270. if ( this.$( '.so-buttons .so-restore' ).hasClass( 'disabled' ) ) {
  271. return false;
  272. }
  273. if ( this.currentEntry.get( 'data' ) === this.selectedEntry.get( 'data' ) ) {
  274. this.closeDialog();
  275. return false;
  276. }
  277. // Add an entry for this restore event
  278. if ( this.selectedEntry.get( 'text' ) !== 'restore' ) {
  279. this.builder.addHistoryEntry( 'restore', this.builder.model.getPanelsData() );
  280. }
  281. this.builder.model.loadPanelsData( JSON.parse( this.selectedEntry.get( 'data' ) ) );
  282. this.closeDialog();
  283. return false;
  284. },
  285. /**
  286. * Update the entry times for the list of entries down the side
  287. */
  288. updateEntryTimes: function () {
  289. var thisView = this;
  290. this.$( '.history-entries .history-entry' ).each( function () {
  291. var $$ = jQuery( this );
  292. var time = $$.find( '.timesince' );
  293. var entry = $$.data( 'historyEntry' );
  294. time.html( thisView.timeSince( entry.get( 'time' ) ) );
  295. } );
  296. },
  297. /**
  298. * Gets the time since as a nice string.
  299. *
  300. * @param date
  301. */
  302. timeSince: function ( time ) {
  303. var diff = parseInt( new Date().getTime() / 1000 ) - time;
  304. var parts = [];
  305. var interval;
  306. // There are 3600 seconds in an hour
  307. if ( diff > 3600 ) {
  308. interval = Math.floor( diff / 3600 );
  309. if ( interval === 1 ) {
  310. parts.push( panelsOptions.loc.time.hour.replace( '%d', interval ) );
  311. } else {
  312. parts.push( panelsOptions.loc.time.hours.replace( '%d', interval ) );
  313. }
  314. diff -= interval * 3600;
  315. }
  316. // There are 60 seconds in a minute
  317. if ( diff > 60 ) {
  318. interval = Math.floor( diff / 60 );
  319. if ( interval === 1 ) {
  320. parts.push( panelsOptions.loc.time.minute.replace( '%d', interval ) );
  321. } else {
  322. parts.push( panelsOptions.loc.time.minutes.replace( '%d', interval ) );
  323. }
  324. diff -= interval * 60;
  325. }
  326. if ( diff > 0 ) {
  327. if ( diff === 1 ) {
  328. parts.push( panelsOptions.loc.time.second.replace( '%d', diff ) );
  329. } else {
  330. parts.push( panelsOptions.loc.time.seconds.replace( '%d', diff ) );
  331. }
  332. }
  333. // Return the amount of time ago
  334. return _.isEmpty( parts ) ? panelsOptions.loc.time.now : panelsOptions.loc.time.ago.replace( '%s', parts.slice( 0, 2 ).join( ', ' ) );
  335. }
  336. } );
  337. },{}],7:[function(require,module,exports){
  338. var panels = window.panels, $ = jQuery;
  339. module.exports = panels.view.dialog.extend( {
  340. directoryTemplate: _.template( $( '#siteorigin-panels-directory-items' ).html().panelsProcessTemplate() ),
  341. builder: null,
  342. dialogClass: 'so-panels-dialog-prebuilt-layouts',
  343. layoutCache: {},
  344. currentTab: false,
  345. directoryPage: 1,
  346. events: {
  347. 'click .so-close': 'closeDialog',
  348. 'click .so-sidebar-tabs li a': 'tabClickHandler',
  349. 'click .so-content .layout': 'layoutClickHandler',
  350. 'keyup .so-sidebar-search': 'searchHandler',
  351. // The directory items
  352. 'click .so-screenshot, .so-title': 'directoryItemClickHandler'
  353. },
  354. /**
  355. * Initialize the prebuilt dialog.
  356. */
  357. initializeDialog: function () {
  358. var thisView = this;
  359. this.on( 'open_dialog', function () {
  360. thisView.$( '.so-sidebar-tabs li a' ).first().click();
  361. thisView.$( '.so-status' ).removeClass( 'so-panels-loading' );
  362. } );
  363. this.on( 'button_click', this.toolbarButtonClick, this );
  364. },
  365. /**
  366. * Render the prebuilt layouts dialog
  367. */
  368. render: function () {
  369. this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-prebuilt' ).html(), {} ) );
  370. this.initToolbar();
  371. },
  372. /**
  373. *
  374. * @param e
  375. * @return {boolean}
  376. */
  377. tabClickHandler: function ( e ) {
  378. e.preventDefault();
  379. // Reset selected item state when changing tabs
  380. this.selectedLayoutItem = null;
  381. this.uploadedLayout = null;
  382. this.updateButtonState( false );
  383. this.$( '.so-sidebar-tabs li' ).removeClass( 'tab-active' );
  384. var $$ = $( e.target );
  385. var tab = $$.attr( 'href' ).split( '#' )[1];
  386. $$.parent().addClass( 'tab-active' );
  387. var thisView = this;
  388. // Empty everything
  389. this.$( '.so-content' ).empty();
  390. thisView.currentTab = tab;
  391. if ( tab == 'import' ) {
  392. this.displayImportExport();
  393. } else {
  394. this.displayLayoutDirectory( '', 1, tab );
  395. }
  396. thisView.$( '.so-sidebar-search' ).val( '' );
  397. },
  398. /**
  399. * Display and setup the import/export form
  400. */
  401. displayImportExport: function () {
  402. var c = this.$( '.so-content' ).empty().removeClass( 'so-panels-loading' );
  403. c.html( $( '#siteorigin-panels-dialog-prebuilt-importexport' ).html() );
  404. var thisView = this;
  405. var uploadUi = thisView.$( '.import-upload-ui' ).hide();
  406. // Create the uploader
  407. var uploader = new plupload.Uploader( {
  408. runtimes: 'html5,silverlight,flash,html4',
  409. browse_button: uploadUi.find( '.file-browse-button' ).get( 0 ),
  410. container: uploadUi.get( 0 ),
  411. drop_element: uploadUi.find( '.drag-upload-area' ).get( 0 ),
  412. file_data_name: 'panels_import_data',
  413. multiple_queues: false,
  414. max_file_size: panelsOptions.plupload.max_file_size,
  415. url: panelsOptions.plupload.url,
  416. flash_swf_url: panelsOptions.plupload.flash_swf_url,
  417. silverlight_xap_url: panelsOptions.plupload.silverlight_xap_url,
  418. filters: [
  419. {title: panelsOptions.plupload.filter_title, extensions: 'json'}
  420. ],
  421. multipart_params: {
  422. action: 'so_panels_import_layout'
  423. },
  424. init: {
  425. PostInit: function ( uploader ) {
  426. if ( uploader.features.dragdrop ) {
  427. uploadUi.addClass( 'has-drag-drop' );
  428. }
  429. uploadUi.show().find( '.progress-precent' ).css( 'width', '0%' );
  430. },
  431. FilesAdded: function ( uploader ) {
  432. uploadUi.find( '.file-browse-button' ).blur();
  433. uploadUi.find( '.drag-upload-area' ).removeClass( 'file-dragover' );
  434. uploadUi.find( '.progress-bar' ).fadeIn( 'fast' );
  435. thisView.$( '.js-so-selected-file' ).text( panelsOptions.loc.prebuilt_loading );
  436. uploader.start();
  437. },
  438. UploadProgress: function ( uploader, file ) {
  439. uploadUi.find( '.progress-precent' ).css( 'width', file.percent + '%' );
  440. },
  441. FileUploaded: function ( uploader, file, response ) {
  442. var layout = JSON.parse( response.response );
  443. if ( ! _.isUndefined( layout.widgets ) ) {
  444. thisView.uploadedLayout = layout;
  445. uploadUi.find( '.progress-bar' ).hide();
  446. thisView.$( '.js-so-selected-file' ).text(
  447. panelsOptions.loc.ready_to_insert.replace( '%s', file.name )
  448. );
  449. thisView.updateButtonState( true );
  450. } else {
  451. alert( panelsOptions.plupload.error_message );
  452. }
  453. },
  454. Error: function () {
  455. alert( panelsOptions.plupload.error_message );
  456. }
  457. }
  458. } );
  459. uploader.init();
  460. // This is
  461. uploadUi.find( '.drag-upload-area' )
  462. .on( 'dragover', function () {
  463. $( this ).addClass( 'file-dragover' );
  464. } )
  465. .on( 'dragleave', function () {
  466. $( this ).removeClass( 'file-dragover' );
  467. } );
  468. // Handle exporting the file
  469. c.find( '.so-export' ).submit( function ( e ) {
  470. var $$ = $( this );
  471. $$.find( 'input[name="panels_export_data"]' ).val( JSON.stringify( thisView.builder.model.getPanelsData() ) );
  472. } );
  473. },
  474. /**
  475. * Display the layout directory tab.
  476. *
  477. * @param query
  478. */
  479. displayLayoutDirectory: function ( search, page, type ) {
  480. var thisView = this;
  481. var c = this.$( '.so-content' ).empty().addClass( 'so-panels-loading' );
  482. if ( search === undefined ) {
  483. search = '';
  484. }
  485. if ( page === undefined ) {
  486. page = 1;
  487. }
  488. if ( type === undefined ) {
  489. type = 'directory';
  490. }
  491. if ( type === 'directory' && ! panelsOptions.directory_enabled ) {
  492. // Display the button to enable the prebuilt layout
  493. c.removeClass( 'so-panels-loading' ).html( $( '#siteorigin-panels-directory-enable' ).html() );
  494. c.find( '.so-panels-enable-directory' ).click( function ( e ) {
  495. e.preventDefault();
  496. // Sent the query to enable the directory, then enable the directory
  497. $.get(
  498. panelsOptions.ajaxurl,
  499. {action: 'so_panels_directory_enable'},
  500. function () {
  501. }
  502. );
  503. // Enable the layout directory
  504. panelsOptions.directory_enabled = true;
  505. c.addClass( 'so-panels-loading' );
  506. thisView.displayLayoutDirectory( search, page );
  507. } );
  508. return;
  509. }
  510. // Get all the items for the current query
  511. $.get(
  512. panelsOptions.ajaxurl,
  513. {
  514. action: 'so_panels_layouts_query',
  515. search: search,
  516. page: page,
  517. type: type,
  518. },
  519. function ( data ) {
  520. // Skip this if we're no longer viewing the layout directory
  521. if ( thisView.currentTab !== type ) {
  522. return;
  523. }
  524. // Add the directory items
  525. c.removeClass( 'so-panels-loading' ).html( thisView.directoryTemplate( data ) );
  526. // Lets setup the next and previous buttons
  527. var prev = c.find( '.so-previous' ), next = c.find( '.so-next' );
  528. if ( page <= 1 ) {
  529. prev.addClass( 'button-disabled' );
  530. } else {
  531. prev.click( function ( e ) {
  532. e.preventDefault();
  533. thisView.displayLayoutDirectory( search, page - 1, thisView.currentTab );
  534. } );
  535. }
  536. if ( page === data.max_num_pages || data.max_num_pages === 0 ) {
  537. next.addClass( 'button-disabled' );
  538. } else {
  539. next.click( function ( e ) {
  540. e.preventDefault();
  541. thisView.displayLayoutDirectory( search, page + 1, thisView.currentTab );
  542. } );
  543. }
  544. // Handle nice preloading of the screenshots
  545. c.find( '.so-screenshot' ).each( function () {
  546. var $$ = $( this ), $a = $$.find( '.so-screenshot-wrapper' );
  547. $a.css( 'height', (
  548. $a.width() / 4 * 3
  549. ) + 'px' ).addClass( 'so-loading' );
  550. if ( $$.data( 'src' ) !== '' ) {
  551. // Set the initial height
  552. var $img = $( '<img/>' ).attr( 'src', $$.data( 'src' ) ).load( function () {
  553. $a.removeClass( 'so-loading' ).css( 'height', 'auto' );
  554. $img.appendTo( $a ).hide().fadeIn( 'fast' );
  555. } );
  556. } else {
  557. $( '<img/>' ).attr( 'src', panelsOptions.prebuiltDefaultScreenshot ).appendTo( $a ).hide().fadeIn( 'fast' );
  558. }
  559. } );
  560. // Set the title
  561. c.find( '.so-directory-browse' ).html( data.title );
  562. },
  563. 'json'
  564. );
  565. },
  566. /**
  567. * Set the selected state for the clicked layout directory item and remove previously selected item.
  568. * Enable the toolbar buttons.
  569. */
  570. directoryItemClickHandler: function ( e ) {
  571. var $directoryItem = this.$( e.target ).closest( '.so-directory-item' );
  572. this.$( '.so-directory-items' ).find( '.selected' ).removeClass( 'selected' );
  573. $directoryItem.addClass( 'selected' );
  574. this.selectedLayoutItem = {lid: $directoryItem.data( 'layout-id' ), type: $directoryItem.data( 'layout-type' )};
  575. this.updateButtonState( true );
  576. },
  577. /**
  578. * Load a particular layout into the builder.
  579. *
  580. * @param id
  581. */
  582. toolbarButtonClick: function ( $button ) {
  583. if ( ! this.canAddLayout() ) {
  584. return false;
  585. }
  586. var position = $button.data( 'value' );
  587. if ( _.isUndefined( position ) ) {
  588. return false;
  589. }
  590. this.updateButtonState( false );
  591. if ( $button.hasClass( 'so-needs-confirm' ) && ! $button.hasClass( 'so-confirmed' ) ) {
  592. this.updateButtonState( true );
  593. if ( $button.hasClass( 'so-confirming' ) ) {
  594. return;
  595. }
  596. $button.addClass( 'so-confirming' );
  597. var originalText = $button.html();
  598. $button.html( '<span class="dashicons dashicons-yes"></span>' + $button.data( 'confirm' ) );
  599. setTimeout( function () {
  600. $button.removeClass( 'so-confirmed' ).html( originalText );
  601. }, 2500 );
  602. setTimeout( function () {
  603. $button.removeClass( 'so-confirming' );
  604. $button.addClass( 'so-confirmed' );
  605. }, 200 );
  606. return false;
  607. }
  608. this.addingLayout = true;
  609. if ( this.currentTab === 'import' ) {
  610. this.addLayoutToBuilder( this.uploadedLayout, position );
  611. } else {
  612. this.loadSelectedLayout().then( function ( layout ) {
  613. this.addLayoutToBuilder( layout, position );
  614. }.bind( this ) );
  615. }
  616. },
  617. canAddLayout: function () {
  618. return (
  619. this.selectedLayoutItem || this.uploadedLayout
  620. ) && ! this.addingLayout;
  621. },
  622. /**
  623. * Load the layout according to selectedLayoutItem.
  624. */
  625. loadSelectedLayout: function () {
  626. this.setStatusMessage( panelsOptions.loc.prebuilt_loading, true );
  627. var args = _.extend( this.selectedLayoutItem, {action: 'so_panels_get_layout'} );
  628. var deferredLayout = new $.Deferred();
  629. $.get(
  630. panelsOptions.ajaxurl,
  631. args,
  632. function ( layout ) {
  633. if ( layout.error !== undefined ) {
  634. // There was an error
  635. alert( layout.error );
  636. deferredLayout.reject( layout );
  637. } else {
  638. this.setStatusMessage( '', false );
  639. deferredLayout.resolve( layout );
  640. }
  641. }.bind( this )
  642. );
  643. return deferredLayout.promise();
  644. },
  645. /**
  646. * Handle an update to the search
  647. */
  648. searchHandler: function ( e ) {
  649. if ( e.keyCode === 13 ) {
  650. this.displayLayoutDirectory( $( e.currentTarget ).val(), 1, this.currentTab );
  651. }
  652. },
  653. /**
  654. * Attempt to set the 'Insert' button's state according to the `enabled` argument, also checking whether the
  655. * requirements for inserting a layout have valid values.
  656. */
  657. updateButtonState: function ( enabled ) {
  658. enabled = enabled && (
  659. this.selectedLayoutItem || this.uploadedLayout
  660. );
  661. var $button = this.$( '.so-import-layout' );
  662. $button.prop( "disabled", ! enabled );
  663. if ( enabled ) {
  664. $button.removeClass( 'disabled' );
  665. } else {
  666. $button.addClass( 'disabled' );
  667. }
  668. },
  669. addLayoutToBuilder: function ( layout, position ) {
  670. this.builder.addHistoryEntry( 'prebuilt_loaded' );
  671. this.builder.model.loadPanelsData( layout, position );
  672. this.addingLayout = false;
  673. this.closeDialog();
  674. }
  675. } );
  676. },{}],8:[function(require,module,exports){
  677. var panels = window.panels, $ = jQuery;
  678. module.exports = panels.view.dialog.extend( {
  679. cellPreviewTemplate: _.template( $( '#siteorigin-panels-dialog-row-cell-preview' ).html().panelsProcessTemplate() ),
  680. events: {
  681. 'click .so-close': 'closeDialog',
  682. // Toolbar buttons
  683. 'click .so-toolbar .so-save': 'saveHandler',
  684. 'click .so-toolbar .so-insert': 'insertHandler',
  685. 'click .so-toolbar .so-delete': 'deleteHandler',
  686. 'click .so-toolbar .so-duplicate': 'duplicateHandler',
  687. // Changing the row
  688. 'change .row-set-form > *': 'setCellsFromForm',
  689. 'click .row-set-form button.set-row': 'setCellsFromForm'
  690. },
  691. dialogClass: 'so-panels-dialog-row-edit',
  692. styleType: 'row',
  693. dialogType: 'edit',
  694. /**
  695. * The current settings, not yet saved to the model
  696. */
  697. row: {
  698. // This is just the cell weights, cell content is not edited by this dialog
  699. cells: [],
  700. // The style settings of the row
  701. style: {}
  702. },
  703. initializeDialog: function () {
  704. this.on( 'open_dialog', function () {
  705. if ( ! _.isUndefined( this.model ) && ! _.isEmpty( this.model.cells ) ) {
  706. this.setRowModel( this.model );
  707. } else {
  708. this.setRowModel( null );
  709. }
  710. this.regenerateRowPreview();
  711. }, this );
  712. // This is the default row layout
  713. this.row = {
  714. cells: [0.5, 0.5],
  715. style: {}
  716. };
  717. // Refresh panels data after both dialog form components are loaded
  718. this.dialogFormsLoaded = 0;
  719. var thisView = this;
  720. this.on( 'form_loaded styles_loaded', function () {
  721. this.dialogFormsLoaded ++;
  722. if ( this.dialogFormsLoaded === 2 ) {
  723. thisView.updateModel( {
  724. refreshArgs: {
  725. silent: true
  726. }
  727. } );
  728. }
  729. } );
  730. },
  731. /**
  732. *
  733. * @param dialogType Either "edit" or "create"
  734. */
  735. setRowDialogType: function ( dialogType ) {
  736. this.dialogType = dialogType;
  737. },
  738. /**
  739. * Render the new row dialog
  740. */
  741. render: function ( dialogType ) {
  742. this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-row' ).html(), {dialogType: this.dialogType} ) );
  743. if ( this.dialogType === 'edit' ) {
  744. // Now we need to attach the style window
  745. this.styles = new panels.view.styles();
  746. this.styles.model = this.model;
  747. this.styles.render( 'row', $( '#post_ID' ).val(), {
  748. builderType: this.builder.config.builderType,
  749. dialog: this
  750. } );
  751. if( ! this.builder.supports( 'addRow' ) ) {
  752. this.$( '.so-buttons .so-duplicate' ).remove();
  753. }
  754. if( ! this.builder.supports( 'deleteRow' ) ) {
  755. this.$( '.so-buttons .so-delete' ).remove();
  756. }
  757. var $rightSidebar = this.$( '.so-sidebar.so-right-sidebar' );
  758. this.styles.attach( $rightSidebar );
  759. // Handle the loading class
  760. this.styles.on( 'styles_loaded', function ( hasStyles ) {
  761. // If we have styles remove the loading spinner, else remove the whole empty sidebar.
  762. if ( hasStyles ) {
  763. $rightSidebar.removeClass( 'so-panels-loading' );
  764. } else {
  765. $rightSidebar.closest( '.so-panels-dialog' ).removeClass( 'so-panels-dialog-has-right-sidebar' );
  766. $rightSidebar.remove();
  767. }
  768. }, this );
  769. $rightSidebar.addClass( 'so-panels-loading' );
  770. }
  771. if ( ! _.isUndefined( this.model ) ) {
  772. // Set the initial value of the
  773. this.$( 'input.so-row-field' ).val( this.model.cells.length );
  774. }
  775. var thisView = this;
  776. this.$( 'input.so-row-field' ).keyup( function () {
  777. $( this ).trigger( 'change' );
  778. } );
  779. return this;
  780. },
  781. /**
  782. * Set the row model we'll be using for this dialog.
  783. *
  784. * @param model
  785. */
  786. setRowModel: function ( model ) {
  787. this.model = model;
  788. if ( _.isEmpty( this.model ) ) {
  789. return this;
  790. }
  791. // Set the rows to be a copy of the model
  792. this.row = {
  793. cells: this.model.cells.map( function ( cell ) {
  794. return cell.get( 'weight' );
  795. } ),
  796. style: {}
  797. };
  798. // Set the initial value of the cell field.
  799. this.$( 'input.so-row-field' ).val( this.model.cells.length );
  800. return this;
  801. },
  802. /**
  803. * Regenerate the row preview and resizing interface.
  804. */
  805. regenerateRowPreview: function () {
  806. var thisDialog = this;
  807. var rowPreview = this.$( '.row-preview' );
  808. rowPreview.empty();
  809. var timeout;
  810. // Represent the cells
  811. _.each( this.row.cells, function ( cell, i ) {
  812. var newCell = $( this.cellPreviewTemplate( {weight: cell} ) );
  813. rowPreview.append( newCell );
  814. var prevCell = newCell.prev();
  815. var handle;
  816. if ( prevCell.length ) {
  817. handle = $( '<div class="resize-handle"></div>' );
  818. handle
  819. .appendTo( newCell )
  820. .dblclick( function () {
  821. var t = thisDialog.row.cells[i] + thisDialog.row.cells[i - 1];
  822. thisDialog.row.cells[i] = thisDialog.row.cells[i - 1] = t / 2;
  823. thisDialog.scaleRowWidths();
  824. } );
  825. handle.draggable( {
  826. axis: 'x',
  827. containment: rowPreview,
  828. start: function ( e, ui ) {
  829. // Create the clone for the current cell
  830. var newCellClone = newCell.clone().appendTo( ui.helper ).css( {
  831. position: 'absolute',
  832. top: '0',
  833. width: newCell.outerWidth(),
  834. left: 6,
  835. height: newCell.outerHeight()
  836. } );
  837. newCellClone.find( '.resize-handle' ).remove();
  838. // Create the clone for the previous cell
  839. var prevCellClone = prevCell.clone().appendTo( ui.helper ).css( {
  840. position: 'absolute',
  841. top: '0',
  842. width: prevCell.outerWidth(),
  843. right: 6,
  844. height: prevCell.outerHeight()
  845. } );
  846. prevCellClone.find( '.resize-handle' ).remove();
  847. $( this ).data( {
  848. 'newCellClone': newCellClone,
  849. 'prevCellClone': prevCellClone
  850. } );
  851. // Hide the
  852. newCell.find( '> .preview-cell-in' ).css( 'visibility', 'hidden' );
  853. prevCell.find( '> .preview-cell-in' ).css( 'visibility', 'hidden' );
  854. },
  855. drag: function ( e, ui ) {
  856. // Calculate the new cell and previous cell widths as a percent
  857. var ncw = thisDialog.row.cells[i] - (
  858. (
  859. ui.position.left + 6
  860. ) / rowPreview.width()
  861. );
  862. var pcw = thisDialog.row.cells[i - 1] + (
  863. (
  864. ui.position.left + 6
  865. ) / rowPreview.width()
  866. );
  867. var helperLeft = ui.helper.offset().left - rowPreview.offset().left - 6;
  868. $( this ).data( 'newCellClone' ).css( 'width', rowPreview.width() * ncw )
  869. .find( '.preview-cell-weight' ).html( Math.round( ncw * 1000 ) / 10 );
  870. $( this ).data( 'prevCellClone' ).css( 'width', rowPreview.width() * pcw )
  871. .find( '.preview-cell-weight' ).html( Math.round( pcw * 1000 ) / 10 );
  872. },
  873. stop: function ( e, ui ) {
  874. // Remove the clones
  875. $( this ).data( 'newCellClone' ).remove();
  876. $( this ).data( 'prevCellClone' ).remove();
  877. // Reshow the main cells
  878. newCell.find( '.preview-cell-in' ).css( 'visibility', 'visible' );
  879. prevCell.find( '.preview-cell-in' ).css( 'visibility', 'visible' );
  880. // Calculate the new cell weights
  881. var offset = ui.position.left + 6;
  882. var percent = offset / rowPreview.width();
  883. // Ignore this if any of the cells are below 2% in width.
  884. if ( thisDialog.row.cells[i] - percent > 0.02 && thisDialog.row.cells[i - 1] + percent > 0.02 ) {
  885. thisDialog.row.cells[i] -= percent;
  886. thisDialog.row.cells[i - 1] += percent;
  887. }
  888. thisDialog.scaleRowWidths();
  889. ui.helper.css( 'left', - 6 );
  890. }
  891. } );
  892. }
  893. // Make this row weight click editable
  894. newCell.find( '.preview-cell-weight' ).click( function ( ci ) {
  895. // Disable the draggable while entering values
  896. thisDialog.$( '.resize-handle' ).css( 'pointer-event', 'none' ).draggable( 'disable' );
  897. rowPreview.find( '.preview-cell-weight' ).each( function () {
  898. var $$ = jQuery( this ).hide();
  899. $( '<input type="text" class="preview-cell-weight-input no-user-interacted" />' )
  900. .val( parseFloat( $$.html() ) ).insertAfter( $$ )
  901. .focus( function () {
  902. clearTimeout( timeout );
  903. } )
  904. .keyup( function ( e ) {
  905. if ( e.keyCode !== 9 ) {
  906. // Only register the interaction if the user didn't press tab
  907. $( this ).removeClass( 'no-user-interacted' );
  908. }
  909. // Enter is clicked
  910. if ( e.keyCode === 13 ) {
  911. e.preventDefault();
  912. $( this ).blur();
  913. }
  914. } )
  915. .keydown( function ( e ) {
  916. if ( e.keyCode === 9 ) {
  917. e.preventDefault();
  918. // Tab will always cycle around the row inputs
  919. var inputs = rowPreview.find( '.preview-cell-weight-input' );
  920. var i = inputs.index( $( this ) );
  921. if ( i === inputs.length - 1 ) {
  922. inputs.eq( 0 ).focus().select();
  923. } else {
  924. inputs.eq( i + 1 ).focus().select();
  925. }
  926. }
  927. } )
  928. .blur( function () {
  929. rowPreview.find( '.preview-cell-weight-input' ).each( function ( i, el ) {
  930. if ( isNaN( parseFloat( $( el ).val() ) ) ) {
  931. $( el ).val( Math.floor( thisDialog.row.cells[i] * 1000 ) / 10 );
  932. }
  933. } );
  934. timeout = setTimeout( function () {
  935. // If there are no weight inputs, then skip this
  936. if ( rowPreview.find( '.preview-cell-weight-input' ).legnth === 0 ) {
  937. return false;
  938. }
  939. // Go through all the inputs
  940. var rowWeights = [],
  941. rowChanged = [],
  942. changedSum = 0,
  943. unchangedSum = 0;
  944. rowPreview.find( '.preview-cell-weight-input' ).each( function ( i, el ) {
  945. var val = parseFloat( $( el ).val() );
  946. if ( isNaN( val ) ) {
  947. val = 1 / thisDialog.row.cells.length;
  948. } else {
  949. val = Math.round( val * 10 ) / 1000;
  950. }
  951. // Check within 3 decimal points
  952. var changed = ! $( el ).hasClass( 'no-user-interacted' );
  953. rowWeights.push( val );
  954. rowChanged.push( changed );
  955. if ( changed ) {
  956. changedSum += val;
  957. } else {
  958. unchangedSum += val;
  959. }
  960. } );
  961. if ( changedSum > 0 && unchangedSum > 0 && (
  962. 1 - changedSum
  963. ) > 0 ) {
  964. // Balance out the unchanged rows to occupy the weight left over by the changed sum
  965. for ( var i = 0; i < rowWeights.length; i ++ ) {
  966. if ( ! rowChanged[i] ) {
  967. rowWeights[i] = (
  968. rowWeights[i] / unchangedSum
  969. ) * (
  970. 1 - changedSum
  971. );
  972. }
  973. }
  974. }
  975. // Last check to ensure total weight is 1
  976. var sum = _.reduce( rowWeights, function ( memo, num ) {
  977. return memo + num;
  978. } );
  979. rowWeights = rowWeights.map( function ( w ) {
  980. return w / sum;
  981. } );
  982. // Set the new cell weights and regenerate the preview.
  983. if ( Math.min.apply( Math, rowWeights ) > 0.01 ) {
  984. thisDialog.row.cells = rowWeights;
  985. }
  986. // Now lets animate the cells into their new widths
  987. rowPreview.find( '.preview-cell' ).each( function ( i, el ) {
  988. $( el ).animate( {'width': Math.round( thisDialog.row.cells[i] * 1000 ) / 10 + "%"}, 250 );
  989. $( el ).find( '.preview-cell-weight-input' ).val( Math.round( thisDialog.row.cells[i] * 1000 ) / 10 );
  990. } );
  991. // So the draggable handle is not hidden.
  992. rowPreview.find( '.preview-cell' ).css( 'overflow', 'visible' );
  993. setTimeout( function () {
  994. thisDialog.regenerateRowPreview();
  995. }, 260 );
  996. }, 100 );
  997. } )
  998. .click( function () {
  999. $( this ).select();
  1000. } );
  1001. } );
  1002. $( this ).siblings( '.preview-cell-weight-input' ).select();
  1003. } );
  1004. }, this );
  1005. this.trigger( 'form_loaded', this );
  1006. },
  1007. /**
  1008. * Visually scale the row widths based on the cell weights
  1009. */
  1010. scaleRowWidths: function () {
  1011. var thisDialog = this;
  1012. this.$( '.row-preview .preview-cell' ).each( function ( i, el ) {
  1013. $( el )
  1014. .css( 'width', thisDialog.row.cells[i] * 100 + "%" )
  1015. .find( '.preview-cell-weight' ).html( Math.round( thisDialog.row.cells[i] * 1000 ) / 10 );
  1016. } );
  1017. },
  1018. /**
  1019. * Get the weights from the
  1020. */
  1021. setCellsFromForm: function () {
  1022. try {
  1023. var f = {
  1024. 'cells': parseInt( this.$( '.row-set-form input[name="cells"]' ).val() ),
  1025. 'ratio': parseFloat( this.$( '.row-set-form select[name="ratio"]' ).val() ),
  1026. 'direction': this.$( '.row-set-form select[name="ratio_direction"]' ).val()
  1027. };
  1028. if ( _.isNaN( f.cells ) ) {
  1029. f.cells = 1;
  1030. }
  1031. if ( isNaN( f.ratio ) ) {
  1032. f.ratio = 1;
  1033. }
  1034. if ( f.cells < 1 ) {
  1035. f.cells = 1;
  1036. this.$( '.row-set-form input[name="cells"]' ).val( f.cells );
  1037. }
  1038. else if ( f.cells > 10 ) {
  1039. f.cells = 10;
  1040. this.$( '.row-set-form input[name="cells"]' ).val( f.cells );
  1041. }
  1042. this.$( '.row-set-form input[name="ratio"]' ).val( f.ratio );
  1043. var cells = [];
  1044. var cellCountChanged = (
  1045. this.row.cells.length !== f.cells
  1046. );
  1047. // Now, lets create some cells
  1048. var currentWeight = 1;
  1049. for ( var i = 0; i < f.cells; i ++ ) {
  1050. cells.push( currentWeight );
  1051. currentWeight *= f.ratio;
  1052. }
  1053. // Now lets make sure that the row weights add up to 1
  1054. var totalRowWeight = _.reduce( cells, function ( memo, weight ) {
  1055. return memo + weight;
  1056. } );
  1057. cells = _.map( cells, function ( cell ) {
  1058. return cell / totalRowWeight;
  1059. } );
  1060. // Don't return cells that are too small
  1061. cells = _.filter( cells, function ( cell ) {
  1062. return cell > 0.01;
  1063. } );
  1064. if ( f.direction === 'left' ) {
  1065. cells = cells.reverse();
  1066. }
  1067. this.row.cells = cells;
  1068. if ( cellCountChanged ) {
  1069. this.regenerateRowPreview();
  1070. } else {
  1071. var thisDialog = this;
  1072. // Now lets animate the cells into their new widths
  1073. this.$( '.preview-cell' ).each( function ( i, el ) {
  1074. $( el ).animate( {'width': Math.round( thisDialog.row.cells[i] * 1000 ) / 10 + "%"}, 250 );
  1075. $( el ).find( '.preview-cell-weight' ).html( Math.round( thisDialog.row.cells[i] * 1000 ) / 10 );
  1076. } );
  1077. // So the draggable handle is not hidden.
  1078. this.$( '.preview-cell' ).css( 'overflow', 'visible' );
  1079. setTimeout( function () {
  1080. thisDialog.regenerateRowPreview();
  1081. }, 260 );
  1082. }
  1083. }
  1084. catch (err) {
  1085. console.log( 'Error setting cells - ' + err.message );
  1086. }
  1087. // Remove the button primary class
  1088. this.$( '.row-set-form .so-button-row-set' ).removeClass( 'button-primary' );
  1089. },
  1090. /**
  1091. * Handle a click on the dialog left bar tab
  1092. */
  1093. tabClickHandler: function ( $t ) {
  1094. if ( $t.attr( 'href' ) === '#row-layout' ) {
  1095. this.$( '.so-panels-dialog' ).addClass( 'so-panels-dialog-has-right-sidebar' );
  1096. } else {
  1097. this.$( '.so-panels-dialog' ).removeClass( 'so-panels-dialog-has-right-sidebar' );
  1098. }
  1099. },
  1100. /**
  1101. * Update the current model with what we have in the dialog
  1102. */
  1103. updateModel: function ( args ) {
  1104. args = _.extend( {
  1105. refresh: true,
  1106. refreshArgs: null
  1107. }, args );
  1108. // Set the cells
  1109. if( ! _.isEmpty( this.model ) ) {
  1110. this.model.setCells( this.row.cells );
  1111. }
  1112. // Update the styles if they've loaded
  1113. if ( ! _.isUndefined( this.styles ) && this.styles.stylesLoaded ) {
  1114. // This is an edit dialog, so there are styles
  1115. var style = {};
  1116. try {
  1117. style = this.getFormValues( '.so-sidebar .so-visual-styles' ).style;
  1118. }
  1119. catch ( err ) {
  1120. console.log( 'Error retrieving styles - ' + err.message );
  1121. }
  1122. this.model.set( 'style', style );
  1123. }
  1124. if ( args.refresh ) {
  1125. this.builder.model.refreshPanelsData( args.refreshArgs );
  1126. }
  1127. },
  1128. /**
  1129. * Insert the new row
  1130. */
  1131. insertHandler: function () {
  1132. this.builder.addHistoryEntry( 'row_added' );
  1133. this.model = new panels.model.row();
  1134. this.updateModel();
  1135. var activeCell = this.builder.getActiveCell( {
  1136. createCell: false,
  1137. defaultPosition: 'last'
  1138. } );
  1139. var options = {};
  1140. if ( activeCell !== null ) {
  1141. options.at = this.builder.model.rows.indexOf( activeCell.row ) + 1;
  1142. }
  1143. // Set up the model and add it to the builder
  1144. this.model.collection = this.builder.model.rows;
  1145. this.builder.model.rows.add( this.model, options );
  1146. this.closeDialog();
  1147. this.builder.model.refreshPanelsData();
  1148. return false;
  1149. },
  1150. /**
  1151. * We'll just save this model and close the dialog
  1152. */
  1153. saveHandler: function () {
  1154. this.builder.addHistoryEntry( 'row_edited' );
  1155. this.updateModel();
  1156. this.closeDialog();
  1157. this.builder.model.refreshPanelsData();
  1158. return false;
  1159. },
  1160. /**
  1161. * The user clicks delete, so trigger deletion on the row model
  1162. */
  1163. deleteHandler: function () {
  1164. // Trigger a destroy on the model that will happen with a visual indication to the user
  1165. this.model.trigger( 'visual_destroy' );
  1166. this.closeDialog( {silent: true} );
  1167. return false;
  1168. },
  1169. /**
  1170. * Duplicate this row
  1171. */
  1172. duplicateHandler: function () {
  1173. this.builder.addHistoryEntry( 'row_duplicated' );
  1174. var duplicateRow = this.model.clone( this.builder.model );
  1175. this.builder.model.rows.add( duplicateRow, {
  1176. at: this.builder.model.rows.indexOf( this.model ) + 1
  1177. } );
  1178. this.closeDialog( {silent: true} );
  1179. return false;
  1180. }
  1181. } );
  1182. },{}],9:[function(require,module,exports){
  1183. var panels = window.panels, $ = jQuery;
  1184. module.exports = panels.view.dialog.extend( {
  1185. builder: null,
  1186. sidebarWidgetTemplate: _.template( $( '#siteorigin-panels-dialog-widget-sidebar-widget' ).html().panelsProcessTemplate() ),
  1187. dialogClass: 'so-panels-dialog-edit-widget',
  1188. widgetView: false,
  1189. savingWidget: false,
  1190. events: {
  1191. 'click .so-close': 'saveHandler',
  1192. 'click .so-nav.so-previous': 'navToPrevious',
  1193. 'click .so-nav.so-next': 'navToNext',
  1194. // Action handlers
  1195. 'click .so-toolbar .so-delete': 'deleteHandler',
  1196. 'click .so-toolbar .so-duplicate': 'duplicateHandler'
  1197. },
  1198. initializeDialog: function () {
  1199. var thisView = this;
  1200. this.model.on( 'change:values', this.handleChangeValues, this );
  1201. this.model.on( 'destroy', this.remove, this );
  1202. // Refresh panels data after both dialog form components are loaded
  1203. this.dialogFormsLoaded = 0;
  1204. this.on( 'form_loaded styles_loaded', function () {
  1205. this.dialogFormsLoaded ++;
  1206. if ( this.dialogFormsLoaded === 2 ) {
  1207. thisView.updateModel( {
  1208. refreshArgs: {
  1209. silent: true
  1210. }
  1211. } );
  1212. }
  1213. } );
  1214. },
  1215. /**
  1216. * Render the widget dialog.
  1217. */
  1218. render: function () {
  1219. // Render the dialog and attach it to the builder interface
  1220. this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-widget' ).html(), {} ) );
  1221. this.loadForm();
  1222. if ( ! _.isUndefined( panelsOptions.widgets[this.model.get( 'class' )] ) ) {
  1223. this.$( '.so-title .widget-name' ).html( panelsOptions.widgets[this.model.get( 'class' )].title );
  1224. } else {
  1225. this.$( '.so-title .widget-name' ).html( panelsOptions.loc.missing_widget.title );
  1226. }
  1227. if( ! this.builder.supports( 'addWidget' ) ) {
  1228. this.$( '.so-buttons .so-duplicate' ).remove();
  1229. }
  1230. if( ! this.builder.supports( 'deleteWidget' ) ) {
  1231. this.$( '.so-buttons .so-delete' ).remove();
  1232. }
  1233. // Now we need to attach the style window
  1234. this.styles = new panels.view.styles();
  1235. this.styles.model = this.model;
  1236. this.styles.render( 'widget', $( '#post_ID' ).val(), {
  1237. builderType: this.builder.config.builderType,
  1238. dialog: this
  1239. } );
  1240. var $rightSidebar = this.$( '.so-sidebar.so-right-sidebar' );
  1241. this.styles.attach( $rightSidebar );
  1242. // Handle the loading class
  1243. this.styles.on( 'styles_loaded', function ( hasStyles ) {
  1244. // If we have styles remove the loading spinner, else remove the whole empty sidebar.
  1245. if ( hasStyles ) {
  1246. $rightSidebar.removeClass( 'so-panels-loading' );
  1247. } else {
  1248. $rightSidebar.closest( '.so-panels-dialog' ).removeClass( 'so-panels-dialog-has-right-sidebar' );
  1249. $rightSidebar.remove();
  1250. }
  1251. }, this );
  1252. $rightSidebar.addClass( 'so-panels-loading' );
  1253. },
  1254. /**
  1255. * Get the previous widget editing dialog by looking at the dom.
  1256. * @returns {*}
  1257. */
  1258. getPrevDialog: function () {
  1259. var widgets = this.builder.$( '.so-cells .cell .so-widget' );
  1260. if ( widgets.length <= 1 ) {
  1261. return false;
  1262. }
  1263. var currentIndex = widgets.index( this.widgetView.$el );
  1264. if ( currentIndex === 0 ) {
  1265. return false;
  1266. } else {
  1267. do {
  1268. widgetView = widgets.eq( --currentIndex ).data( 'view' );
  1269. if ( ! _.isUndefined( widgetView ) && ! widgetView.model.get( 'read_only' ) ) {
  1270. return widgetView.getEditDialog();
  1271. }
  1272. } while( ! _.isUndefined( widgetView ) && currentIndex > 0 );
  1273. }
  1274. return false;
  1275. },
  1276. /**
  1277. * Get the next widget editing dialog by looking at the dom.
  1278. * @returns {*}
  1279. */
  1280. getNextDialog: function () {
  1281. var widgets = this.builder.$( '.so-cells .cell .so-widget' );
  1282. if ( widgets.length <= 1 ) {
  1283. return false;
  1284. }
  1285. var currentIndex = widgets.index( this.widgetView.$el ), widgetView;
  1286. if ( currentIndex === widgets.length - 1 ) {
  1287. return false;
  1288. } else {
  1289. do {
  1290. widgetView = widgets.eq( ++currentIndex ).data( 'view' );
  1291. if ( ! _.isUndefined( widgetView ) && ! widgetView.model.get( 'read_only' ) ) {
  1292. return widgetView.getEditDialog();
  1293. }
  1294. } while( ! _.isUndefined( widgetView ) );
  1295. }
  1296. return false;
  1297. },
  1298. /**
  1299. * Load the widget form from the server.
  1300. * This is called when rendering the dialog for the first time.
  1301. */
  1302. loadForm: function () {
  1303. // don't load the form if this dialog hasn't been rendered yet
  1304. if ( ! this.$( '> *' ).length ) {
  1305. return;
  1306. }
  1307. var thisView = this;
  1308. this.$( '.so-content' ).addClass( 'so-panels-loading' );
  1309. var data = {
  1310. 'action': 'so_panels_widget_form',
  1311. 'widget': this.model.get( 'class' ),
  1312. 'instance': JSON.stringify( this.model.get( 'values' ) ),
  1313. 'raw': this.model.get( 'raw' )
  1314. };
  1315. $.post(
  1316. panelsOptions.ajaxurl,
  1317. data,
  1318. function ( result ) {
  1319. // Add in the CID of the widget model
  1320. var html = result.replace( /{\$id}/g, thisView.model.cid );
  1321. // Load this content into the form
  1322. thisView.$( '.so-content' )
  1323. .removeClass( 'so-panels-loading' )
  1324. .html( html );
  1325. // Trigger all the necessary events
  1326. thisView.trigger( 'form_loaded', thisView );
  1327. // For legacy compatibility, trigger a panelsopen event
  1328. thisView.$( '.panel-dialog' ).trigger( 'panelsopen' );
  1329. // If the main dialog is closed from this point on, save the widget content
  1330. thisView.on( 'close_dialog', thisView.updateModel, thisView );
  1331. },
  1332. 'html'
  1333. );
  1334. },
  1335. /**
  1336. * Save the widget from the form to the model
  1337. */
  1338. updateModel: function ( args ) {
  1339. args = _.extend( {
  1340. refresh: true,
  1341. refreshArgs: null
  1342. }, args );
  1343. // Get the values from the form and assign the new values to the model
  1344. this.savingWidget = true;
  1345. if ( ! this.model.get( 'missing' ) ) {
  1346. // Only get the values for non missing widgets.
  1347. var values = this.getFormValues();
  1348. if ( _.isUndefined( values.widgets ) ) {
  1349. values = {};
  1350. } else {
  1351. values = values.widgets;
  1352. values = values[Object.keys( values )[0]];
  1353. }
  1354. this.model.setValues( values );
  1355. this.model.set( 'raw', true ); // We've saved from the widget form, so this is now raw
  1356. }
  1357. if ( this.styles.stylesLoaded ) {
  1358. // If the styles view has loaded
  1359. var style = {};
  1360. try {
  1361. style = this.getFormValues( '.so-sidebar .so-visual-styles' ).style;
  1362. }
  1363. catch ( e ) {
  1364. }
  1365. this.model.set( 'style', style );
  1366. }
  1367. this.savingWidget = false;
  1368. if ( args.refresh ) {
  1369. this.builder.model.refreshPanelsData( args.refreshArgs );
  1370. }
  1371. },
  1372. /**
  1373. *
  1374. */
  1375. handleChangeValues: function () {
  1376. if ( ! this.savingWidget ) {
  1377. // Reload the form when we've changed the model and we're not currently saving from the form
  1378. this.loadForm();
  1379. }
  1380. },
  1381. /**
  1382. * Save a history entry for this widget. Called when the dialog is closed.
  1383. */
  1384. saveHandler: function () {
  1385. this.builder.addHistoryEntry( 'widget_edited' );
  1386. this.closeDialog();
  1387. },
  1388. /**
  1389. * When the user clicks delete.
  1390. *
  1391. * @returns {boolean}
  1392. */
  1393. deleteHandler: function () {
  1394. this.model.trigger( 'visual_destroy' );
  1395. this.closeDialog( {silent: true} );
  1396. this.builder.model.refreshPanelsData();
  1397. return false;
  1398. },
  1399. duplicateHandler: function () {
  1400. this.model.trigger( 'user_duplicate' );
  1401. this.closeDialog( {silent: true} );
  1402. this.builder.model.refreshPanelsData();
  1403. return false;
  1404. }
  1405. } );
  1406. },{}],10:[function(require,module,exports){
  1407. var panels = window.panels, $ = jQuery;
  1408. module.exports = panels.view.dialog.extend( {
  1409. builder: null,
  1410. widgetTemplate: _.template( $( '#siteorigin-panels-dialog-widgets-widget' ).html().panelsProcessTemplate() ),
  1411. filter: {},
  1412. dialogClass: 'so-panels-dialog-add-widget',
  1413. events: {
  1414. 'click .so-close': 'closeDialog',
  1415. 'click .widget-type': 'widgetClickHandler',
  1416. 'keyup .so-sidebar-search': 'searchHandler'
  1417. },
  1418. /**
  1419. * Initialize the widget adding dialog
  1420. */
  1421. initializeDialog: function () {
  1422. this.on( 'open_dialog', function () {
  1423. this.filter.search = '';
  1424. this.filterWidgets( this.filter );
  1425. }, this );
  1426. this.on( 'open_dialog_complete', function () {
  1427. // Clear the search and re-filter the widgets when we open the dialog
  1428. this.$( '.so-sidebar-search' ).val( '' ).focus();
  1429. this.balanceWidgetHeights();
  1430. } );
  1431. // We'll implement a custom tab click handler
  1432. this.on( 'tab_click', this.tabClickHandler, this );
  1433. },
  1434. render: function () {
  1435. // Render the dialog and attach it to the builder interface
  1436. this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-widgets' ).html(), {} ) );
  1437. // Add all the widgets
  1438. _.each( panelsOptions.widgets, function ( widget ) {
  1439. var $w = $( this.widgetTemplate( {
  1440. title: widget.title,
  1441. description: widget.description
  1442. } ) );
  1443. if ( _.isUndefined( widget.icon ) ) {
  1444. widget.icon = 'dashicons dashicons-admin-generic';
  1445. }
  1446. $( '<span class="widget-icon" />' ).addClass( widget.icon ).prependTo( $w.find( '.widget-type-wrapper' ) );
  1447. $w.data( 'class', widget.class ).appendTo( this.$( '.widget-type-list' ) );
  1448. }, this );
  1449. // Add the sidebar tabs
  1450. var tabs = this.$( '.so-sidebar-tabs' );
  1451. _.each( panelsOptions.widget_dialog_tabs, function ( tab ) {
  1452. $( this.dialogTabTemplate( {'title': tab.title} ) ).data( {
  1453. 'message': tab.message,
  1454. 'filter': tab.filter
  1455. } ).appendTo( tabs );
  1456. }, this );
  1457. // We'll be using tabs, so initialize them
  1458. this.initTabs();
  1459. var thisDialog = this;
  1460. $( window ).resize( function () {
  1461. thisDialog.balanceWidgetHeights();
  1462. } );
  1463. },
  1464. /**
  1465. * Handle a tab being clicked
  1466. */
  1467. tabClickHandler: function ( $t ) {
  1468. // Get the filter from the tab, and filter the widgets
  1469. this.filter = $t.parent().data( 'filter' );
  1470. this.filter.search = this.$( '.so-sidebar-search' ).val();
  1471. var message = $t.parent().data( 'message' );
  1472. if ( _.isEmpty( message ) ) {
  1473. message = '';
  1474. }
  1475. this.$( '.so-toolbar .so-status' ).html( message );
  1476. this.filterWidgets( this.filter );
  1477. return false;
  1478. },
  1479. /**
  1480. * Handle changes to the search value
  1481. */
  1482. searchHandler: function ( e ) {
  1483. this.filter.search = $( e.target ).val();
  1484. this.filterWidgets( this.filter );
  1485. },
  1486. /**
  1487. * Filter the widgets that we're displaying
  1488. * @param filter
  1489. */
  1490. filterWidgets: function ( filter ) {
  1491. if ( _.isUndefined( filter ) ) {
  1492. filter = {};
  1493. }
  1494. if ( _.isUndefined( filter.groups ) ) {
  1495. filter.groups = '';
  1496. }
  1497. this.$( '.widget-type-list .widget-type' ).each( function () {
  1498. var $$ = $( this ), showWidget;
  1499. var widgetClass = $$.data( 'class' );
  1500. var widgetData = (
  1501. ! _.isUndefined( panelsOptions.widgets[widgetClass] )
  1502. ) ? panelsOptions.widgets[widgetClass] : null;
  1503. if ( _.isEmpty( filter.groups ) ) {
  1504. // This filter doesn't specify groups, so show all
  1505. showWidget = true;
  1506. } else if ( widgetData !== null && ! _.isEmpty( _.intersection( filter.groups, panelsOptions.widgets[widgetClass].groups ) ) ) {
  1507. // This widget is in the filter group
  1508. showWidget = true;
  1509. } else {
  1510. // This widget is not in the filter group
  1511. showWidget = false;
  1512. }
  1513. // This can probably be done with a more intelligent operator
  1514. if ( showWidget ) {
  1515. if ( ! _.isUndefined( filter.search ) && filter.search !== '' ) {
  1516. // Check if the widget title contains the search term
  1517. if ( widgetData.title.toLowerCase().indexOf( filter.search.toLowerCase() ) === - 1 ) {
  1518. s