/*
* Copyright 2015 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 tool displays the dashboard.
* Composed with a grid and a tabpanel.
* @private
*/
Ext.define('Ametys.plugins.dashboard.DashboardTool', {
extend: 'Ametys.tool.Tool',
/**
* @private
* @property {Object} _grids All the grids of this tool indexed by their id.
*/
/**
* @private
* @property {Object} _tasklabels All the labels of the grids of this tool indexed by their id.
*/
/**
* @private
* @property {Object} _taskLengths All the max lengths of the grids of this tool indexed by their id.
*/
/**
* @private
* @property {Ext.panel.Panel} _mainPanel The main panel of the tool.
*/
/**
* @private
* @property {Ext.data.Model[]} _selection The current selection.
*/
constructor: function(config)
{
this.callParent(arguments);
Ametys.message.MessageBus.on(Ametys.message.Message.WORKFLOW_CHANGED, this._onMessageModified, this);
Ametys.message.MessageBus.on(Ametys.message.Message.MODIFIED, this._onMessageModified, this);
Ametys.message.MessageBus.on(Ametys.message.Message.DELETED, this._onMessageDeleted, this);
},
createPanel: function()
{
this._mainPanel = Ext.create('Ext.panel.Panel', {
layout: {
type: 'vbox',
align: 'stretch'
},
margin: '5 10 5 10',
border: false,
items: [
{
xtype: 'panel',
id: 'recent-drafts-panel',
border: false,
ui: 'light',
title: '',
minHeight: 180,
maxHeight: 250,
layout: 'fit',
items: [{
xtype: 'grid',
id: 'recent-drafts-grid',
scrollable: true,
deferRowRender: false,
selModel: {
mode: 'MULTI'
}
}]
},
{
xtype: 'component',
html: '',
height: 30
},
{
flex: 1,
cls: 'ametys-form-tab-item',
xtype: 'tabpanel',
id: 'tabs-panel',
activeTab: 0,
deferredRender: false,
border: false,
items: [] // will be set on refresh
}
]
});
return this._mainPanel;
},
getMBSelectionInteraction: function()
{
return Ametys.tool.Tool.MB_TYPE_ACTIVE;
},
sendCurrentSelection: function()
{
var records = this._selection;
if (records && records.length > 0)
{
var contentIdsByPageId = {};
var contentIds = [];
// Categorize the selected contents
for (var i = 0; i < records.length; i++)
{
var id = records[i].get('id');
var pageId = records[i].get('page-id');
if (pageId != null && pageId != '')
{
if (contentIdsByPageId[pageId] == null)
{
contentIdsByPageId[pageId] = [];
}
contentIdsByPageId[pageId].push(id);
}
else
{
contentIds.push(id);
}
}
var targets = [];
// Message targets for contents belonging to a page
Ext.Object.each(contentIdsByPageId, function(pageId){
Ext.Array.each(contentIdsByPageId[pageId], function(contentId){
targets.push({
id: Ametys.message.MessageTarget.PAGE,
parameters: {
ids: [pageId],
'content-id': contentId
}
});
})
});
// Message targets for orphan contents
targets.push({
id: Ametys.message.MessageTarget.CONTENT,
parameters: {
ids: contentIds
}
});
// Send the message on the bus
Ext.create('Ametys.message.Message', {
type: Ametys.message.Message.SELECTION_CHANGED,
targets: targets
});
}
else
{
Ext.create('Ametys.message.Message', {
type: Ametys.message.Message.SELECTION_CHANGED,
targets: []
});
}
},
/**
* Listener for change of selection in any grid.
* @param {String} selectedTaskId The id of the grid where the selection change happened.
* @param {Ext.data.Model} model The model.
* @param {Ext.data.Model[]} selected The selected records.
* @private
*/
_onSelectionChange: function(selectedTaskId, model, selected)
{
for (var taskId in this._grids)
{
if (taskId != selectedTaskId)
{
this._grids[taskId].getSelectionModel().deselectAll(true); // deselect all the other grids
}
}
this._selection = Ext.isArray(selected) ? selected : [selected];
this.sendCurrentSelection();
},
setParams: function(params)
{
this.callParent(arguments);
this.refresh();
},
refresh: function()
{
this.showRefreshing();
Ametys.cms.dashboard.DashboardDAO.getTasks(Ext.bind(this._refreshCb, this));
},
/**
* Does the real refresh after retrieving the tasks from the server.
* Draws and loads the grids.
* @param {HTMLElement} tasks The tasks sent by the server.
* @private
*/
_refreshCb: function(tasks)
{
this._taskLabels = {};
this._taskLengths = {};
this._grids = {};
// Retrieve the tasks
var taskNodes = Ext.dom.Query.select("task", tasks);
for (var i = 0; i < taskNodes.length; i++)
{
var taskId = Ext.dom.Query.selectValue('@id', taskNodes[i], '');
this._taskLabels[taskId] = Ext.dom.Query.getNodeValue(taskNodes[i]);
this._taskLengths[taskId] = Ext.dom.Query.selectValue('@length', taskNodes[i]);
}
// Recent drafts panel
var recentDraftPanel = this._mainPanel.items.get('recent-drafts-panel');
var recentDraftsGrid = recentDraftPanel.items.get('recent-drafts-grid');
this._reconfigureRecentDraftsGrid(recentDraftsGrid, 'recent-drafts', this._taskLabels['recent-drafts'], this._taskLengths['recent-drafts']);
recentDraftPanel.items.add(recentDraftsGrid);
// Tabs panel
var taskPanels = [];
for (var taskId in this._taskLabels)
{
if (taskId != 'recent-drafts')
{
var taskLabel = this._taskLabels[taskId];
var max = this._taskLengths['taskId'];
taskPanels.push({
xtype: 'panel',
itemId: taskId + '-panel',
title: taskLabel,
border: false,
layout: 'fit',
items: [this._drawGridPanel(taskId, taskLabel, max)]
});
}
}
this._mainPanel.items.get('tabs-panel').removeAll();
for (var i = 0; i < taskPanels.length; i++)
{
this._mainPanel.items.get('tabs-panel').add(taskPanels[i]).show();
}
// Load the grids
for (var taskId in this._grids)
{
this._grids[taskId].getStore().load({
params: {
taskId: taskId,
login: Ametys.getAppParameter('user').login,
siteName: Ametys.getAppParameter('siteName'),
lang: Ametys.getAppParameter('user').locale
}
});
}
this.showRefreshed();
},
/**
* Changes the store and the columns of the recent drafts grid.
* @param {String} grid The grid to reconfigure.
* @param {String} taskId The id of the task.
* @param {String} taskLabel The label of the task.
* @param {Number} max The max items to display.
* @private
*/
_reconfigureRecentDraftsGrid: function(grid, taskId, taskLabel, max)
{
var store = Ext.create('Ext.data.Store', {
model: 'Ametys.plugins.dashboard.DashboardTool.ContentEntry',
proxy: {
type: 'ametys',
plugin: 'web',
url: 'contents/todo-list.xml',
reader: {
type: 'xml',
record: 'content'
}
},
sorters: [{property: 'lastModified', direction: 'DESC'}],
taskId: taskId,
taskLabel: taskLabel
});
var columns = [
{stateId: 'grid-column-title', header: "{{i18n PLUGINS_DASHBOARD_COLUMN_CONTENT_TITLE}}", flex: 1, sortable: true, dataIndex: 'title', renderer: Ametys.grid.GridColumnHelper.renderTextWithIcon},
{stateId: 'grid-column-lastModified', header: "{{i18n PLUGINS_DASHBOARD_COLUMN_LASTMODIFIED}}", width: 180, sortable: true, dataIndex: 'lastModified', renderer: Ext.util.Format.dateRenderer(Ext.Date.patterns.FriendlyDateTime)},
{stateId: 'grid-column-workflow-step', header: "{{i18n PLUGINS_DASHBOARD_COLUMN_WORKFLOW_STEP}}", width: 60, sortable: true, dataIndex: 'workflow-step', renderer: this._renderWorkflowStep}
];
grid.reconfigure(store, columns);
// Events
grid.on('selectionchange', this._onSelectionChange, this, {args: [taskId]});
grid.on('celldblclick', this._openContent, this);
grid.getStore().on('load', this._onLoad, this, [taskId, taskLabel]);
this._grids[taskId] = grid;
},
/**
* Draws a grid for a tab.
* @param {String} taskId The id of the task.
* @param {String} taskLabel The label of the task.
* @param {Number} max The max items to display.
* @return {Ext.grid.Panel} The grid.
* @private
*/
_drawGridPanel: function(taskId, taskLabel, max)
{
var store = Ext.create('Ext.data.Store', {
model: 'Ametys.plugins.dashboard.DashboardTool.ContentEntry',
proxy: {
type: 'ametys',
plugin: 'web',
url: 'contents/todo-list.xml',
reader: {
type: 'xml',
record: 'content'
}
},
sorters: [{property: 'lastModified', direction: 'DESC'}],
taskId: taskId,
taskLabel: taskLabel
});
var itemId = taskId + '-grid';
var grid = Ext.create('Ext.grid.Panel', {
itemId: itemId,
store: store,
scrollable: true,
deferRowRender: true,
selModel: {
mode: 'MULTI'
},
stateful: true,
stateId: this.self.getName() + "$" + itemId,
columns: [
{stateId: 'grid-column-title', header: "{{i18n PLUGINS_DASHBOARD_COLUMN_CONTENT_TITLE}}", flex: 1, sortable: true, dataIndex: 'title', renderer: Ametys.grid.GridColumnHelper.renderTextWithIcon},
{stateId: 'grid-column-contributor', header: "{{i18n PLUGINS_DASHBOARD_COLUMN_CONTRIBUTOR}}", width: 270, sortable: true, dataIndex: 'contributor'},
{stateId: 'grid-column-lastModified', header: "{{i18n PLUGINS_DASHBOARD_COLUMN_LASTMODIFIED}}", width: 180, sortable: true, dataIndex: 'lastModified', renderer: Ext.util.Format.dateRenderer(Ext.Date.patterns.FriendlyDateTime)}
]
});
// Events
grid.on('selectionchange', this._onSelectionChange, this, {args: [taskId]});
grid.on('celldblclick', this._openContent, this);
grid.getStore().on('load', this._onLoad, this, [taskId, taskLabel]);
this._grids[taskId] = grid;
return grid;
},
/**
* Renders a workflow step.
* @param {Object} value The data value for the current cell.
* @param {Object} metaData A collection of metadata about the current cell.
* @param {Ext.data.Model} record The record for the current row.
* @return {String} The HTML for rendering the workflow step.
* @private
*/
_renderWorkflowStep: function(value, metaData, record)
{
var step = record.get('workflow-step');
var iconSmall = record.get('workflow-icon-small');
if (iconSmall)
{
return '<img src="' + Ametys.CONTEXT_PATH + iconSmall + '" alt="' + step + '" title="' + step + '" class="a-grid-icon a-grid-icon-workflow"/>';
}
else
{
return step;
}
},
/**
* Opens the selected content.
* @param {Ext.view.Table} table The table.
* @param {HTMLElement} td The TD element for the cell.
* @param {Number} cellIndex The cell index.
* @param {Ext.data.Model} record The record to open.
* @private
*/
_openContent: function(table, td, cellIndex, record)
{
var pageId = record.get('page-id');
var id = record.get('id');
if (pageId == null || pageId == '')
{
Ametys.tool.ToolsManager.openTool('uitool-content', {'id': id});
}
else
{
Ametys.tool.ToolsManager.openTool('uitool-page', {'id': pageId});
}
},
/**
* Listener when a store reads data.
* @param {Ext.data.Store} store The store.
* @param {Ext.data.Model[]} records An array of records.
* @private
*/
_onLoad: function(store, records)
{
this._modifyPanelTitle(store.taskId, store.taskLabel, records.length);
},
/**
* Updates the title of a panel.
* @param {String} taskId The id of the panel to update.
* @param {String} taskLabel The new title.
* @param {String} count The number to set in brackets at the end of the new title.
* @private
*/
_modifyPanelTitle: function(taskId, taskLabel, count)
{
if (taskId && taskLabel)
{
var panel = this._mainPanel.getComponent(taskId + '-panel') || this._mainPanel.getComponent('tabs-panel').getComponent(taskId + '-panel');
if (panel != null)
{
panel.setTitle(taskLabel + ' (' + count + ')');
}
}
},
/**
* Listener on edition message.
* @param {Ametys.message.Message} message The edition message.
* @private
*/
_onMessageModified: function(message)
{
var targets = message.getTargets("content");
if (targets)
{
var contentIds = this._convertToId(targets);
if (this._areInStore(contentIds))
{
// We are concerned by this message
this.showOutOfDate();
}
}
},
/**
* Listener on deletion message.
* @param {Ametys.message.Message} message The deletion message.
* @private
*/
_onMessageDeleted: function(message)
{
var targets = message.getTargets("content");
if (targets)
{
var contentIds = this._convertToId(targets);
if (this._areInStore(contentIds))
{
// We are concerned by this message
this.showOutOfDate();
}
}
},
/**
* Gives the ids of the given targets.
* @param {Object[]} targets The targets.
* @return {String[]} The ids.
* @private
*/
_convertToId: function(targets)
{
var targetsIds = [];
for (var i = 0; i < targets.length; i++)
{
targetsIds.push(targets[i].getParameters()['id']);
}
return targetsIds;
},
/**
* Tells if one of the contentIds are currently somewhere in the tool's grids.
* @param {String[]} contentIds The ids to test.
* @return {Boolean} True if at least one id is in one of the stores.
* @private
*/
_areInStore: function(contentIds)
{
for (var i = 0; i < contentIds.length; i++)
{
for (var j in this._grids)
{
var record = this._grids[j].getStore().getById(contentIds[i]);
if (record != null)
{
return true;
}
}
}
return false;
}
});