/*
* Copyright 2014 Anyware Services
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* This is a grid that can only have contents and that allow to edit it (with the correct columns).
* It does handle messages for the bus, and request to save the content for you.
* Consider the provided events to set your tool as dirty, or lock your tool while modifications are running.
*
* Ext.create("Ametys.cms.content.EditContentsGrid", {
* store: this.store,
*
* columns: [
* {header: "Title", width: 250, sortable: true, dataIndex: 'title', renderer: Ametys.plugins.cms.search.SearchGridHelper.renderTitle, hideable: false, editor: { allowBlank: false}},
* {header: "Author", width: 200, sortable: true, dataIndex: 'contributor', editor: { allowBlank: false}},
* {header: "Last modified", width: 200, sortable: true, renderer: Ext.util.Format.dateRenderer(Ext.Date.patterns.ShortDateTime), dataIndex: 'lastModified'},
* {header: "Type", width: 150, sortable: true, dataIndex: 'content-type'},
* {header: "Workflow", width: 50, sortable: true, renderer: Ametys.plugins.cms.search.SearchGridHelper.renderWorkflowStep, dataIndex: 'worflow-step'}
* ],
*
* selModel : {
* mode: 'MULTI'
* },
*
* listeners: {
* 'outofdate': Ext.bind(this.showOutOfDate, this, [false], false),
* 'dirtychange': Ext.bind(this.onDirtyChange, this)
* }
* });
*
* onDirtyChange: function(dirty)
* {
* this.setDirty(dirty);
*
* // Hide the search form
* var topDockItems = this.grid.getDockedItems("*[form]")[0].setVisible(!dirty);
* },
*
* refresh: function()
* {
* if (this.grid.isModeEdition())
* {
* var me = this;
* this.grid.discardChanges(true, function() {
* me.refresh();
* });
* }
* else
* {
* ...
* }
* }
*
*
* The only restriction is that record ids has to be content ids.
*
* Consider Ametys.cms.content.EditContentsGrid#openContent and Ametys.cms.content.EditContentsGrid#openContent for the action on the double click on a cell.
*/
Ext.define('Ametys.cms.content.EditContentsGrid', {
extend: "Ext.grid.Panel",
mixins: {
editContentsView: "Ametys.cms.content.EditContentsView"
},
statics: {
/**
* Function to open a content.
* To use like this to open a content on a double click on it.
* Will use the default 'uitool-content'
*
* listeners: {
* 'itemdblclick': function (view, record, item, index, e) { Ametys.cms.content.EditContentsGrid.openContent (record); }
* }
*
* or do this to choose another Tool
*
* listeners: {
* 'itemdblclick': function (view, record, item, index, e) { Ametys.cms.content.EditContentsGrid.openContent (record, {role: 'myfactory'});
* }
* @param {Ext.data.Model} record The record
* @param {Object} [options] The options
* @param {String} [options.role='uitool-content'] The role of the factory to use, when opening the tool
* @param {String} [options.toolParams] The parameters to passe to the opened tool
*/
openContent: function (record, options)
{
var opt = options || {};
var role = opt.role || 'uitool-content';
var toolParams = opt.toolParams || {};
Ametys.tool.ToolsManager.openTool(role || 'uitool-content', Ext.apply({'id' : record.get('id')}, toolParams));
},
/**
* Opens the double-clicked content in edition. To use like this to open a content on a double click on it.
* Will use the default 'uitool-content'
*
* listeners: {
* 'itemdblclick': Ametys.cms.content.EditContentsGrid.editContent
* }
*
* or do this to choose another Tool
*
* listeners: {
* 'itemdblclick': { fn: Ametys.cms.content.EditContentsGrid.editContent, options: {role: 'myfactory'}}
* }
*
* @param {Ext.grid.View} view The view of the grid
* @param {Ext.data.Model} record The record
* @param {HTMLElement} item The html element that was clicked
* @param {Number} index The items index
* @param {Ext.event.Event} e The event object
* @param {Object} [eOpts] The request parameters passed to the listener
* @param {Object} [eOpts.options] The options for the listeners
* @param {String} [eOpts.options.role='uitool-content'] The role of the factory to use, when opening the tool on a double-click
*/
editContent: function (view, record, item, index, e, eOpts)
{
var role = eOpts.options ? eOpts.options.role : null;
Ametys.tool.ToolsManager.openTool(role || 'uitool-content', {'id': record.get('id'), 'mode': 'edit', 'edit-workflow-action': this.workflowEditActionId, 'view-name': this.viewName });// FIXME this.workflowEditActionId will always be empty since it is static
}
},
/**
* @private
* @cfg {String} cls
* @inheritdoc
*/
cls: ['edit-contents-grid', 'a-visible-focus'],
selModel : {
mode: 'MULTI'
},
/**
* @cfg {String} [messageTarget=Ametys.message.MessageTarget#CONTENT] The message target factory role to use for the event of the bus thrown and listened by the grid
*/
/**
* @cfg {Boolean} [enablePagination=true] Enable the pagination for the grid.
*/
/**
* @property {Boolean} paginationEnabled Is the pagination enabled for the grid?
* @readonly
*/
/**
* @cfg {Boolean} [sendSelectionOnChange=true] When selecting a line, the selection message is sent to the bus automatically
*/
constructor: function(config)
{
config.id = config.id || Ext.id();
config.messageTarget = config.messageTarget || Ametys.message.MessageTarget.CONTENT;
config.viewConfig = config.viewConfig || {};
config.viewConfig.plugins = [{
ptype: 'ametysgridviewdragdrop',
dragTextField: 'title',
setAmetysDragInfos: Ext.bind(this.getDragInfo, this),
setAmetysDropZoneInfos: Ext.bind(this.getDropInfo, this)
}];
config.dockedItems = config.dockedItems || [];
this.paginationEnabled = config.enablePagination !== false; // default to true
if (this.paginationEnabled)
{
config.dockedItems.push(this._createPageBar(config));
}
this.callParent(arguments);
this.mixins.editContentsView.constructor.call(this, config);
// Protect the method before registering the listener to protect them
this._protectMethods();
this.store.on('remove', this._updatePagingToolbarInfo, this);
if (config.sendSelectionOnChange !== false)
{
this.on('selectionchange', this.sendCurrentSelection, this);
}
Ametys.message.MessageBus.on(Ametys.message.Message.CREATED, this._onContentCreated, this);
Ametys.message.MessageBus.on(Ametys.message.Message.MODIFIED, this._onContentEdited, this);
Ametys.message.MessageBus.on(Ametys.message.Message.WORKFLOW_CHANGED, this._onContentEdited, this);
Ametys.message.MessageBus.on(Ametys.message.Message.DELETED, this._onContentDeleted, this);
Ametys.message.MessageBus.on(Ametys.message.Message.ARCHIVED, this._onContentDeleted, this);
Ametys.message.MessageBus.on(Ametys.message.Message.UNARCHIVED, this._onContentCreated, this);
},
/**
* This method will protect many tools methods from beeing called at bad time
* Do not call "sendCurrentSelection" if tool has not the focus, do not call "onFocus" if the tool is destroyed...
* @private
*/
_protectMethods: function()
{
this.sendCurrentSelection = Ext.Function.createInterceptor(this.sendCurrentSelection, (_) => !this.destroyed , this);
this._updatePagingToolbarInfo = Ext.Function.createInterceptor(this._updatePagingToolbarInfo, (_) => !this.destroyed , this);
this._onContentDeleted = Ext.Function.createInterceptor(this._onContentDeleted, (_) => !this.destroyed , this);
this._onContentEdited = Ext.Function.createInterceptor(this._onContentEdited, (_) => !this.destroyed , this);
this._onContentCreated = Ext.Function.createInterceptor(this._onContentCreated, (_) => !this.destroyed , this);
},
/**
* @event outofdate
* Fires when the grid is now out of date and the store should be reloaded (you should take care of #isModeEdition before reloading).
*/
/**
* @event dirtychange
* Fires when the dirty state of the grid has changed.
* @param {Boolean} dirty True if the grid is now dirty. False otherwise.
*/
/**
* Fires a event of selection on message bus, from the selected contents in the grid.
* @protected
*/
sendCurrentSelection: function ()
{
Ext.create("Ametys.message.Message", {
type: Ametys.message.Message.SELECTION_CHANGED,
targets: this.getCurrentSelectionTargets()
});
},
/**
* Get the current selection targets.
* @return {Object} A target configuration object, usable in Message's {@link Ametys.message.Message#cfg-targets targets} or subtargets configuration properties.
*/
getCurrentSelectionTargets: function()
{
var contentIds = [];
var selection = this.getSelectionModel().getSelection();
for (var i=0; i < selection.length; i++)
{
contentIds.push(selection[i].get('id'));
}
return {
id: this.messageTarget,
parameters: { ids: contentIds }
};
},
/**
* @private
* This event is thrown by the getDragData to add the 'source' of the drag.
* @param {Object} item The default drag data that will be transmitted. You have to add a 'source' item in it:
* @param {Object} item.source The source (in the relation way) of the drag operation. A Ametys.relation.RelationPoint config.
*/
getDragInfo: function(item)
{
var contentIds = [];
for (var i = 0; i < item.records.length; i++)
{
contentIds.push(item.records[i].get('id'));
}
if (contentIds.length > 0)
{
item.source = {
relationTypes: [Ametys.relation.Relation.COPY, Ametys.relation.Relation.REFERENCE],
targets: {
id: this.messageTarget,
parameters: { ids: contentIds }
}
};
}
},
/**
* @private
* This event is thrown before the beforeDrop event and create the target of the drop operation relation.
* @param {Ext.data.Model[]} targetRecords The target records of the drop operation.
* @param {Object} item The default drag data that will be transmitted. You have to add a 'target' item in it:
* @param {Object} item.target The target (in the relation way) of the drop operation. A Ametys.relation.RelationPoint config.
*/
getDropInfo: function(targetRecords, item)
{
var contentIds = [];
for (var i = 0; i < targetRecords.length; i++)
{
contentIds.push(targetRecords[i].get('id'));
}
if (contentIds.length > 0)
{
item.target = {
relationTypes: [Ametys.relation.Relation.MOVE, Ametys.relation.Relation.REFERENCE, Ametys.relation.Relation.COPY],
targets: {
id: this.messageTarget,
parameters: { ids: contentIds }
}
};
}
},
onDestroy: function()
{
Ametys.message.MessageBus.unAll(this);
},
getState: function()
{
var state = this.callParent(arguments),
column;
if (this.headerCt && this.headerCt.items)
{
Ext.Array.each(state.columns || [], function(columnState) {
column = this.headerCt.items.findBy(function(item) {
return item.stateId == columnState.id;
});
if (column)
{
Ext.applyIf(columnState, Ametys.plugins.cms.search.SearchGridHelper.getStateColumnPropertiesToForce(column));
}
}, this);
}
return state;
},
/**
* @private
* Create the paging bar
* @param {Object} config The constructor configuration
*/
_createPageBar: function(config)
{
return {
xtype: 'pagingtoolbar',
id: config.id + "-pagebar",
store: config.store,
dock: 'bottom',
displayInfo: true,
displayMsg: "{{i18n plugin.cms:UITOOL_CONTENTEDITIONGRID_RESULT_1}}{0}{{i18n plugin.cms:UITOOL_CONTENTEDITIONGRID_RESULT_2}}{1}{{i18n plugin.cms:UITOOL_CONTENTEDITIONGRID_RESULT_3}}{2}",
emptyMsg: "{{i18n plugin.cms:UITOOL_CONTENTEDITIONGRID_NO_RESULT}}"
};
},
/**
* Get the paging bar
* @return {Ext.toolbar.Paging} The paging toolbar
*/
getPageBar: function()
{
return this.paginationEnabled ? Ext.getCmp(this.getId() + "-pagebar") : null;
},
/**
* Update the paging toolbar information when store count has changed
* @private
*/
_updatePagingToolbarInfo: function ()
{
var pagingtoolbar = this.getPageBar();
if (pagingtoolbar)
{
var displayItem = pagingtoolbar.child('#displayItem');
if (displayItem)
{
var count = this.store.getCount();
var pageData = pagingtoolbar.getPageData();
var msg = count == 0 ?
pagingtoolbar.emptyMsg :
Ext.String.format(
pagingtoolbar.displayMsg,
pageData.fromRecord, pageData.fromRecord + count - 1, count
);
displayItem.setText(msg);
}
}
},
/**
* Listener when contents have been edited
* Will set the tool in "out of date" mode
* @param {Ametys.message.Message} message The edition message.
* @protected
*/
_onContentEdited: function (message)
{
var me = this;
var targets = message.getTargets(
function (target)
{
return new RegExp("^" + me.messageTarget + "$").test(target.getId());
}
);
if (targets.length > 0)
{
// Determine the content fields
var contentFields = [];
Ext.Array.each(this.store.getModel().getFields(), function(field) {
if (field.ftype == "content")
{
contentFields.push(field);
}
});
for (var i=0; i < targets.length; i++)
{
if (this.store.getById(targets[i].getParameters().id) != null)
{
this.fireEvent("outofdate");
return;
}
// If the modified content is referenced, update the title
for (var j=0; j < contentFields.length; j++)
{
var contentField = contentFields[j];
this.store.getData().each(function(record) {
var value = Ext.Array.from(record.get(contentField.getName()));
if (Ext.Array.contains(value, targets[i].getParameters().id))
{
me.fireEvent("outofdate");
return;
}
});
}
}
}
},
/**
* Listener when contents have been created
* Will set the tool in "out of date" mode
* @param {Ametys.message.Message} message The creation message.
* @protected
*/
_onContentCreated: function (message)
{
var me = this;
var targets = message.getTargets(
function (target)
{
return new RegExp("^" + me.messageTarget + "$").test(target.getId());
}
);
if (targets.length > 0)
{
this.fireEvent("outofdate");
}
},
/**
* Listener when contents have been deleted
* Will remove the records if present
* @param {Ametys.message.Message} message The deletion message.
* @protected
*/
_onContentDeleted: function (message)
{
var me = this;
var targets = message.getTargets(
function (target)
{
return new RegExp("^" + me.messageTarget + "$").test(target.getId());
}
);
if (targets.length > 0)
{
var toRemove = [];
for (var i=0; i < targets.length; i++)
{
var record = this.store.getById(targets[i].getParameters().id);
if (record != null)
{
toRemove.push(record);
}
}
// CMS-12057: Wait for removed of all elements before sending a selection change event
this.suspendEvent('selectionchange');
this.store.remove(toRemove);
this.resumeEvent('selectionchange');
this.fireEvent('selectionchange', this.getSelectionModel(), this.getSelectionModel().getSelection());
this._showHideSaveBar();
}
}
});