/** * File: LeftAndMain.BatchActions.js */ (function($) { $.entwine('ss.tree', function($){ /** * Class: #Form_BatchActionsForm * * Batch actions which take a bunch of selected pages, * usually from the CMS tree implementation, and perform serverside * callbacks on the whole set. We make the tree selectable when the jQuery.UI tab * enclosing this form is opened. * * Events: * register - Called before an action is added. * unregister - Called before an action is removed. */ $('#Form_BatchActionsForm').entwine({ /** * Variable: Actions * (Array) Stores all actions that can be performed on the collected IDs as * function closures. This might trigger filtering of the selected IDs, * a confirmation message, etc. */ Actions: [], getTree: function() { return $('.cms-tree'); }, fromTree: { oncheck_node: function(e, data){ this.serializeFromTree(); }, onuncheck_node: function(e, data){ this.serializeFromTree(); } }, /** * @func registerDefault * @desc Register default bulk confirmation dialogs */ registerDefault: function() { // Publish selected pages action this.register('admin/pages/batchactions/publish', function(ids) { var confirmed = confirm( ss.i18n.inject( ss.i18n._t( "CMSMAIN.BATCH_PUBLISH_PROMPT", "You have {num} page(s) selected.\n\nDo you really want to publish?" ), {'num': ids.length} ) ); return (confirmed) ? ids : false; }); // Unpublish selected pages action this.register('admin/pages/batchactions/unpublish', function(ids) { var confirmed = confirm( ss.i18n.inject( ss.i18n._t( "CMSMAIN.BATCH_UNPUBLISH_PROMPT", "You have {num} page(s) selected.\n\nDo you really want to unpublish" ), {'num': ids.length} ) ); return (confirmed) ? ids : false; }); // Delete selected pages action // @deprecated since 4.0 Use archive instead this.register('admin/pages/batchactions/delete', function(ids) { var confirmed = confirm( ss.i18n.inject( ss.i18n._t( "CMSMAIN.BATCH_DELETE_PROMPT", "You have {num} page(s) selected.\n\nDo you really want to delete?" ), {'num': ids.length} ) ); return (confirmed) ? ids : false; }); // Delete selected pages action this.register('admin/pages/batchactions/archive', function(ids) { var confirmed = confirm( ss.i18n.inject( ss.i18n._t( "CMSMAIN.BATCH_ARCHIVE_PROMPT", "You have {num} page(s) selected.\n\nAre you sure you want to archive these pages?\n\nThese pages and all of their children pages will be unpublished and sent to the archive." ), {'num': ids.length} ) ); return (confirmed) ? ids : false; }); // Restore selected archived pages this.register('admin/pages/batchactions/restore', function(ids) { var confirmed = confirm( ss.i18n.inject( ss.i18n._t( "CMSMAIN.BATCH_RESTORE_PROMPT", "You have {num} page(s) selected.\n\nDo you really want to restore to stage?\n\nChildren of archived pages will be restored to the root level, unless those pages are also being restored." ), {'num': ids.length} ) ); return (confirmed) ? ids : false; }); // Delete selected pages from live action this.register('admin/pages/batchactions/deletefromlive', function(ids) { var confirmed = confirm( ss.i18n.inject( ss.i18n._t( "CMSMAIN.BATCH_DELETELIVE_PROMPT", "You have {num} page(s) selected.\n\nDo you really want to delete these pages from live?" ), {'num': ids.length} ) ); return (confirmed) ? ids : false; }); }, onadd: function() { this.registerDefault(); this._super(); }, /** * @func register * @param {string} type * @param {function} callback */ register: function(type, callback) { this.trigger('register', {type: type, callback: callback}); var actions = this.getActions(); actions[type] = callback; this.setActions(actions); }, /** * @func unregister * @param {string} type * @desc Remove an existing action. */ unregister: function(type) { this.trigger('unregister', {type: type}); var actions = this.getActions(); if(actions[type]) delete actions[type]; this.setActions(actions); }, /** * @func refreshSelected * @param {object} rootNode * @desc Ajax callbacks determine which pages is selectable in a certain batch action. */ refreshSelected : function(rootNode) { var self = this, st = this.getTree(), ids = this.getIDs(), allIds = [], viewMode = $('.cms-content-batchactions-button'), actionUrl = this.find(':input[name=Action]').val(); // Default to refreshing the entire tree if(rootNode == null) rootNode = st; for(var idx in ids) { $($(st).getNodeByID(idx)).addClass('selected').attr('selected', 'selected'); } // If no action is selected, enable all nodes if(!actionUrl || actionUrl == -1 || !viewMode.hasClass('active')) { $(rootNode).find('li').each(function() { $(this).setEnabled(true); }); return; } // Disable the nodes while the ajax request is being processed $(rootNode).find('li').each(function() { allIds.push($(this).data('id')); $(this).addClass('treeloading').setEnabled(false); }); // Post to the server to ask which pages can have this batch action applied // Retain existing query parameters in URL before appending path var actionUrlParts = $.path.parseUrl(actionUrl); var applicablePagesUrl = actionUrlParts.hrefNoSearch + '/applicablepages/'; applicablePagesUrl = $.path.addSearchParams(applicablePagesUrl, actionUrlParts.search); applicablePagesUrl = $.path.addSearchParams(applicablePagesUrl, {csvIDs: allIds.join(',')}); jQuery.getJSON(applicablePagesUrl, function(applicableIDs) { // Set a CSS class on each tree node indicating which can be batch-actioned and which can't jQuery(rootNode).find('li').each(function() { $(this).removeClass('treeloading'); var id = $(this).data('id'); if(id == 0 || $.inArray(id, applicableIDs) >= 0) { $(this).setEnabled(true); } else { // De-select the node if it's non-applicable $(this).removeClass('selected').setEnabled(false); $(this).prop('selected', false); } }); self.serializeFromTree(); }); }, /** * @func serializeFromTree * @return {boolean} */ serializeFromTree: function() { var tree = this.getTree(), ids = tree.getSelectedIDs(); // write IDs to the hidden field this.setIDs(ids); return true; }, /** * @func setIDS * @param {array} ids */ setIDs: function(ids) { this.find(':input[name=csvIDs]').val(ids ? ids.join(',') : null); }, /** * @func getIDS * @return {array} */ getIDs: function() { // Map empty value to empty array var value = this.find(':input[name=csvIDs]').val(); return value ? value.split(',') : []; }, onsubmit: function(e) { var self = this, ids = this.getIDs(), tree = this.getTree(), actions = this.getActions(); // if no nodes are selected, return with an error if(!ids || !ids.length) { alert(ss.i18n._t('CMSMAIN.SELECTONEPAGE', 'Please select at least one page')); e.preventDefault(); return false; } // apply callback, which might modify the IDs var type = this.find(':input[name=Action]').val(); if(actions[type]) { ids = this.getActions()[type].apply(this, [ids]); } // Discontinue processing if there are no further items if(!ids || !ids.length) { e.preventDefault(); return false; } // write (possibly modified) IDs back into to the hidden field this.setIDs(ids); // Reset failure states tree.find('li').removeClass('failed'); var button = this.find(':submit:first'); button.addClass('loading'); jQuery.ajax({ // don't use original form url url: type, type: 'POST', data: this.serializeArray(), complete: function(xmlhttp, status) { button.removeClass('loading'); // Refresh the tree. // Makes sure all nodes have the correct CSS classes applied. tree.jstree('refresh', -1); self.setIDs([]); // Reset action self.find(':input[name=Action]').val('').change(); // status message (decode into UTF-8, HTTP headers don't allow multibyte) var msg = xmlhttp.getResponseHeader('X-Status'); if(msg) statusMessage(decodeURIComponent(msg), (status == 'success') ? 'good' : 'bad'); }, success: function(data, status) { var id, node; if(data.modified) { var modifiedNodes = []; for(id in data.modified) { node = tree.getNodeByID(id); tree.jstree('set_text', node, data.modified[id]['TreeTitle']); modifiedNodes.push(node); } $(modifiedNodes).effect('highlight'); } if(data.deleted) { for(id in data.deleted) { node = tree.getNodeByID(id); if(node.length) tree.jstree('delete_node', node); } } if(data.error) { for(id in data.error) { node = tree.getNodeByID(id); $(node).addClass('failed'); } } }, dataType: 'json' }); // Never process this action; Only invoke via ajax e.preventDefault(); return false; } }); $('.cms-content-batchactions-button').entwine({ onmatch: function () { this._super(); this.updateTree(); }, onunmatch: function () { this._super(); }, onclick: function (e) { this.updateTree(); }, updateTree: function () { var tree = $('.cms-tree'), form = $('#Form_BatchActionsForm'); this._super(); if(this.data('active')) { tree.addClass('multiple'); tree.removeClass('draggable'); form.serializeFromTree(); } else { tree.removeClass('multiple'); tree.addClass('draggable'); } $('#Form_BatchActionsForm').refreshSelected(); } }); /** * Class: #Form_BatchActionsForm :select[name=Action] */ $('#Form_BatchActionsForm select[name=Action]').entwine({ onchange: function(e) { var form = $(e.target.form), btn = form.find(':submit'), selected = $(e.target).val(); if(!selected || selected == -1) { btn.attr('disabled', 'disabled').button('refresh'); } else { btn.removeAttr('disabled').button('refresh'); } // Refresh selected / enabled nodes $('#Form_BatchActionsForm').refreshSelected(); // TODO Should work by triggering change() along, but doesn't - entwine event bubbling? this.trigger("liszt:updated"); this._super(e); } }); }); })(jQuery);