/*
* Copyright 2021 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 all forms in READ access for current user
* @private
*/
Ext.define('Ametys.plugins.forms.tool.FormTool', {
extend: "Ametys.tool.Tool",
constructor: function(config)
{
this.callParent(arguments);
Ametys.message.MessageBus.on(Ametys.message.Message.DELETED, this._onDeletionMessage, this);
Ametys.message.MessageBus.on(Ametys.plugins.forms.tool.FormsPreviewTool.SELECTION_PREVIEW_CHANGED, this._onSelectionPreviewChanged, this);
Ametys.message.MessageBus.on(Ametys.message.Message.MOVED, this._onMoved, this);
Ametys.message.MessageBus.on(Ametys.message.Message.SELECTION_CHANGED, this._onMessageSelectionChanged, this);
},
setParams: function (params)
{
this.callParent(arguments);
this._formId = this.getParams().id;
this.refresh();
},
refresh: function()
{
this.showRefreshing();
this._tree.initRootNodeParameter(Ext.bind(this._loadTree, this), this._formId);
},
/**
* @private
* Load the tree
*/
_loadTree: function()
{
this._tree.getStore().load({
scope: this,
callback: Ext.bind(this._refreshCb, this)
});
},
/**
* @private
* Callback function after (re)loading the tree
*/
_refreshCb: function ()
{
this.showRefreshed();
// Select first page
this._tree.getSelectionModel().select(this._tree.getRootNode().firstChild);
},
getMBSelectionInteraction: function()
{
return Ametys.tool.Tool.MB_TYPE_ACTIVE;
},
onFocus: function()
{
this._previousSelection = Ametys.message.MessageBus.getCurrentSelectionMessage().getTargets();
Ametys.plugins.forms.tool.FormTool.superclass.onFocus.call(this);
},
sendCurrentSelection: function()
{
var selection = this._tree.getSelectionModel().getSelection();
if(selection != null && selection.length > 0){
var currentRecord = selection[0]; //multiselection is not supported
var target = this._getMessageTargetConfiguration(currentRecord) || [],
realTarget;
if(currentRecord.isRoot())
{
realTarget = target;
}
else if(currentRecord.getData().type == "page")
{
var formNode = this._tree.getStore().getNodeById(currentRecord.get('parentId'));
realTarget = this._getMessageTargetConfiguration(formNode);
realTarget.subtargets = target;
}
else if(currentRecord.getData().type == "question")
{
var pageNode = this._tree.getStore().getNodeById(currentRecord.get('parentId'));
var formNode = this._tree.getStore().getNodeById(pageNode.get('parentId'));
realTarget = this._getMessageTargetConfiguration(formNode);
var subtarget = this._getMessageTargetConfiguration(pageNode);
subtarget.subtargets = target;
realTarget.subtargets = subtarget;
}
}
Ext.create("Ametys.message.Message", {
type: Ametys.message.Message.SELECTION_CHANGED,
targets: realTarget,
parameters: {}
});
},
createPanel: function()
{
this._tree = Ext.create("Ametys.plugins.forms.tree.FormTree", {
rootVisible: true,
stateful: true,
stateId: this.self.getName() + "$tree",
scrollable: true,
indicators: this._getIndicators(),
cls: 'uitool-form',
listeners: {
beforerender: this._onBeforeRender,
selectionchange: this.sendCurrentSelection,
itemdblclick: function(view, record){
this._openPreviewTool(view, record);
if (record.get("type") == 'question' && record.data.canWrite)
{
this._openQuestionDialog(record.data);
}
},
scope: this
},
viewConfig: {
plugins: {
ptype: 'ametystreeviewdragdrop',
containerScroll: true,
appendOnly: false,
sortOnDrop: false,
expandDelay: 500,
allowContainerDrops: false,
setAmetysDragInfos: Ext.bind(this._getDragInfo, this),
setAmetysDropZoneInfos: Ext.bind(this._getDropInfo, this)
}
}
});
return this._tree;
},
/**
* @private
* Listener before render to add edtior on grid
* @return {Object} grid the grid
*/
_onBeforeRender: function(grid)
{
grid.getColumns()[0].editor = {
xtype: 'textfield',
allowBlank: false,
selectOnFocus: true
}
},
/**
* @protected
* Get for the available indicators
* @return {Object[]} the available indicators
*/
_getIndicators: function()
{
return [
// configuration status
{
id: 'config-status',
description: "{{i18n PLUGINS_FORMS_TOOL_INDICATOR_WARNING_MSG}}",
label: "{{i18n PLUGINS_FORMS_TOOL_INDICATOR_WARNING}}",
iconGlyph: 'ametysicon-sign-caution',
matchFn: this._needConfiguration,
applyFn: this._showConfigurationStatus,
defaultValue: true
},
{
id: 'rules-status',
description: "{{i18n PLUGINS_FORMS_TOOL_INDICATOR_RULE_MSG}}",
label: "{{i18n PLUGINS_FORMS_TOOL_INDICATOR_RULE}}",
iconGlyph: 'ametysicon-arrow-up-right-curve',
matchFn: this._hasRules,
applyFn: this._showRulesIcon,
defaultValue: true
},
{
id: 'read-status',
description: "{{i18n PLUGINS_FORMS_TOOL_INDICATOR_READ_RESTRICTION_MSG}}",
label: "{{i18n PLUGINS_FORMS_TOOL_INDICATOR_READ_RESTRICTION}}",
iconGlyph: 'ametysicon-body-part-eye-no',
matchFn: this._isReadRestricted,
applyFn: this._showReadRestrictionIcon,
defaultValue: true
},
{
id: 'modifiable-status',
description: "{{i18n PLUGINS_FORMS_TOOL_INDICATOR_MODIFIABLE_RESTRICTION_MSG}}",
label: "{{i18n PLUGINS_FORMS_TOOL_INDICATOR_MODIFIABLE_RESTRICTION}}",
iconGlyph: 'ametysicon-edit45',
matchFn: this._isModifiable,
applyFn: this._showModifiableRestrictionIcon,
defaultValue: true
}
]
},
/**
* @private
* Return true if node needs further configuration
*/
_needConfiguration: function(node)
{
return !node.getData().root && !node.getData().isConfigured;
},
/**
* @private
* Return content for rendering tree when node needs further configuration
*/
_showConfigurationStatus: function(node)
{
switch (node.getData().type)
{
case 'root':
return '<span title="{{i18n PLUGINS_FORMS_TREE_INDICATORS_CONFIG_STATUS_FORM}}" class="orange-color form-indicator ametysicon-sign-caution"></span>';
case 'page':
return '<span title="{{i18n PLUGINS_FORMS_TREE_INDICATORS_CONFIG_STATUS_PAGE}}" class="orange-color form-indicator ametysicon-sign-caution"></span>';
case 'question':
return '<span title="{{i18n PLUGINS_FORMS_TREE_INDICATORS_CONFIG_STATUS_QUESTION}}" class="orange-color form-indicator ametysicon-sign-caution"></span>';
default:
return '';
}
},
/**
* @private
* Return true if node needs has any rule
*/
_hasRules: function(node)
{
return node.getData().hasRule;
},
/**
* @private
* Return true if node is read restricted
*/
_isReadRestricted: function(node)
{
return node.getData().isReadRestricted;
},
/**
* @private
* Return true if node needs is modifiable
*/
_isModifiable: function(node)
{
return node.getData().isModifiable;
},
/**
* @private
* Return content for rendering tree when node needs further configuration
*/
_showRulesIcon: function(node)
{
var data = node.getData();
switch (data.type)
{
case 'question':
var title = "";
if (data.hasTerminalRule)
{
title += "{{i18n PLUGINS_FORMS_TREE_INDICATORS_RULES_STATUS_TERMINAL}}";
}
var pageTitles = data.pageTitlesWithRule;
if (pageTitles.length > 0)
{
if (title != "") { title += "\n";}
title += "{{i18n PLUGINS_FORMS_TREE_INDICATORS_RULES_STATUS_PAGE}}\n- " + pageTitles.join("\n- ");
}
var questionTitles = data.questionTitlesWithRule;
if (questionTitles.length > 0)
{
if (title != "") { title += "\n";}
title += "{{i18n PLUGINS_FORMS_TREE_INDICATORS_RULES_STATUS_QUESTION}}\n- " + questionTitles.join("\n- ");
}
return '<span title="' + title + '" class="form-indicator ametysicon-arrow-up-right-curve"></span>';
default:
return '';
}
},
/**
* @private
* Return content for rendering tree when node is modifiable
*/
_showModifiableRestrictionIcon: function(node)
{
var data = node.getData();
switch (data.type)
{
case 'question':
var title = "{{i18n PLUGINS_FORMS_TREE_INDICATORS_MODIFIABLE_RESTRICTION_QUESTION}}";
return '<span title="' + title + '" class="form-indicator ametysicon-edit45"></span>';
default:
return '';
}
},
/**
* @private
* Return content for rendering tree when node has any read restriction
*/
_showReadRestrictionIcon: function(node)
{
var data = node.getData();
switch (data.type)
{
case 'question':
var title = "{{i18n PLUGINS_FORMS_TREE_INDICATORS_READ_RESTRICTION_QUESTION}}";
return '<span title="' + title + '" class="form-indicator ametysicon-body-part-eye-no"></span>';
default:
return '';
}
},
/**
* @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 {Ametys.relation.RelationPoint} item.source The source (in the relation way) of the drag operation.
*/
_getDragInfo: function (item)
{
var targets = [];
for (var i = 0; i < item.records.length; i++)
{
var node = item.records[i];
if ((node.get('type') == 'page' || node.get('type') == 'question') && node.get('canWrite'))
{
targets.push(this._getMessageTargetConfiguration(node));
}
}
if (targets.length > 0)
{
item.source = {
relationTypes: [Ametys.relation.Relation.MOVE],
targets: targets
};
}
},
/**
* @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.
* @param {String} dropPosition "before", "after" or "append" depending whether the dragged object (in item.target) should be inserted before, after the targetRecords, or appended to it.
*/
_getDropInfo: function(targetRecords, item, dropPosition)
{
var targets = [];
var positionInTargets = -1
for (var i = 0; i < targetRecords.length; i++)
{
var node = targetRecords[i];
if (dropPosition == 'append')
{
targets.push(this._getMessageTargetConfiguration(node));
}
else if (node.parentNode != null) // dropPosition == 'before' or 'after'
{
// Get node position
for (var i=0; i < node.parentNode.childNodes.length; i++)
{
if (node.parentNode.childNodes[i].getId() == node.getId())
{
positionInTargets = i + (dropPosition == 'after' ? + 1 : 0);
break;
}
}
targets.push(this._getMessageTargetConfiguration(node.parentNode));
}
}
if (targets.length > 0)
{
item.target = {
relationTypes: [Ametys.relation.Relation.MOVE],
targets: targets,
positionInTargets: positionInTargets
};
}
},
/**
* @private
* Get the target configuration object for given record
* @param {Ext.data.Model} record The tree record to convert to its Ametys.message.MessageTarget configuration
* @return {Object} The configuration to create a Ametys.message.MessageTarget. Can be null, if the record is null or not relevant to be a messagetarget.
*/
_getMessageTargetConfiguration: function (record)
{
if (record == null)
{
// Empty selection
return null;
}
else
{
if (record.isRoot())
{
return {
id: Ametys.message.MessageTarget.FORM_TARGET,
parameters: {
id: record.getId(),
hasEntries: record.get('hasEntries')
}
};
}
else if (record.get('type') == 'page')
{
return {
id: Ametys.message.MessageTarget.FORM_PAGE,
parameters: {
id: record.getId(),
formId: record.get('parentId'),
canWrite: record.get('canWrite')
}
};
}
else if (record.get('type') == 'question')
{
var pageId = record.get('parentId');
var formId = this._tree.getStore().getNodeById(pageId).get('parentId');
return {
id: Ametys.message.MessageTarget.FORM_QUESTION,
parameters: {
id: record.getId(),
type: record.get('questionType'),
pageId: pageId,
formId: formId,
canWrite: record.get('canWrite')
}
};
}
}
},
/**
* Listener on deletion messages
* @param {Ametys.message.Message} message The deletion message.
* @private
*/
_onDeletionMessage: function (message)
{
var questionTarget = message.getTarget(Ametys.message.MessageTarget.FORM_QUESTION),
pageTarget = message.getTarget(Ametys.message.MessageTarget.FORM_PAGE),
formTarget = message.getTarget(Ametys.message.MessageTarget.FORM_TARGET);
if (questionTarget)
{
this._tree.reloadParent(questionTarget, false, Ext.bind(this._tree._refreshIsConfiguedAttribute, this._tree));
}
else if (pageTarget)
{
this._tree.reloadParent(pageTarget, false, Ext.emptyFn);
}
else if (formTarget)
{
this._tree.reloadParent(formTarget, false, Ext.emptyFn);
}
},
/**
* Listener on selection preview messages
* @param {Ametys.message.Message} message The selection message.
* @private
*/
_onSelectionPreviewChanged: function (message)
{
var targets = message.getTargets(function(target) {
return target.getId() == Ametys.message.MessageTarget.FORM_QUESTION
});
if (targets.length > 0)
{
var targetParams = targets[0].getParameters();
var formId = this._formId;
if (targetParams.formId == formId)
{
this._tree.onFormComponentCreated(targetParams.id, Ext.emptyFn);
}
}
},
/**
* Listener on moved messages
* @param {Ametys.message.Message} message The moved message.
* @private
*/
_onMoved: function (message)
{
var targets = message.getTargets(function(target) {
return target.getId() == Ametys.message.MessageTarget.FORM_QUESTION
});
if (targets.length > 0)
{
var targetParams = targets[0].getParameters();
var formId = this._formId;
if (targetParams.formId == formId)
{
this._tree.refreshPageNode(targets[0].getParameters().pageId);
}
}
},
/**
* Opens the preview tool for the record.
* @param {Ext.view.View} view The view.
* @param {Ext.data.Model} record The record that belongs to the item.
*/
_openPreviewTool: function(view, record)
{
var type = record.get('type');
switch (type) {
case 'form':
case 'root':
var id = record.get('id');
break;
case 'page':
var pageId = record.get('id');
var id = record.get('parentId');
break;
case 'question':
var questionId = record.get('id');
var pageId = record.get('parentId');
var id = this._tree.getStore().getNodeById(pageId).get('parentId');
break;
case 'root':
default:
return;
}
var tool = Ametys.tool.ToolsManager.getTool('uitool-form-preview$' + id);
if (tool)
{
// Focus the preview tool to select and see it
if (!tool.hasFocus())
{
tool.focus();
}
// Then refocus the forms tool
this.focus();
}
else
{
var params = {
id: id,
pageId: pageId,
questionId: questionId
};
Ametys.tool.ToolsManager.openTool('uitool-form-preview', params);
}
},
/**
* @private
* Open the question dialog for edition
* @param {Object} questionData The question data.
*/
_openQuestionDialog: function(questionData)
{
Ametys.plugins.forms.helper.QuestionEditDialogHelper.open({
questionType: questionData.questionType,
formId: questionData.formId,
id: questionData.id,
dialogTitle: questionData.typeLabel,
iconGlyph: questionData.iconGlyph,
hasRule: questionData.hasRule
});
},
/**
* Listener on selection changed message.
* @param {Ametys.message.Message} message The selection changed message.
* @private
*/
_onMessageSelectionChanged: function(message)
{
var formTarget = message.getTarget(Ametys.message.MessageTarget.FORM_TARGET);
if (formTarget && formTarget.getParameters().id != this._formId)
{
this._formId = formTarget.getParameters().id;
this.showOutOfDate();
}
}
});
/** Class defining target message names for form pages */
Ext.define("Ametys.message.FormMessageTarget",
{
override: "Ametys.message.MessageTarget",
statics:
{
/**
* @member Ametys.message.MessageTarget
* @readonly
* @property {String} FORM_PAGE The target type is a page. The expected parameters are:
* @property {String} FORM_PAGE.id The id of the form page
* @property {String} FORM_PAGE.title The title of the page
* @property {String} FORM_PAGE.formId The id of the parent form
*/
FORM_PAGE: "form-page",
/**
* @member Ametys.message.MessageTarget
* @readonly
* @property {String} FORM_QUESTION The target type is a question. The expected parameters are:
* @property {String} FORM_QUESTION.id The id of the question
* @property {String} FORM_QUESTION.title The name of the question
* @property {String} FORM_QUESTION.formId The id of the parent form
*/
FORM_QUESTION: "form-question"
}
});