/** * Javascript Library for Layout Builder Admin * * - Page Layouts Model * * @author D-THEMES * @since 1.0 * @package WP Alpus Framework * @subpackage Theme */ 'use strict'; window.themeAdmin = window.themeAdmin || {}; ( function ( $ ) { /** * Layout Builder Model Class * * @since 1.0 */ var LayoutBuilderModel = { /** * Setup layout builder model. * * @since 1.0 */ init: function () { this.conditions = JSON.parse( JSON.stringify( alpus_layout_vars.conditions ) ) || {}; this.schemes = alpus_layout_vars.schemes || {}; this.clipboard = false; this.controls = []; for ( var part in alpus_layout_vars.controls ) { if ( !part.startsWith( 'content' ) ) { for ( var key in alpus_layout_vars.controls[ part ] ) { this.controls.push( key ); } } } }, /** * Get conditions by category or get all conditions. * * @since 1.0 * @param {string} category * @param {number} conditionNo */ getConditions: function ( category = '', conditionNo = -1 ) { if ( !category ) { return this.conditions; } if ( !this.conditions[ category ] ) { this.conditions[ category ] = []; } if ( conditionNo >= 0 && this.conditions[ category ][ conditionNo ] ) { return this.conditions[ category ][ conditionNo ]; } return this.conditions[ category ]; }, /** * Get layout option values by category. * * @since 1.0 * @param {string} category * @param {number} conditionNo */ getOptionValues: function ( category, conditionNo ) { return this.conditions[ category ] && this.conditions[ category ][ conditionNo ] ? this.conditions[ category ][ conditionNo ].options : false; }, /** * Get condition title of given category. * * @since 1.0 * @param {string} category * @param {boolean} getLayoutTitle */ getConditionTitle: function ( category, getLayoutTitle ) { if ( category && this.schemes[ category ] ) { return getLayoutTitle ? this.schemes[ category ].layout_title : this.schemes[ category ].title; } return ''; }, /** * Set condition title * @param {string} category * @param {number} conditionNo * @param {string} title */ setConditionTitle: function ( category, conditionNo, title ) { if ( this.conditions[ category ][ conditionNo ] ) { this.conditions[ category ][ conditionNo ].title = title; } this.requireSave(); }, /** * Get scheme by category. * * @since 1.0 * @param {string} category * @param {string} type */ getScheme: function ( category, type = '' ) { return type ? this.schemes[ category ].scheme[ type ] : this.schemes[ category ].scheme; }, /** * Get layout option controls for given layout part. * * @since 1.0 * @param {string} part */ getOptionControls: function ( part ) { return alpus_layout_vars.controls[ part ] ? alpus_layout_vars.controls[ part ] : false; }, /** * Get templates by block type. * * @since 1.0 * @param {string} block_type */ getTemplates: function ( block_type ) { return alpus_layout_vars.templates[ block_type ]; }, /** * Check if new conditions could be added for given category. * * @since 1.0 * @param {string} category Conditions category to check * @param {string} type Condition type to check. */ canExtendCondition: function ( category, type = '' ) { if ( !category || !this.schemes[ category ] || !this.schemes[ category ].scheme ) { return false; } return !type || ( this.schemes[ category ].scheme[ type ] && ( this.schemes[ category ].scheme[ type ].list || this.schemes[ category ].scheme[ type ].ajaxselect ) ); }, /** * Update condition UI. * @since 1.0 * @param {string} category */ updateCategoryUI: function ( category = '' ) { var _updateCategoryUI = ( function ( category ) { // update UI var $count = $( '.alpus-condition-cat-' + category + '> .alpus-condition-count' ); var count = this.conditions[ category ].filter( function ( v ) { return v } ).length; $count.text( '(' + count + ')' ); count ? $count.slideDown() : $count.slideUp(); } ).bind( this ); // update special category category && _updateCategoryUI( category ); // count total var count = 0; for ( var cat in this.conditions ) { count += this.conditions[ cat ].filter( function ( v ) { return v } ).length; // update all categories category || _updateCategoryUI( cat ); } $( '.alpus-condition-cat-site > .alpus-condition-count' ).text( '(' + count + ')' ).slideDown(); }, /** * Add a new empty condition. * * @since 1.0 * @param {string} category * @param {string} type * @return {number} added index */ addCondition: function ( category ) { if ( !this.conditions[ category ] ) { this.conditions[ category ] = []; } var data = {}; data.title = this.getConditionTitle( category, true ) + ' ' + ( this.conditions[ category ].length + 1 ); data.scheme = {}; if ( this.schemes[ category ].scheme && this.schemes[ category ].scheme.all ) { data.scheme.all = true; } this.conditions[ category ].push( data ); this.updateCategoryUI( category ); this.requireSave(); // return added index return this.conditions[ category ].length - 1; }, /** * Delete a condition. * * @since 1.0 * @param {string} category * @param {number} conditionNo */ deleteCondition: function ( category, conditionNo ) { if ( 'undefined' != typeof this.conditions[ category ][ conditionNo ] ) { this.conditions[ category ].splice( conditionNo, 1 ); $( '.alpus-layout-item[data-category=' + category + ']' ).each( function () { var no = this.getAttribute( 'data-condition-no' ); if ( no > conditionNo ) { this.setAttribute( 'data-condition-no', no - 1 ); $( this ).data( 'condition-no', no - 1 ) } } ) $( '#alpus_layout_content' ).isotope( 'updateSortData' ).isotope(); } this.updateCategoryUI( category ); this.requireSave(); }, /** * Reset a condition. If no parameter is given, all options will be reset. * * @since 1.0 * @param {string} category * @param {number} conditionNo */ // resetCondition: function (category, conditionNo) { // if (category) { // this.conditions[category][conditionNo] = // alpus_layout_vars.conditions[category] && alpus_layout_vars.conditions[category][conditionNo] ? // JSON.parse(JSON.stringify(alpus_layout_vars.conditions[category][conditionNo])) : // {}; // } else { // this.conditions = // alpus_layout_vars.conditions ? // JSON.parse(JSON.stringify(alpus_layout_vars.conditions)) : // {}; // } // this.requireSave(); // }, /** * Duplicate a condition. * * @since 1.0 * @param {string} category * @param {number} conditionNo */ duplicateCondition: function ( category, conditionNo ) { if ( category && 'number' == typeof conditionNo && this.conditions[ category ][ conditionNo ] ) { var duplicated = JSON.parse( JSON.stringify( this.conditions[ category ][ conditionNo ] ) ); $( '.alpus-layout-item[data-category=' + category + ']' ).each( function () { var no = this.getAttribute( 'data-condition-no' ); if ( no > conditionNo ) { this.setAttribute( 'data-condition-no', no * 1 + 1 ); $( this ).data( 'condition-no', no * 1 + 1 ) } } ) this.conditions[ category ].splice( conditionNo, 0, duplicated ); this.updateCategoryUI( category ); this.requireSave(); return conditionNo + 1; } }, /** * Copy options * * @since 1.0 * @param {string} category * @param {number} conditionNo */ copyOptions: function ( category, conditionNo ) { this.clipboard = { category: category, options: this.getOptionValues( category, conditionNo ) }; }, /** * Paste options * * @since 1.0 * @param {string} category * @param {number} conditionNo * @param {jQuery} $item */ pasteOptions: function ( category, conditionNo, $item ) { if ( this.clipboard ) { if ( category == this.clipboard.category ) { // paste all options if ( this.conditions[ category ][ conditionNo ] ) { this.conditions[ category ][ conditionNo ].options = this.clipboard.options; } else { this.conditions[ category ][ conditionNo ] = { options: this.clipboard.options }; } } else { if ( this.conditions[ category ][ conditionNo ].options ) { // remove current options except content for ( var optionName in this.conditions[ category ][ conditionNo ].options ) { if ( this.controls.indexOf( optionName ) ) { delete this.conditions[ category ][ conditionNo ].options[ optionName ]; } } } else { this.conditions[ category ][ conditionNo ].options = {}; } // paste copied options except content for ( var optionName in this.clipboard.options ) { if ( this.controls.indexOf( optionName ) ) { this.conditions[ category ][ conditionNo ].options[ optionName ] = this.clipboard.options[ optionName ]; } } } LayoutBuilderView.refreshLayoutStatus( $item ); this.requireSave(); } }, /** * Notify that save is required. * * @since 1.0 */ requireSave: function () { $( '.alpus-layouts-save' ).addClass( 'require-save' ); $( window ).trigger( 'show_btn_header' ); }, /** * Add a new condition with type or update existing condition's type. * * @since 1.0 * @param {string} category * @param {number} conditionNo * @param {string} scheme * @param {mixed} value {boolean} isChecked or {array} list */ setConditionScheme: function ( category, conditionNo, scheme, value ) { if ( 'undefined' == typeof this.conditions[ category ][ conditionNo ] ) { // add var v = {}; v[ scheme ] = value; v.all = true; this.conditions[ category ][ conditionNo ] = { scheme: v }; } else if ( this.conditions[ category ][ conditionNo ] ) { // update if ( !this.conditions[ category ][ conditionNo ].scheme ) { var v = {}; v[ scheme ] = value; this.conditions[ category ][ conditionNo ].scheme = v; } if ( value ) { this.conditions[ category ][ conditionNo ].scheme[ scheme ] = value; } else { delete this.conditions[ category ][ conditionNo ].scheme[ scheme ]; } } this.requireSave(); }, /** * Set type and list for given condition. * * @since 1.0 * @param {string} category * @param {number} conditionNo * @param {string} type * @param {array} list */ setConditionList: function ( category, conditionNo, type, list ) { this.conditions[ category ][ conditionNo ] = list ? { type: type, list: list } : { type: type }; this.requireSave(); }, /** * Set layout options for given condition. * * @since 1.0 * @param {string} category * @param {number} conditionNo * @param {string} option * @param {mixed} value */ setConditionOption: function ( category, conditionNo, option, value ) { if ( !this.conditions[ category ][ conditionNo ].options ) { this.conditions[ category ][ conditionNo ].options = {}; } if ( value ) { this.conditions[ category ][ conditionNo ].options[ option ] = value; } else { delete this.conditions[ category ][ conditionNo ].options[ option ]; } this.requireSave(); }, /** * Save all modifications of conditions. * * @since 1.0 */ save: function () { $( '.alpus-layouts-save' ).removeClass( 'require-save' ); $( window ).trigger( 'show_btn_header' ); if ( typeof window.top.alpus_core_vars.layout_save != 'undefined' ) { window.top.alpus_core_vars.layout_save = false; } $.post( alpus_layout_vars.ajax_url, { action: 'alpus_layout_builder_save', nonce: alpus_layout_vars.nonce, conditions: this.conditions }, function () { } ).fail( function () { $( '.alpus-layouts-save' ).addClass( 'require-save' ); $( '.alpus-modal-message' ).remove(); // issue : show message $( '.alpus-layouts-save' ).before( '' ); $( window ).trigger( 'show_btn_header' ); } ); } } /** * Layout Builder View Class * * @since 1.0 */ var LayoutBuilderView = { /** * Setup layout builder view. * * @since 1.0 */ init: function () { // Delete button this.buttonDelete = ''; // Duplicate button this.buttonDuplicate = ''; // Set conditions button this.buttonSet = ''; // get layout box template. this.layoutBoxTemplate = $( '#alpus_layout_template' ).text(); $( '#alpus_layout_template' ).remove(); // register events. $( document.body ) // events for context menu .on( 'click', '.alpus-layouts-save', this.onSave ) .on( 'contextmenu', '.alpus-layout-item', this.onContextMenu.bind( this ) ) .on( 'click', '#alpus_layout_content', this.closeContextMenu ) .on( 'click', '.alpus-condition-menu > a', this.clickContextMenuItem ) .on( 'click', '.alpus-condition-copy', this.copyOptions ) .on( 'click', '.alpus-condition-paste', this.pasteOptions ) .on( 'click', '.alpus-condition-edit-back', this.goBackFromEdit ) // events for condition .on( 'click', '.alpus-condition-cat', this.clickCategory.bind( this ) ) .on( 'click', '.alpus-layout-more', this.addCondition.bind( this ) ) .on( 'click', '.alpus-condition-delete', this.deleteCondition.bind( this ) ) // .on('click', '.alpus-condition-reset', this.resetCondition.bind(this)) // .on('click', '.alpus-layouts-reset', this.resetAll.bind(this)) .on( 'click', '.alpus-condition-duplicate', this.duplicateCondition.bind( this ) ) .on( 'change', '.alpus-scheme-options > div > label input[type=checkbox]', this.changeConditionScheme.bind( this ) ) .on( 'change', '.alpus-scheme-list', this.changeConditionItem ) // events for layout box .on( 'input', '.alpus-condition-title', this.changeConditionTitle.bind( this ) ) .on( 'click', '.alpus-layout .layout-part', this.editPart ) .on( 'click', '.alpus-condition-set', this.editCondition ) .on( 'click', this.clickOther.bind( this ) ) // events for layout control .on( 'change', '.alpus-block-select input', this.changeBlockMode.bind( this ) ) .on( 'change', '.alpus-layout-options input', this.changeOptionInput.bind( this ) ) .on( 'change', '.alpus-layout-options select', this.changeOptionInput.bind( this ) ); this.setupLayouts(); }, /** * Initialize plugins for layout controls. * * @since 1.0 */ refreshUI: function ( mode ) { if ( !mode || 'layout' == mode || 'add' == mode ) { $( '#alpus_layout_content' ).isotope(); } if ( !mode || 'add' == mode ) { this.refreshLayoutStatus(); } }, /** * Display status of layout parts. * * @since 1.0 * @param {jQuery} $container This can be omitted. */ refreshLayoutStatus: function ( $container ) { $container || ( $container = $( '#alpus_layout_content' ) ); $container.is( '.alpus-layout-item' ) || ( $container = $container.find( '.alpus-layout-item' ) ); $container.each( function () { var $item = $( this ); var category = $item.data( 'category' ); var conditionNo = $item.data( 'conditionNo' ); var optionValues = LayoutBuilderModel.getOptionValues( category, conditionNo ); if ( optionValues ) { for ( var part in alpus_layout_vars.controls ) { if ( LayoutBuilderModel.controls.indexOf( part ) ) { var optionControls = LayoutBuilderModel.getOptionControls( part ); var $part = $item.find( '.layout-part[data-part="' + part + '"]' ); var set = false; // Reset $part.removeClass( 'set hide' ); $part.children( '.block-value' ).text( '' ); // Check set for ( var control in optionControls ) { if ( optionValues[ control ] ) { set = true; break; } } if ( optionControls[ part ] && 'hide' == optionValues[ part ] ) { // Hide $part.addClass( 'hide' ); } else if ( set ) { // Set $part.addClass( 'set' ); if ( optionControls[ part ] ) { var blocks = LayoutBuilderModel.getTemplates( optionControls[ part ].type.replace( 'block_', '' ) ); if ( blocks && blocks[ optionValues[ part ] ] ) { $part.children( '.block-value' ).text( blocks[ optionValues[ part ] ] ); } } } } } } } ) }, /** * Setup layouts * * @since 1.0 */ setupLayouts: function () { var layoutItems = ''; var schemes = LayoutBuilderModel.schemes; if ( schemes ) { for ( var category in schemes ) { // add layouts already set var layouts = LayoutBuilderModel.getConditions( category ); for ( var conditionNo in layouts ) { layoutItems += this.getNewConditionUI( category, conditionNo ); } // add more button if ( 'site' != category && ( LayoutBuilderModel.canExtendCondition( category ) || !layouts.length ) ) { layoutItems += this.getAddMoreUI( category ); } } // show conditions $( '#alpus_layout_content' ).html( layoutItems ).isotope( { layoutMode: 'fitRows', filter: '.alpus-layout-item', sortBy: [ 'category', 'no' ], getSortData: { category: function ( el ) { var category = el.getAttribute( 'data-category' ); var categories = Object.keys( LayoutBuilderModel.schemes ); return categories.indexOf( category ); }, no: function ( el ) { return parseInt( el.getAttribute( 'data-condition-no' ) ); } } } ); // init plugins LayoutBuilderModel.updateCategoryUI(); this.refreshUI(); } }, /** * Refresh conditions * @param {string} category * @param {number} conditionNo */ refreshCondition: function ( category, conditionNo ) { var selector = '.alpus-layout-item'; var view = this; category && ( selector += '[data-category="' + category + '"]' ); conditionNo && ( selector += '[data-condition-no="' + conditionNo + '"]' ); $( selector ).each( function () { view.editPart( { currentTarget: $( this ).find( '.layout-part.active' ).get( 0 ) } ) } ); }, /** * Event handler to save layout controls. * * @since 1.0 */ onSave: function () { LayoutBuilderModel.save(); }, /** * Event handler to show context menu. * * @since 1.0 * @param {Event} e */ onContextMenu: function ( e ) { this.closeContextMenu(); var $item = $( e.currentTarget ); var $container = $( '.alpus-admin-panel-content' ); var containerOffset = $container.get( 0 ).getBoundingClientRect(); var category = $item.data( 'category' ); var html = '
'; $container.append( html ); $( '.alpus-condition-menu' ).data( 'item', $item ); e.preventDefault(); }, /** * Close context menu of condition. * * @since 1.0 */ closeContextMenu: function () { $( '.alpus-condition-menu' ).remove(); }, /** * Event handler to show context menu for condition. * * @since 1.0 * @param {Event} e */ clickContextMenuItem: function ( e ) { e.preventDefault(); }, /** * Event handler to copy options. * * @since 1.0 * @param {Event} e */ copyOptions: function ( e ) { var $menuItem = $( e.currentTarget ); var $item = $menuItem.parent().data( 'item' ); LayoutBuilderModel.copyOptions( $item.data( 'category' ), $item.data( 'condition-no' ) ); }, /** * Event handler to paste options. * * @since 1.0 * @param {Event} e */ pasteOptions: function ( e ) { var $item = $( e.currentTarget ).parent().data( 'item' ); // from menu item. LayoutBuilderModel.pasteOptions( $item.data( 'category' ), $item.data( 'condition-no' ), $item ); }, /** * Event handler to show conditions by category. * * @since 1.0 */ clickCategory: function ( e ) { var $category = $( e.currentTarget ).addClass( 'active' ); var category = $category.data( 'category' ); // toggle category $category.siblings( '.active' ).removeClass( 'active' ); // filter layouts $( '#alpus_layout_content' ).isotope( { filter: 'site' == category ? '.alpus-layout-item' : '[data-category="' + category + '"]' } ); }, /** * Event handler to reset condition. * * @since 1.0 */ // resetCondition: function (e) { // var $reset = $(e.currentTarget); // var $item = $reset.is('.alpus-condition-menu > a') ? // $reset.parent().data('item') : // $reset.closest('.alpus-layout-item'); // LayoutBuilderModel.resetCondition($item.data('category'), $item.data('condition-no')); // var $activePart = $item.find('.layout-part.active'); // $activePart.length && // this.editPart({ // currentTarget: $activePart.get(0) // }); // }, /** * Event handler to reset all conditions. * * @since 1.0 */ // resetAll: function () { // LayoutBuilderModel.resetCondition(); // $('.alpus-condition-cat-all').click(); // }, /** * Get add more item html. * @param {string} category */ getAddMoreUI: function ( category ) { return '' + control.description + '