/*
* Copyright 2013 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.
*/
/**
* Helper for grids of {@link Ametys.plugins.cms.search.AbstractSearchTool}s
* @private
*/
Ext.define('Ametys.plugins.cms.search.SearchGridHelper', {
singleton: true,
/**
* @private
* @property {Ext.XTemplate} _repeaterTpl The template used for rendering repeater entries
*/
_repeaterTpl: Ext.create('Ext.XTemplate', [
'<ul class="a-grid-repeater-entries">',
'<tpl for="entries">',
'<li class="a-grid-repeater-entry">{label}<tpl if="!tpl"> ({position})</tpl></li>',
'</tpl>',
'</ul>'
]),
// ----------------------------------------------------------
// ---------------- Converters and renderers ----------------
// ----------------------------------------------------------
/**
* Convert workflow step value for model.
* @param {String/Object[]} value If it is an object, it will return the 'name' property.
* @param {String} value.name The name of workflow step
* @param {Ext.data.Model} record The record
* @param {String} dataIndex The model data index
* @return {String} The name of workflow step
*/
convertWorkflowStep: function (value, record, dataIndex)
{
var properties = {};
record.data[dataIndex + "_workflowStep"] = properties;
if (Ext.isArray(value) && value.length > 0 && Ext.isObject(value[0]))
{
for (var i = 0; i < value.length; i++)
{
properties[value[i].name] = value[i];
value[i] = value[i].name;
}
}
else if (Ext.isObject(value) && !Ext.Object.isEmpty(value))
{
properties[value.name] = value;
value = value.name;
}
else
{
value = '';
}
return value;
},
/**
* Function to render workflow step in result grid
* @param {Object} value The data value
* @param {Object} metaData A collection of metadata about the current cell
* @param {Ext.data.Model} record The record
* @param {Number} rowIndex The index of the current row
* @param {Number} colIndex The index of the current column
* @param {Ext.data.Store} store The data store
* @param {Ext.view.View} view The current view
* @param {String} dataIndex The data index of the column
*
*/
renderWorkflowStep: function (value, metaData, record, rowIndex, colIndex, store, view, dataIndex)
{
if (Ext.isEmpty(value))
{
return '';
}
var inGroup = (dataIndex == null);
dataIndex = inGroup ? rowIndex : dataIndex; // When used by grouping feature, data index is the 4th arguments
var properties = record.get(dataIndex + "_workflowStep");
var property = properties ? properties[value] : null;
if (property)
{
return '<img src="' + Ametys.CONTEXT_PATH + property.smallIcon + '" alt="' + property.name + '" title="' + property.name + '" class="a-grid-icon a-grid-icon-workflow"/>'
+ (inGroup ? property.name :'');
}
else if (record.get('workflowStepIcon'))
{
return '<img src="' + Ametys.CONTEXT_PATH + record.get('workflowStepIcon') + '" alt="' + value + '" title="' + value + '" class="a-grid-icon a-grid-icon-workflow"/>'
+ (inGroup ? value :'');
}
else
{
return value;
}
},
/**
* Function to render content's title in result grid
* @param {Object} value The data value
* @param {Object} metaData A collection of metadata about the current cell
* @param {Ext.data.Model} record The record
*/
renderTitle: function (value, metaData, record)
{
var title = Ametys.plugins.cms.search.SearchGridHelper.renderMultilingualString(value, metaData, record) || record.get('name');
if (record.get('iconGlyph') != null)
{
return '<span class="a-grid-glyph ' + record.get('iconGlyph') + (record.get('iconDecorator') != null ? ' ' + record.get('iconDecorator') : '') + '"></span>' + Ext.String.escapeHtml(title);
}
else if (record.get('iconSmall'))
{
return '<img src="' + Ametys.CONTEXT_PATH + record.get('iconSmall') + '" class="a-grid-icon a-grid-icon-title"/>' + Ext.String.escapeHtml(title);
}
return title;
},
/**
* Function to render content's title in result grid
* @param {Object} value The data value
* @param {Object} metaData A collection of metadata about the current cell
* @param {Ext.data.Model} record The record
*/
renderMultilingualTitle: function (value, metaData, record)
{
console.warn("Ametys.plugins.cms.search.SearchGridHelper.renderMultilingualTitle is obsolete and should be replaced by Ametys.plugins.cms.search.SearchGridHelper.renderTitle that supports both string and multilingual string titles")
return this.renderTitle(value, metaData, record);
},
/**
* Function to render a boolean value as 'yes' or 'no' text
* @param {Object} value The data value
* @param {Object} metaData A collection of metadata about the current cell
* @param {Ext.data.Model} record The record
* @Deprecated Use {@link Ametys.grid.GridColumnHelper.renderBoolean}
*/
renderBoolean: function (value, metaData, record)
{
return Ametys.grid.GridColumnHelper.renderBoolean.apply(this, arguments);
},
/**
* Function to render a boolean value with icon
* @param {Object} value The data value
* @param {Object} metaData A collection of metadata about the current cell
* @param {Ext.data.Model} record The record
* @Deprecated Use {@link Ametys.grid.GridColumnHelper.renderBooleanIcon}
*/
renderBooleanIcon: function (value, metaData, record)
{
return Ametys.grid.GridColumnHelper.renderBooleanIcon.apply(this, arguments);
},
/**
* Function to render a multiple string (Array) on one line, separated by commas.
* @param {Object} value The value
* @param {Object} metaData A collection of metadata about the current cell
* @param {Ext.data.Model} record The record
*/
renderMultipleString: function(value, metaData, record)
{
if (Ext.isArray(value))
{
return value.join(', ');
}
return value;
},
/**
* Convert an multiple String value into a Array of values
* @param {Object/Object[]} val If it is an Array, it will return the string 'value' separated by comma.
* @param {Ext.data.Model} record The record
* @param {String} dataIndex The model data index
* @return {String[]} The values into a Array
*/
convertMultipleValue: function(val, record, dataIndex)
{
if (Ext.isArray(val))
{
// Keep value as an array
return val;
}
else
{
return Ext.isEmpty(val) ? [] : val.split(',');
}
},
/**
* Function to render a multiple value (Array) with a line for each value
* @param {Object} value The value
* @param {Object} metaData A collection of metadata about the current cell
* @param {Ext.data.Model} record The record
* @param {Number} rowIndex The index of the current row
* @param {Number} colIndex The index of the current column
* @param {Ext.data.Store} store The store
* @param {Ext.view.View} view The current view
* @param {String} dataIndex The index of column
* @param {Object[]} enumeration The enumeration of possible values. The key is the field value, and the value is the associated label.
* @param {Function} [singleValueRenderer] The renderer of a single value
*/
renderMultipleValue: function(value, metaData, record, rowIndex, colIndex, store, view, dataIndex, enumeration, singleValueRenderer)
{
if (Ext.isEmpty(value))
{
return '';
}
dataIndex = dataIndex || rowIndex; // When used by grouping feature, data index is the 4th arguments
if (singleValueRenderer == null)
{
singleValueRenderer = Ext.identityFn;
}
let values = this._getValuesForMultipleData(dataIndex, record.data);
return values
.map(function(v) { return singleValueRenderer(v, metaData, record, rowIndex, colIndex, store, view, dataIndex, enumeration); })
.join('<br/>');
},
/**
* Convert a multilingual value for an object.
* @param {Object} value The value as a object with the value for each language
* @param {Ext.data.Model} record The record
* @param {String} dataIndex The model data index
* @return {String} The property value or code.
*/
convertMultilingualString: function (value, record, dataIndex)
{
var properties = {};
record.data[dataIndex + '_multilingual'] = properties;
if (Ext.isObject(value))
{
properties = value;
var currentLang = Ametys.cms.language.LanguageDAO.getCurrentLanguage();
if (value[currentLang])
{
value = value[currentLang];
}
else if (value.en)
{
value = value.en;
}
else
{
value = '';
Ext.Object.eachValue(properties, function(v) {
if (Ext.isEmpty(v))
{
value = v;
return false; // stop iteration
}
});
}
}
return value;
},
/**
* Returns the localized value of a multilingual string value.<br>
* The return localized value will be in order:<br>
* - the value for given language if not null and value for this language is not empty,<br>
* - or the value for current language if exists and not empty,<br>
* - or the value for default language 'en' if exists and not empty,<br>
* - or the first non-empty value<br>
* @param {Object} values the multilingual values as a Map of values for each available languages
* @param {String} [defaultLanguage] The language of preference. Can be null.
* @return the localized value
*/
getDefaultLocalizedValue: function (values, defaultLanguage)
{
// If not null, first try to get the value for given language
if (defaultLanguage && values[defaultLanguage])
{
return values[defaultLanguage];
}
// Then try to get the value for current language
var currentLang = Ametys.cms.language.LanguageDAO.getCurrentLanguage();
if (values[currentLang])
{
return values[currentLang];
}
// Then try to get value for default language 'en'
if (values.en)
{
return values.en;
}
// Then return the first non-empty value
var value = '';
Ext.Object.eachValue(values, function(v) {
if (Ext.isEmpty(v))
{
value = v;
return false; // stop iteration
}
});
return value;
},
/**
* Function to render an multilingual value in result grid
* @param {Object} value The value for each languages
* @param {Object} metadata A collection of metadata about the current cell
* @param {Ext.data.Model} record The record
* @param {Number} rowIndex The index of the current row
* @param {Number} colIndex The index of the current column
* @param {Ext.data.Store} store The store
* @param {Ext.view.View} view The current view
* @param {String} dataIndex the record data index
* @return {String} The value for the default language.
*/
renderMultilingualString: function(value, metadata, record, rowIndex, colIndex, store, view, dataIndex)
{
if (Ext.isObject(value))
{
return Ext.String.escapeHtml(this.getDefaultLocalizedValue(value));
}
return value;
},
/**
* Function to render an enumerated value in result grid
* @param {Object} value The data value
* @param {Object} metadata A collection of metadata about the current cell
* @param {Ext.data.Model} record The record
* @param {Number} rowIndex The index of the current row
* @param {Number} colIndex The index of the current column
* @param {Ext.data.Store} store The store
* @param {Ext.view.View} view The current view
* @param {String} dataIndex the record data index
* @param {Object[]} enumeration The enumeration of possible values. The key is the field value, and the value is the associated label.
*/
renderEnumeration: function(value, metadata, record, rowIndex, colIndex, store, view, dataIndex, enumeration)
{
value = Ext.Array.from(value);
var labels = [];
Ext.Array.forEach(value, function(v) {
labels.push(Ext.String.escapeHtml(enumeration[v] || v));
});
return labels.join('<br/>');
},
/**
* Convert an enumerated value for an object.
* @param {String/Object[]} val If it is an object, it will return the 'value' property.
* @param {String} val.value The property value or code.
* @param {Ext.data.Model} record The record
* @param {String} dataIndex The model data index
* @return {String} The property value or code.
*/
convertEnumeratedData: function(val, record, dataIndex)
{
var properties = {};
record.data[dataIndex + '_enum'] = properties;
if (Ext.isArray(val) && val.length > 0 && Ext.isObject(val[0]))
{
for (var i = 0; i < val.length; i++)
{
properties[val[i].value] = val[i];
val[i] = val[i].value;
}
}
else if (Ext.isObject(val))
{
properties[val.value] = val;
val = val.value;
}
return val;
},
/**
* Function to render a geocode metadata.
* @param {Object} value The value
* @param {Object} metaData A collection of metadata about the current cell
* @param {Ext.data.Model} record The record
* @param {Number} rowIndex The index of the current row
* @param {Number} colIndex The index of the current column
* @param {Ext.data.Store} store The store
* @param {Ext.view.View} view The current view
* @param {String} dataIndex The index of column
*/
renderGeocode: function(value, metaData, record, rowIndex, colIndex, store, view, dataIndex)
{
if (value != null && value.latitude != undefined && value.longitude != undefined)
{
return parseFloat(value.latitude).toFixed(4) + "°N, " + parseFloat(value.longitude).toFixed(4) + "°E";
}
return value;
},
/**
* Convert a language value for model.
* @param {String/Object[]} value If it is an object, it will return the 'code' property.
* @param {String} value.code The language code
* @param {Ext.data.Model} record The record
* @param {String} dataIndex The model data index
* @return {String} The language code only
*/
convertLanguage: function (value, record, dataIndex)
{
var properties = {};
record.data[dataIndex + "_language"] = properties;
if (Ext.isArray(value) && value.length > 0 && Ext.isObject(value[0]))
{
for (var i = 0; i < value.length; i++)
{
properties[value[i].code] = value[i];
value[i] = value[i].code;
}
}
else if (Ext.isObject(value))
{
properties[value.code] = value;
value = value.code;
}
return value;
},
/**
* Function to render the content language
* @param {Object} value The data value
* @param {Object} metaData A collection of metadata about the current cell
* @param {Ext.data.Model} record The record
* @param {Number} rowIndex The index of the current row
* @param {Number} colIndex The index of the current column
* @param {Ext.data.Store} store The store
* @param {Ext.view.View} view The current view
* @param {String} dataIndex concatenated with '_language' will be get the language additional informations.
*/
renderLanguage: function (value, metaData, record, rowIndex, colIndex, store, view, dataIndex)
{
if (Ext.isEmpty(value))
{
return '';
}
dataIndex = dataIndex || rowIndex; // When used by grouping feature, data index is the 4th arguments
var properties = record.get(dataIndex + "_language");
var property = properties[value];
return property ? property.label : value;
},
/**
* Convert a user object to a user light-object. The light version is the same handled by the user widget, so editing such user will not change the object
* @param {Object/Object[]} value The value as a object with the value for each language
* @param {Ext.data.Model} record The record
* @param {String} dataIndex The model data index
* @return {Object/Object[]} The property value or code.
* @Deprecated Use {@link Ametys.grid.GridColumnHelper.convertUser}
*/
convertUser: function (value, record, dataIndex)
{
return Ametys.grid.GridColumnHelper.convertUser.apply(this, arguments);
},
/**
* Function to render the user login
* @param {Object} value The data value
* @param {Object} metaData A collection of metadata about the current cell
* @param {Ext.data.Model} record The record
* @param {Number} rowIndex The index of the current row
* @param {Number} colIndex The index of the current column
* @param {Ext.data.Store} store The store
* @param {Ext.view.View} view The current view
* @param {String} dataIndex concatenated with '_user' will get the user additional informations.
* @Deprecated Use {@link Ametys.grid.GridColumnHelper.renderUser}
*/
renderUser: function(value, metaData, record, rowIndex, colIndex, store, view, dataIndex)
{
return Ametys.grid.GridColumnHelper.renderUser.apply(this, arguments);
},
/**
* Convert a content value for model.
* Content values can be of two kinds
* @param {String/String[]/Object/Object[]} value If it is an object, it will return the 'id' property.
* @param {String} value.id The id
* @param {Ext.data.Model} record The record
* @param {String} dataIndex The model data index
* @return {String/String[]} The ids only
*/
convertContent: function(value, record, dataIndex)
{
if ((typeof value == "string"
|| Ext.isArray(value) && (value.length == 0
|| (typeof value[0] == "string")
|| Ext.isArray(value[0]) && (value[0].length == 0
|| typeof value[0][0] == "string")))
&& record.data[dataIndex + "_content"])
{
// already converted when back from repeater
return value;
}
var properties = {};
if (!record.data[dataIndex + "_content_initial"])
{
record.data[dataIndex + "_content_initial"] = Ext.clone(value);
}
record.data[dataIndex + "_content"] = properties;
return valueToProp(value, properties);
function valueToProp(value, properties)
{
if (Ext.isArray(value))
{
let values = [];
for (var i = 0; i < value.length; i++)
{
values.push(valueToProp(value[i], properties));
}
return values;
}
else if (Ext.isObject(value))
{
properties[value.id] = value;
return value.id;
}
else
{
return value; // Should be null when null or undefined when undefined ODF-3560
}
}
},
/**
* @protected
* Creates a content renderer
* @param {Object} [config] The config
* @param {String} [config.pluginName=cms] The plugin holding the url to retrieve info
* @param {String} [config.url=contents/get-info] The plugin holding the url to retrieve info
* @param {Boolean} [config.notClickable=fals] The plugin holding the url to retrieve info
*/
createRenderContent: function(config)
{
config = Ext.applyIf(config || {}, {
pluginName: 'cms',
url: 'contents/get-info',
notClickable: false
});
return function (value, metaData, record, rowIndex, colIndex, store, view, dataIndex)
{
if (Ext.isEmpty(value))
{
return '';
}
dataIndex = dataIndex || rowIndex; // When used by grouping feature, data index is the 4th arguments
var values = Ext.isArray(value) ? value : [value];
var html = '';
var properties = record.get((dataIndex) + "_content");
var contentToResolve = [];
for (var i=0; i < values.length; i++)
{
var contentId = values[i];
var property = properties[contentId];
if (!property)
{
contentToResolve.push(contentId);
}
}
if (contentToResolve.length > 0)
{
// To resolve
var response = Ametys.data.ServerComm.send({
plugin: config.pluginName,
url: config.url,
parameters: {ids: contentToResolve},
priority: Ametys.data.ServerComm.PRIORITY_SYNCHRONOUS,
responseType: 'text'
});
if (Ametys.data.ServerComm.isBadResponse(response))
{
for (var i=0; i < values.length; i++)
{
record.get(dataIndex + "_content")[values[i]] = {
id: values[i],
isSimple: true,
title: values[i]
}
}
return Ext.isArray(value) ? value.join('<br/>') : value;
}
else
{
var result = Ext.JSON.decode(Ext.dom.Query.selectValue("", response));
var contents = result.contents;
for (var i=0; i < contents.length; i++)
{
record.get(dataIndex + "_content")[contents[i].id] = {
id: contents[i].id,
isSimple: contents[i].isSimple,
title: contents[i].title
}
}
}
}
var properties = record.get(dataIndex + "_content");
for (var i=0; i < values.length; i++)
{
var contentId = values[i];
var property = properties[contentId];
// Already resolved
if (!Ext.isEmpty(html))
{
html +='<br/>';
}
if (property.isSimple || config.notClickable)
{
html += Ext.String.escapeHtml(property.title);
}
else if (property.title)
{
html += '<a href="javascript:(function(){Ametys.tool.ToolsManager.openTool(\'uitool-content\', {id:\'' + contentId + '\'});})()">' + Ext.String.escapeHtml(property.title) + '</a>';
}
}
return html;
}
},
/**
* Function to render a content value
* @param {Object} value The data value
* @param {Object} metaData A collection of metadata about the current cell
* @param {Ext.data.Model} record The record
* @param {Number} rowIndex The index of the current row
* @param {Number} colIndex The index of the current column
* @param {Ext.data.Store} store The store
* @param {Ext.view.View} view The current view
* @param {String} dataIndex concatenated with '_content' will be get the content additional informations.
*/
/**
* Convert a tag value for model.
* @param {String/Object[]} value If it is an object, it will return the 'name' property.
* @param {String} value.name The tag name
* @param {Ext.data.Model} record The record
* @param {String} dataIndex The model data index
* @return {String} The tag name only
*/
convertTag: function (value, record, dataIndex)
{
var properties = {};
record.data[dataIndex + "_tag"] = properties;
if (Ext.isArray(value) && value.length > 0 && Ext.isObject(value[0]))
{
for (var i = 0; i < value.length; i++)
{
properties[value[i].name] = value[i];
value[i] = value[i].name;
}
}
else if (Ext.isObject(value))
{
properties[value.name] = value;
value = value.name;
}
return value;
},
/**
* Function to render a tag value
* @param {Object} value The data value
* @param {Object} metaData A collection of metadata about the current cell
* @param {Ext.data.Model} record The record
* @param {Number} rowIndex The index of the current row
* @param {Number} colIndex The index of the current column
* @param {Ext.data.Store} store The store
* @param {Ext.view.View} view The current view
* @param {String} dataIndex concatenated with '_tag' will be get the tag additional informations.
*/
renderTag: function (value, metaData, record, rowIndex, colIndex, store, view, dataIndex)
{
if (Ext.isEmpty(value))
{
return '';
}
dataIndex = dataIndex || rowIndex; // When used by grouping feature, data index is the 4th arguments
var values = Ext.isArray(value) ? value : [value];
var labels = [];
var properties = record.get(dataIndex + "_tag");
for (var i=0; i < values.length; i++)
{
var tagName = values[i];
var property = properties[tagName];
labels.push (property ? Ext.String.escapeHtml(property.label) : tagName);
}
return labels.join(", ");
},
/**
* Function to render a richtext
* @param {Object} value The data value
* @param {Object} metaData A collection of metadata about the current cell
* @param {Ext.data.Model} record The record
* @param {Number} rowIndex The index of the current row
* @param {Number} colIndex The index of the current column
* @param {Ext.data.Store} store The store
* @param {Ext.view.View} view The current view
* @param {String} dataIndex concatenated with '_richtext' will get the richtext additional informations.
*/
renderRichText: function(value, metaData, record, rowIndex, colIndex, store, view, dataIndex)
{
if (Ext.isEmpty(value))
{
return '';
}
var values = Ext.Array.from(value);
var html = '';
for (var i=0; i < values.length; i++)
{
var v = values[i];
if (!Ext.isEmpty(html))
{
html += '<br/>';
}
if (Ext.isObject(v))
{
html += v.value || '';
}
else
{
html += v;
}
}
return html;
},
/**
* Function to render a file
* @param {Object} value The data value
* @param {Object} metaData A collection of metadata about the current cell
* @param {Ext.data.Model} record The record
* @param {Number} rowIndex The index of the current row
* @param {Number} colIndex The index of the current column
* @param {Ext.data.Store} store The store
* @param {Ext.view.View} view The current view
* @param {String} dataIndex concatenated with '_file' will get the file additional informations.
*/
renderFile: function(value, metaData, record, rowIndex, colIndex, store, view, dataIndex)
{
if (Ext.isEmpty(value))
{
return '';
}
dataIndex = dataIndex || rowIndex; // When used by grouping feature, data index is the 4th arguments
var values = Ext.Array.from(value);
var html = '';
for (var i=0; i < values.length; i++)
{
var fileValue = values[i];
if (!Ext.isEmpty(html))
{
html += '<br/>';
}
if (Ext.isObject(fileValue))
{
var hint = Ext.String.format("{{i18n plugin.cms:UITOOL_CONTENTEDITIONGRID_COLUMN_DOWNLOAD_FILE}}", fileValue.filename);
html += fileValue.filename ? '<a href="' + fileValue.downloadUrl + '" target="_blank" title="' + hint + '">' + Ext.String.escapeHtml(fileValue.filename) +'</a>' : '';
}
else
{
html += fileValue[i];
}
}
return html;
},
/**
* Function to render a reference
* @param {Object} value The data value
* @param {Object} metaData A collection of metadata about the current cell
* @param {Ext.data.Model} record The record
* @param {Number} rowIndex The index of the current row
* @param {Number} colIndex The index of the current column
* @param {Ext.data.Store} store The store
* @param {Ext.view.View} view The current view
* @param {String} dataIndex The date column.
*/
renderReference: function(value, metaData, record, rowIndex, colIndex, store, view, dataIndex)
{
if (Ext.isEmpty(value))
{
return '';
}
return value.value ? value.value : value;
},
/**
* Function to render a display value based upon another record entry.
* You have to specify as an additional parameter the base field id.
*
* // record has a entry 'entry' and an entry 'entryDisplay' that show a displayable version of 'entry'
* { renderer: Ext.bind(Ametys.plugins.cms.search.SearchGridHelper.renderDisplay, null, ['entry'], true);
*
* @param {Object} value The data value
* @param {Object} metaData A collection of metadata about the current cell
* @param {Ext.data.Model} record The record
* @param {Number} rowIndex The index of the current row
* @param {Number} colIndex The index of the current column
* @param {Ext.data.Store} store The store
* @param {Ext.view.View} view The current view
* @param {String} dataIndex concatenated with 'Display' will be the name of the dataIndex to display
*/
renderDisplay: function (value, metaData, record, rowIndex, colIndex, store, view, dataIndex)
{
dataIndex = dataIndex || rowIndex; // When used by grouping feature, data index is the 4th arguments
var fields = Ext.create('Ext.util.MixedCollection', {
getKey: function(el) {
return el.name; //the key for this collection is the attribute 'name' of each item
}
});
fields.addAll( record.getFields() );
var displayField = fields.getByKey(dataIndex).displayField;
if (displayField)
{
var displayValue = record.data[displayField] || value;
return Ext.isArray(displayValue) ? Ext.Array.map(displayValue, v => Ext.String.escapeHtml(v)).join('<br/>') : Ext.String.escapeHtml(displayValue);
}
return Ext.isArray(value) ? Ext.Array.map(value, v => Ext.String.escapeHtml(v)).join('<br/>') : Ext.String.escapeHtml(value);
},
/**
* Function to render a repeater
* @param {Object} value The data value
* @param {Object} metaData A collection of metadata about the current cell
* @param {Ext.data.Model} record The record
* @param {Number} rowIndex The index of the current row
* @param {Number} colIndex The index of the current column
* @param {Ext.data.Store} store The store
* @param {Ext.view.View} view The current view
* @param {String} dataIndex concatenated with 'Display' will be the name of the dataIndex to display
*/
renderRepeater: function (value, metaData, record, rowIndex, colIndex, store, view, dataIndex)
{
if (Ext.isEmpty(value))
{
return '';
}
var repeater = record.get(dataIndex + "_repeater") || record.get(dataIndex);
if (Ext.isObject(repeater))
{
var rawValues = repeater.entries;
if (Ext.isArray(rawValues) && rawValues.length > 0)
{
var repeaterLabel = repeater.label;
var headerTpl = null;
let tplString = repeater["header-label"] || record.fields.filter(f => f.name == dataIndex)[0]['header-label'];
if (tplString)
{
var headerLabel = tplString;
headerTpl = new Ext.Template(headerLabel, {compiled: true});
}
var entries = [];
for (var i in rawValues)
{
var rawValue = rawValues[i];
var label = repeaterLabel;
var emptyValue = true;
if (headerTpl != null)
{
headerParams = {};
while ((result = Ametys.form.ConfigurableFormPanel.Repeater.HEADER_VARIABLES.exec(headerLabel)) != null)
{
var fieldValue = rawValue.values[result[1]];
if (fieldValue != null)
{
emptyValue = false;
if (rawValue.values[result[1] + "_content"] != null) // Content in model (we just have its id)
{
let texts = [];
for (let c of Ext.Array.from(fieldValue))
{
if (rawValue.values[result[1] + "_content"][c])
{
texts.push(rawValue.values[result[1] + "_content"][c].title || c);
}
}
fieldValue = texts;
}
else if (Ext.isObject(fieldValue) || Ext.isArray(fieldValue)) // Content not in model (we have a full object)
{
let texts = [];
for (let c of Ext.Array.from(fieldValue))
{
if (Ext.isObject(c))
{
texts.push(c.title || c.id || c);
}
}
fieldValue = texts;
}
headerParams[result[1]] = fieldValue;
}
}
var addTitle = headerTpl.apply(headerParams);
if (!emptyValue && addTitle)
{
label = addTitle;
}
}
entries.push({
label: Ext.String.escapeHtml(label),
position: rawValue.position,
tpl: headerTpl != null && !emptyValue
});
}
var html = this._repeaterTpl.applyTemplate({
entries: entries,
});
return html;
}
}
return '';
},
_explodeRepeater: function (entries, record, repeater, prefix)
{
let me = this;
let repeaterEntries = repeater.entries;
Ext.Array.each(repeaterEntries, function (repeaterEntry) {
entries["_" + prefix.substring(0, prefix.length - 1) + "[" + repeaterEntry.position + "]/previous-position"] = repeaterEntry['previous-position'] != undefined ? repeaterEntry['previous-position'] : repeaterEntry.position;
Ext.Object.each(repeaterEntry.values, function (attributeName, repeaterEntryValue) {
if (Ext.String.endsWith(attributeName, "_content")
|| Ext.String.endsWith(attributeName, "_initial")
|| Ext.String.endsWith(attributeName, "_external_status")
|| Ext.String.endsWith(attributeName, "_enum")
|| attributeName == "id"
|| record.get(repeaterEntry.values[attributeName + "_repeater"]))
{
// We do not expose internal record attributs
return;
}
if (Ext.String.endsWith(attributeName, "_repeater"))
{
attributeName = attributeName.substring(0, attributeName.length - "_repeater".length);
}
if (Ext.isObject(repeaterEntryValue) && repeaterEntryValue.entries !== undefined && Ext.isArray(repeaterEntryValue.entries))
{
me._explodeRepeater(entries, record, repeaterEntryValue, prefix.substring(0, prefix.length - 1) + "[" + repeaterEntry.position + "]/" + attributeName + "/");
}
else
{
entries[prefix.substring(0, prefix.length - 1) + "[" + repeaterEntry.position + "]/" + attributeName] = repeaterEntryValue;
}
});
});
entries["_" + prefix + "size"] = repeaterEntries.length;
},
/**
* Convert a repeater value for model.
* @param {String/Object} value If it is an object, it will return the array of keys.
* @param {String} value.login The user login
* @param {Ext.data.Model} record The record
* @param {String} dataIndex The model data index
* @return {String} The user login only
*/
convertRepeater: function(value, record, dataIndex)
{
// Non-empty repeater ?
if (Ext.isObject(value) && Ext.isArray(value.entries))
{
record.data[dataIndex + "_repeater"] = value;
let entries = {};
// Convert the dataformat recursively in repeaters now
Ametys.plugins.cms.search.SearchGridRepeaterDialog.convert(value.entries, record.getProxy().getModel().getField(dataIndex).subcolumns, dataIndex);
this._explodeRepeater(entries, record, value, "");
if (!record.data[dataIndex + "_repeater_initial"])
{
// First rendering of this repeater, let's store the original objet that contains all data, to be able to know if there is any modified stuff
record.data[dataIndex + "_repeater_initial"] = Ext.clone(value);
}
return entries;
}
else if (value && value._size) // Subrepeater already converted
{
return value;
}
else // Empty repeater
{
record.data[dataIndex + "_repeater"] = { entries: [], label: "" };
return {
"_size": 0
}
}
},
/**
* Function to render a date
* @param {Object} value The data value
* @param {Object} metaData A collection of metadata about the current cell
* @param {Ext.data.Model} record The record
* @param {Number} rowIndex The index of the current row
* @param {Number} colIndex The index of the current column
* @param {Ext.data.Store} store The store
* @param {Ext.view.View} view The current view
* @param {String} dataIndex concatenated with 'Display' will be the name of the dataIndex to display
* @Deprecated Use {@link Ametys.grid.GridColumnHelper.renderDate}
*/
renderDate: function (value, metaData, record, rowIndex, colIndex, store, view, dataIndex)
{
return Ametys.grid.GridColumnHelper.renderDate.apply(this, arguments);
},
/**
* Function to render a datetime
* @param {Object} value The data value
* @param {Object} metaData A collection of metadata about the current cell
* @param {Ext.data.Model} record The record
* @param {Number} rowIndex The index of the current row
* @param {Number} colIndex The index of the current column
* @param {Ext.data.Store} store The store
* @param {Ext.view.View} view The current view
* @param {String} dataIndex concatenated with 'Display' will be the name of the dataIndex to display
* @Deprecated Use {@link Ametys.grid.GridColumnHelper.renderDateTime}
*/
renderDateTime: function (value, metaData, record, rowIndex, colIndex, store, view, dataIndex)
{
return Ametys.grid.GridColumnHelper.renderDateTime.apply(this, arguments);
},
/**
* Function to render an hidden cell
* @param {Object} value The data value
* @param {Object} metaData A collection of metadata about the current cell
* @param {Ext.data.Model} record The record
* @param {Number} rowIndex The index of the current row
* @param {Number} colIndex The index of the current column
* @param {Ext.data.Store} store The store
* @param {Ext.view.View} view The current view
* @param {String} dataIndex concatenated with 'Display' will be the name of the dataIndex to display
*/
renderHiddenValue: function (value, metaData, record, rowIndex, colIndex, store, view, dataIndex)
{
return '';
},
/**
* Function to render error if the value is invalid
* @param {Object} value The value
* @param {Object} metaData A collection of metadata about the current cell
* @param {Ext.data.Model} record The record
* @param {Number} rowIndex The index of the current row
* @param {Number} colIndex The index of the current column
* @param {Ext.data.Store} store The store
* @param {Ext.view.View} view The current view
* @param {String} dataIndex The index of column
* @param {Object[]} enumeration The enumeration of possible values. The key is the field value, and the value is the associated label.
* @param {Function} [wrappedRenderer] The renderer of the value, wrapped by this one
*/
renderValueError: function(me, value, metaData, record, wrappedRenderer, args)
{
// metaData is a empty objet when used in grouping
if (metaData.column && !me.isValidValue(metaData.column.config, record))
{
// Add a class to the cell to indicate that the data is not valid
metaData.innerCls = ' cell-error';
}
if (metaData.column)
{
let dataIndex = metaData.column.dataIndex;
if (this._evaluateDisableCondition(record, dataIndex))
{
metaData.tdCls += ' cell-disabled';
if (this._shouldHideDisabledCell(record, dataIndex))
{
wrappedRenderer = Ext.bind(Ametys.getFunctionByName('Ametys.plugins.cms.search.SearchGridHelper.renderHiddenValue'), null, [dataIndex], true);
}
}
}
let root = this._getRootRecord(record)
rootRecord = root.record;
parentMetadataPath = root.parentPath;
if (rootRecord.get('notEditableData') == true || rootRecord.data['notEditableDataIndex'] && Ext.Array.contains(rootRecord.data['notEditableDataIndex'], (parentMetadataPath ? parentMetadataPath + '/' : '') + metaData.column.dataIndex))
{
metaData.tdCls += ' cell-noteditable';
}
if (wrappedRenderer == null)
{
wrappedRenderer = Ext.identityFn;
}
return wrappedRenderer.apply(me, args);
},
/**
* @private
*/
_evaluateDisableCondition(record, dataIndex)
{
let parentMetadataPath = this._getParentMetadataPath(record);
if (parentMetadataPath)
{
parentRecord = this._getParentRecord(record);
if (this._evaluateDisableCondition(parentRecord, parentMetadataPath))
{
return true;
}
}
// With imbricated repeaters and composite you may be here with a path including /
// We are interested only in the top level dataIndex... but we do not know if it is the last part or included in a composite
// So with a/b/c we have to try b/c then c
// TODO CMS-12481: get parentMetadataPath of parent record and remove common part instead of this algorithm
let field = null;
while (true)
{
field = record.getField(dataIndex);
if (field == null)
{
let i = dataIndex.indexOf('/');
if (i <= 0)
{
return false; // No field found, no disable condition
}
dataIndex = dataIndex.substring(i + 1);
}
else
{
break;
}
}
return Ametys.form.Widget.evaluateDisableCondition({
disableCondition: field.disableCondition,
dataPath: dataIndex,
record: record
});
},
/**
* @private
*/
_shouldHideDisabledCell(record, dataIndex)
{
let parentMetadataPath = this._getParentMetadataPath(record);
if (parentMetadataPath)
{
parentRecord = this._getParentRecord(record);
if (this._shouldHideDisabledCell(parentRecord, parentMetadataPath))
{
return true;
}
}
// With imbricated repeaters and composite you may be here with a path including /
// We are interested only in the top level dataIndex... but we do not know if it is the last part or included in a composite
// So with a/b/c we have to try b/c then c
// TODO CMS-12481: get parentMetadataPath of parent recode and remove common part instead of this algorithm
let field = null;
while (true)
{
field = record.getField(dataIndex);
if (field == null)
{
let i = dataIndex.indexOf('/');
if (i <= 0)
{
return false; // No field found, no disable condition
}
dataIndex = dataIndex.substring(i + 1);
}
else
{
break;
}
}
return Ametys.form.Widget.shouldHideDisabledField({
disableCondition: field.disableCondition,
disabledItemRendering: field.disabledItemRendering,
dataPath: dataIndex,
record: record
});
},
// -------------------------------------
// ---------------- API ----------------
// -------------------------------------
/**
* Return default sorters, based on the filled values of search criteria.
* <br>If one of the criteria uses a SEARCH/SEARCH_STEMMED operator and is not empty, then the returned default sorter
* will be an empty array (considered by server-side as a sort by score). Otherwise, it returns undefined and it should be other default ones (on your choice)
* @param {Object[]} criteria The search criteria
* @return {Object[]} The sorters
*/
getDefaultSorters: function(criteria)
{
var hasNonEmptySearchCriterion = false;
Ext.Object.each(criteria, function(criterionName, criterionVal) {
if ((criterionName.endsWith("-search") || criterionName.endsWith("-searchStemmed")) && Ext.isString(criterionVal) && criterionVal)
{
hasNonEmptySearchCriterion = true;
return false;
}
});
return hasNonEmptySearchCriterion ? [] /*would normally be [{property: 'score', direction: 'DESC'}] but it is not possible with current search API. Empty array is equivalent */
: undefined;
},
/**
* Return fields configuration from JSON definition
* @param {Object} data The fields definition
* @return {Object[]} The fields configuration
*/
getFieldsFromJson: function(data)
{
var fields = [];
for (var i in data)
{
var columnData = data[i];
var id = data[i].path;
var field = this.getFieldFromJson(id, columnData);
fields.push(field);
}
return fields;
},
/**
* Return a field configuration from a definition described in JSON
* @param {String} id The id of the field
* @param {Object} data The field definition
* @return {Object} The configuration object
*/
getFieldFromJson: function(id, data)
{
var cfg = {
name: id,
mapping: "properties['" + id + "']",
useNull: true
};
if (data.converter)
{
cfg.convert = Ext.bind(Ametys.getFunctionByName(data.converter), null, [id], true);
}
else
{
let defaultConvertor = this._getDefaultConvertor(id, data);
if (defaultConvertor != null)
{
cfg.convert = defaultConvertor;
}
}
if (data.disableCondition)
{
cfg.disableCondition = data.disableCondition;
if (data.disabledItemRendering)
{
cfg.disabledItemRendering = data.disabledItemRendering;
}
}
if (data['widget'])
{
cfg.widget = data['widget'];
}
if (data['widget-params'] && data['widget-params'].changeListeners)
{
cfg.changeListeners = data['widget-params'].changeListeners;
}
var needsMultipleConverter;
var extJsTypes = {
'long': 'int',
'double': 'number'
};
var type = data.type.toLowerCase();
cfg.ftype = type;
switch (type)
{
case 'string':
cfg.type = 'string';
if (data.enumeration && !data.converter)
{
cfg.convert = Ext.bind(Ametys.plugins.cms.search.SearchGridHelper.convertEnumeratedData, null, [id], true);
}
else if (data.multiple && !data.converter)
{
needsMultipleConverter = true;
}
break;
case 'long':
case 'double':
case 'boolean':
if (data.enumeration && !data.converter)
{
cfg.convert = Ext.bind(Ametys.plugins.cms.search.SearchGridHelper.convertEnumeratedData, null, [id], true);
}
else if (data.multiple && !data.converter)
{
needsMultipleConverter = true;
}
else
{
cfg.type = extJsTypes[type] || type;
}
cfg.allowNull = true;
break;
case 'repeater':
// repeaters
cfg.subcolumns = data.columns;
cfg['min-size'] = data['min-size'];
cfg['max-size'] = data['max-size'];
cfg['initial-size'] = data['initial-size'];
cfg['add-label'] = data['add-label'];
cfg['del-label'] = data['del-label'];
cfg['header-label'] = data['header-label'];
cfg['widget-params'] = data['widget-params'];
break;
}
if (needsMultipleConverter)
{
// need to keep value as an array (ExtJS default converter for multiple values join value separated by comma)
cfg.type = 'string';
cfg.convert = Ext.bind(Ametys.plugins.cms.search.SearchGridHelper.convertMultipleValue, null, [id], true);
}
// Wraps convertor with SynchronizedValues extractor
let wrappedConvertor = cfg.convert;
cfg.convert = function()
{
let args = Ext.Array.clone(arguments);
let originalValue = args[0];
let isValueAnArray = Ext.isArray(originalValue);
let values = Ext.Array.from(originalValue)
let outputValues = [];
for (let value of values)
{
if (value && (value.status == "external" || value.status == "local"))
{
var record = args[1];
record.data[id + "_external_status"] = value.status;
value = value[value.status] !== undefined ? value[value.status] : null; // Return the null value if there is no value at the given status because, the caller of convertor will ignore "undefined" but assign "null"
}
outputValues.push(value);
}
if (!isValueAnArray)
{
outputValues = outputValues[0];
}
// args is a copy, we can modify it to remove the args[0] and replace it
args.splice(0, 1, outputValues);
return wrappedConvertor ? wrappedConvertor.apply(this, args) : outputValues;
};
return cfg;
},
/**
* Return a convertor depending on the type
* @param {String} id The id of the field
* @param {Object} data The field definition
* @returns {Function} The convertor function or null
*/
_getDefaultConvertor: function(id, data)
{
var type = data.type.toLowerCase();
switch (type)
{
case 'user':
return Ext.bind(Ametys.grid.GridColumnHelper.convertUser, this);
case 'content':
return Ext.bind(Ametys.plugins.cms.search.SearchGridHelper.convertContent, this, [id], true);
default:
return null;
}
},
/**
* Return a sorters configuration from JSON definition
* @param {Object[]} columns The columns configuration from #getColumnsFromJson
* @param {Ext.grid.Panel} [grid] The grid to get state informations
* @param {Boolean} [forceDataOrder=false] True to make data with a higher priority in state
* @param {Object} [state] A state (in the Ext.state.Stateful vocabulary) of the grid
* @return {Object[]} The sorters configuration
*/
getSortersFromJson: function(columns, grid, forceDataOrder, state)
{
var stateSorters;
if (!state || Ext.Object.isEmpty(state))
{
var stateId = grid && grid.stateful ? grid.getStateId() : null;
if (stateId)
{
var state = Ext.state.Manager.get(stateId);
if (state && state.columns)
{
stateSorters = state.storeState ? state.storeState.sorters : null;
}
}
}
else
{
stateSorters = state.storeState.sorters;
}
if (Ext.Object.isEmpty(stateSorters))
{
// Loop on columns to find all with defaultSort or first sortable
var defaultSorters = [];
var firstSortable = null;
Ext.Array.each(columns, function(column) {
if (column.defaultSorter)
{
defaultSorters.push({
property: column.dataIndex,
direction: column.defaultSorter == 'DESC' ? 'DESC' : 'ASC'
});
}
else if (defaultSorters.length == 0 && !firstSortable && column.sortable && !column.hidden)
{
firstSortable = {
property: column.dataIndex,
direction: 'ASC'
};
}
});
return defaultSorters.length > 0 ? defaultSorters : (firstSortable != null ? [firstSortable] : []);
}
else
{
// Filter stateSorters as it can reference unexisting columns
var columnIds = Ext.Array.map(columns, function(column) {return column.dataIndex;});
return stateSorters.filter(function(stateSorter) {return Ext.Array.contains(columnIds, stateSorter.property);});
}
},
/**
* Return columns configuration from JSON definition
* @param {Object[]} data The columns definition
* @param {Boolean} withEditor True to enable edition in the columns (then, an editor will be tried to be built from data object).
* @param {Ext.grid.Panel} [grid] The grid to get state informations
* @param {Boolean} [forceDataOrder=false] True to make data with a higher priority on state
* @param {Object} [state] A state (in the Ext.state.Stateful vocabulary) of the grid
* @return {Object[]} The columns configuration
*/
getColumnsFromJson: function (data, withEditor, grid, forceDataOrder, state)
{
var stateColumns;
if (!state || Ext.Object.isEmpty(state))
{
var stateId = grid && grid.stateful ? grid.getStateId() : null;
if (stateId)
{
var state = Ext.state.Manager.get(stateId);
if (state && state.columns)
{
stateColumns = state.columns;
}
}
}
else
{
stateColumns = state.columns || {};
}
var columns = [];
var handledColumns = [];
if (!forceDataOrder)
{
// Handle stated
for (var o in stateColumns)
{
var id = stateColumns[o].id;
var column = this.getColumn(id, data);
if (column)
{
columns.push(this.getColumnFromJson(id, column, stateColumns[o], withEditor));
handledColumns.push(id);
}
}
}
// Handle others
for (var i = 0; i < data.length; i++)
{
var id = data[i].path;
if (!Ext.Array.contains(handledColumns, id))
{
var stateCol = this.getColumn(id, stateColumns);
// stateCol can be null for non-stated columns.
columns.push(this.getColumnFromJson(id, data[i], stateCol, withEditor));
}
}
return columns;
},
/**
* @private
* Find the column with a particular id
* @param {String} id The id to seek
* @param {Object} columns A non-null object where to seek in. This object contains object with "path" property.
* @return {Object} The found object or null
*/
getColumn: function(id, columns)
{
for (var i in columns)
{
if (columns[i].path == id)
{
return columns[i];
}
}
return null;
},
/**
* Return a column configuration from a definition described in JSON
* @param {String} id The data index of the column
* @param {Object} data The field definition
* @param {Object} state A state (in the Ext.state.Stateful vocabulary) of the grid
* @param {Boolean} withEditor True to enable edition in the column (then, an editor will be tried to be built from data object).
* @return {Object} The configuration object
* @private
*/
getColumnFromJson: function(id, data, state, withEditor)
{
var cfg = {
stateId: id,
headerId: id, // FIXME workaround for https://issues.ametys.org/browse/CMS-9008 (see https://www.sencha.com/forum/showthread.php?469623-ExtJS-6-5-3-Grid-reconfigure-methods-generates-a-warning&p=1317295#post1317295)
// columnPropId: id,
header: data.label || data.header,
width: state && state.width ? state.width : (data.width || (data.subColumns ? undefined : 100)),
flex: state ? state.flex : null,
sortable: Ext.isBoolean(data.sortable) ? data.sortable : true,
defaultSorter: data.defaultSorter,
// Check if state.hidden is defined, as otherwise hidden columns from data side will appear
hidden: (state && state.hidden !== undefined ) ? state.hidden : (Ext.isBoolean(data.hidden) ? data.hidden : false),
locked: state ? (state.locked ? state.locked : false) : (false),
// Group header does not accept a dataIndex
dataIndex: data.subColumns ? undefined : id,
// we need to store enumeration, type and multiple into initial config to be able to save and reapply column formatting
type: data.type,
multiple: data.multiple,
enumeration: data.enumeration,
groupable: !data.multiple,
validation: data.validation,
filter: data.filter
};
if (withEditor)
{
cfg.editor = this.getEditorFromJson(data);
}
// we need to store rendererFn configuration to be able to save and reapply column formatting
if (data.renderer)
{
cfg.rendererFn = data.renderer;
}
else
{
let defaultRenderer = this._getDefaultRenderer(data, cfg);
if (defaultRenderer != null)
{
cfg.rendererFn = defaultRenderer;
}
}
cfg.renderer = this.getRenderer(cfg);
if (data.subColumns)
{
cfg.columns = data.subColumns.map(element => this.getColumnFromJson(element.id, element, state ? state.columns[element.id] : null, withEditor));
}
return cfg;
},
/**
* Return a rendering function based on the initial config of an ExtJS column build from a search model
* @param {Object} cfg The config of an Ext.grid.column.Column
* @param {String} [cfg.rendererFn] The name of the function used to render a single value. Can be null in the case of an enumeration
* @param {Object} [cfg.enumeration] The enumeration value if the column value is an enumerated
* @param {boolean} [cfg.multiple] true if the column value is multiple
* @param {String} [cfg.type] the element type of the value
* @return {Function} The rendering function
*/
getRenderer: function(cfg)
{
let renderer;
var enumeration = {};
if (cfg.enumeration)
{
for (var i = 0; i < cfg.enumeration.length; i++)
{
enumeration[cfg.enumeration[i].value] = cfg.enumeration[i].label;
}
if (!cfg.rendererFn)
{
// default renderer for enumerated data
cfg.rendererFn = 'Ametys.plugins.cms.search.SearchGridHelper.renderEnumeration';
}
}
if (cfg.rendererFn)
{
renderer = Ext.bind(Ametys.getFunctionByName(cfg.rendererFn), null, [cfg.dataIndex, enumeration], true);
}
if (cfg.multiple && cfg.type != 'COMPOSITE')
{
var singleValueRenderer = renderer;
var appendedArgs = [cfg.dataIndex, enumeration, singleValueRenderer];
renderer = Ext.bind(Ametys.getFunctionByName('Ametys.plugins.cms.search.SearchGridHelper.renderMultipleValue'), null, appendedArgs, true);
}
// CMS-10738 - If our renderers are Ext.binded, the final renderer function as no arguments
// But extjs considers that a good renderer is a renderer with at least 2 arguments
// Otherwise we are not called after a grid modification (unless when using the rowexpander plugin)
// wrap the renderer to indicate invalid values
let wrappedRenderer = renderer;
let me = this;
renderer = function(value, metaData, record) {
return me.renderValueError(me, value, metaData, record, wrappedRenderer, arguments)
};
return renderer;
},
/**
* @private
* Gets the default renderer
* @param {Object} data The field definition
* @param {Object} cfg The configuration object
* @return {String} The default renderer function name
*/
_getDefaultRenderer: function(data, cfg)
{
var defaultRenderer = data.rendererFn;
var type = data.type.toLowerCase();
switch (type) {
case 'string':
if (data.enumeration)
{
defaultRenderer = 'Ametys.plugins.cms.search.SearchGridHelper.renderEnumeration';
}
else
{
defaultRenderer = 'Ametys.grid.GridColumnHelper.renderWithHtmlEscaping';
}
break;
case 'multilingual-string':
defaultRenderer = 'Ametys.plugins.cms.search.SearchGridHelper.renderMultilingualString';
break;
case 'rich-text':
defaultRenderer = 'Ametys.plugins.cms.search.SearchGridHelper.renderRichText';
break;
case 'date':
defaultRenderer = 'Ametys.grid.GridColumnHelper.renderDate';
break;
case 'datetime':
defaultRenderer = 'Ametys.grid.GridColumnHelper.renderDateTime';
break;
case 'long':
cfg.xtype = 'numbercolumn';
cfg.format = data.format || '0';
break;
case 'double':
cfg.xtype = data.format || 'numbercolumn';
break;
case 'boolean':
cfg.xtype = data.format || 'booleancolumn';
defaultRenderer = 'Ametys.grid.GridColumnHelper.renderBooleanIcon';
break;
case 'geocode':
defaultRenderer = 'Ametys.plugins.cms.search.SearchGridHelper.renderGeocode';
break;
case 'content':
case 'sub_content':
defaultRenderer = 'Ametys.plugins.cms.search.SearchGridHelper.renderContent';
break;
case 'user':
defaultRenderer = 'Ametys.grid.GridColumnHelper.renderUser';
break;
case 'binary':
case 'file':
defaultRenderer = 'Ametys.plugins.cms.search.SearchGridHelper.renderFile';
break;
case 'reference':
defaultRenderer = 'Ametys.plugins.cms.search.SearchGridHelper.renderReference';
break;
default:
break;
}
return defaultRenderer;
},
/**
* Retrieves the properties that must persist when the column are in a
* stateful grid. These data can be lost when a grid is reconfigured. This
* method can be used in this case in order to force the changes to persist.
* @param {Ext.grid.column.Column} column The column
* @return {Object} The properties
*/
getStateColumnPropertiesToForce: function(column)
{
var p = {
hidden: column.isHidden(),
locked: column.locked || false
};
if (column.flex)
{
p.flex = column.flex;
}
if (column.width)
{
p.width = column.width;
}
return p;
},
/**
* Returns the editor configuration from a metadata definition described in XML
* @param {HTMLElement} data The metadata definition
* @return {Object} The editor configuration object
*/
getEditorFromJson: function (data)
{
if (!data.editable)
{
return null;
}
var widgetCfg = {
fieldLabel: data.label || data.header,
hideLabel: true,
// In grids, we want to let the user set a mandatory field to empty, event if he won't be able to save afterward
// allowBlank: !(data.validation ? data.validation.mandatory : false),
allowBlank: true, // true should be the default valeur but CMS-11988
multiple: data.multiple,
widget: data.widget,
contentType: data.contentType,
msgTarget: 'qtip',
ftype: data.type,
defaultValue: data['default-value'],
listeners: {
'focus': function() { this.validate() }
},
cls: 'ametys'
};
if (data.validation)
{
var validation = data.validation;
widgetCfg.regexp = validation.regexp || null;
if (validation.invalidText)
{
widgetCfg.invalidText = validation.invalidText;
}
if (validation.regexText)
{
widgetCfg.regexText = validation.regexText;
}
}
if (data.enumeration)
{
var enumeration = [];
var entries = data.enumeration;
for (var j=0; j < entries.length; j++)
{
enumeration.push([entries[j].value, entries[j].label]);
}
widgetCfg.enumeration = enumeration;
}
if (data['widget-params'])
{
widgetCfg = Ext.merge (widgetCfg, data['widget-params']);
}
var xtype = Ametys.form.WidgetManager.getWidgetXType (data.widget, data.type.toLowerCase(), data.enumeration && data.enumeration.length > 0, data.multiple);
return Ext.apply (widgetCfg, {xtype: xtype});
},
_getParentRecord: function(record)
{
if (!record)
{
return null;
}
return record.parentRecord
|| (record.store || record.getTreeStore()).parentRecord;
},
_getParentMetadataPath: function(record)
{
if (!record)
{
return null;
}
return record.parentMetadataPath
|| (record.store || record.getTreeStore()).parentMetadataPath;
},
_getRootRecord: function(record)
{
let rootRecord = record;
let parentPath = "";
while (this._getParentRecord(rootRecord))
{
parentPath += (parentPath ? "/" : "") + this._getParentMetadataPath(rootRecord);
rootRecord = this._getParentRecord(rootRecord);
}
return {
record: rootRecord,
parentPath: parentPath
}
},
/**
* Check if the value in the given record is valid for the given column
* @param {Object} column the column configuration (containing validation info)
* @param {Object} record The record
* @param {Object} recordFields The record fields
* @param {String} parentMetadataPath When in a sub grid, the parent metadatapath
* @param {Boolean} notEditableData If not editable at all
* @param {String[]} notEditableDataIndex The list of absolute metadata path that cannot be edited
* @return {Boolean} true if the value is valid, false otherwise
*/
isValidValue: function(column, record, parentMetadataPath, notEditableData, notEditableDataIndex)
{
if (!column.editor && !column.editable && column.config && !column.config.editor)
{
return true;
}
if (record.isModel)
{
let root = this._getRootRecord(record)
rootRecord = root.record;
parentMetadataPath = root.parentPath;
notEditableData = rootRecord.get('notEditableData');
notEditableDataIndex = rootRecord.get('notEditableDataIndex');
}
if (notEditableData === undefined)
{
notEditableData = record.get('notEditableData') || null;
}
if (notEditableDataIndex === undefined)
{
notEditableDataIndex = record.get('notEditableDataIndex') || null;
}
if (notEditableData)
{
return true;
}
let data = record.data;
let fields = record.fields;
let dataIndex = column.dataIndex || column.path || column.name;
if (column.validation)
{
if (column.multiple)
{
let hasInvalidValue = false;
let me = this;
let values = this._getValuesForMultipleData(dataIndex, data);
if (column.validation.mandatory && (!values || values.length ==0))
{
return false;
}
values.forEach(function (value) {
if (!me._isSingleValueValid(column.validation, value))
{
hasInvalidValue = true;
return;
}
});
if (hasInvalidValue)
{
return false;
}
}
else
{
let value = data[dataIndex];
if (notEditableDataIndex && parentMetadataPath)
{
let cursor = parentMetadataPath;
while (true)
{
if (notEditableDataIndex.includes(cursor))
{
return true;
}
let i = cursor.lastIndexOf('/');
if (i == -1)
{
break;
}
cursor = cursor.substring(0, i);
}
}
if ((!notEditableDataIndex || !notEditableDataIndex.includes((parentMetadataPath ? parentMetadataPath + "/" : "") + dataIndex))
&& !this._evaluateDisableCondition(record, dataIndex)
&& !this._isSingleValueValid(column.validation, value))
{
return false;
}
}
}
if((column.ftype || column.type) == 'repeater')
{
for (let entry of data[dataIndex + "_repeater"].entries)
{
let f = fields.filter(f => (f.path || f.name) == dataIndex)[0];
let columns = f.subcolumns || f.columns;
for (let subcolumn of columns)
{
if (!this.isValidValue(
subcolumn,
{
data: entry.values,
parentMetadataPath: (parentMetadataPath ? parentMetadataPath + "/" : "") + dataIndex,
parentRecord: record,
fields: columns,
getField: function(d){return this.fields.filter(f => (f.path || f.name) == d)[0];},
get: function(d){return this.data[d];}
},
(parentMetadataPath ? parentMetadataPath + "/" : "") + dataIndex,
notEditableData,
notEditableDataIndex
))
{
return false;
}
}
}
}
return true;
},
/**
* Check if the given single value is valid
* @param {Object} validation the validation info
* @param {Object} value the value to validate
* @return {Boolean} true if the value is valid, false otherwise
*/
_isSingleValueValid: function(validation, value)
{
let isBlank = Ext.isEmpty(value) || Ext.isObject(value) && Ext.Object.isEmpty(value);
if (isBlank)
{
if (validation.mandatory)
{
return false;
}
}
else if (validation.regexp)
{
let regexp = new RegExp(validation.regexp);
if (Ext.isString(value) && !regexp.test(value) // For string type
|| Ext.isObject(value) && Ext.isString(value.value) && !regexp.test(value.value)) // For reference type
{
return false;
}
}
return true;
},
/**
* Retrieves the values at the given data index in the record for a multiple data
* This method always retrieves an array
* @param {String} dataIndex the data index
* @param {Object[]} data the record data
* @return {Array} the values of the multiple data
*/
_getValuesForMultipleData: function(dataIndex, data)
{
let value = data[dataIndex];
let values = data[dataIndex + "_values"];
if (Ext.isArray(values))
{
return values;
}
else if (Ext.isArray(value))
{
return value;
}
else
{
return[value];
}
}
});
Ametys.plugins.cms.search.SearchGridHelper.renderContent = Ametys.plugins.cms.search.SearchGridHelper.createRenderContent();