[IMP] website, web_editor: add new chart snippet
Introducing a new snippet called Chart. This snippet uses the lib https://www.chartjs.org/ 6 types of graph are possible: Bar vertical Bar horizontal Line Pie Doughnut Radar task-2092267 closes odoo/odoo#40051 Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com>
This commit is contained in:
@@ -158,16 +158,32 @@ const ColorPaletteWidget = Widget.extend({
|
||||
return ColorpickerDialog.normalizeCSSColor(el.style.backgroundColor);
|
||||
})
|
||||
));
|
||||
this.trigger_up('get_custom_colors', {
|
||||
onSuccess: (colors) => {
|
||||
colors.forEach(color => {
|
||||
this._addCustomColor(existingColors, color);
|
||||
});
|
||||
},
|
||||
});
|
||||
_.each(this.options.$editable.find('[style*="color"]'), el => {
|
||||
for (const colorProp of ['color', 'backgroundColor']) {
|
||||
const color = ColorpickerDialog.normalizeCSSColor(el.style[colorProp]);
|
||||
if (color && !existingColors.has(color)) {
|
||||
this._addCustomColorButton(color);
|
||||
existingColors.add(color);
|
||||
}
|
||||
this._addCustomColor(existingColors, el.style[colorProp]);
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Add the color to the custom color section if it is not in the existingColors.
|
||||
*
|
||||
* @param {string[]} existingColors Colors currently in the colorpicker
|
||||
* @param {string} color Color to add to the cuustom colors
|
||||
*/
|
||||
_addCustomColor: function (existingColors, color) {
|
||||
const normColor = ColorpickerDialog.normalizeCSSColor(color);
|
||||
if (color && !existingColors.has(normColor)) {
|
||||
this._addCustomColorButton(normColor);
|
||||
existingColors.add(normColor);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Add a custom button in the coresponding section.
|
||||
*
|
||||
|
||||
@@ -408,6 +408,47 @@ body.editor_enable.editor_has_snippets {
|
||||
}
|
||||
}
|
||||
|
||||
// Chart Snippet
|
||||
we-matrix {
|
||||
overflow-y: auto;
|
||||
|
||||
table {
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
|
||||
td, th {
|
||||
text-align: center;
|
||||
we-button {
|
||||
display: inline-block;
|
||||
color: white;
|
||||
height: 100%;
|
||||
|
||||
&.o_we_matrix_remove_col, &.o_we_matrix_remove_row {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
input {
|
||||
border: 1px solid var(--o-we-bg-color-dark);
|
||||
background-color: var(--o-we-bg-color-dark);
|
||||
color: var(--o-we-color);
|
||||
font-size: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
&:last-child {
|
||||
width: 28px;
|
||||
}
|
||||
}
|
||||
&.o_we_matrix_five_col {
|
||||
width: auto;
|
||||
td, th {
|
||||
input {
|
||||
width: 43px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Layout Utils
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 571 B |
@@ -1561,6 +1561,130 @@ registry.countdown = publicWidget.Widget.extend({
|
||||
},
|
||||
});
|
||||
|
||||
registry.chart = publicWidget.Widget.extend({
|
||||
selector: '.s_chart',
|
||||
disabledInEditableMode: false,
|
||||
jsLibs: [
|
||||
'/web/static/lib/Chart/Chart.js',
|
||||
],
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @param {Object} parent
|
||||
* @param {Object} options The default value of the chartbar.
|
||||
*/
|
||||
init: function (parent, options) {
|
||||
this._super.apply(this, arguments);
|
||||
this.style = window.getComputedStyle(document.documentElement);
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
start: function () {
|
||||
// Convert Theme colors to css color
|
||||
const data = JSON.parse(this.el.dataset.data);
|
||||
data.datasets.forEach(el => {
|
||||
if (Array.isArray(el.backgroundColor)) {
|
||||
el.backgroundColor = el.backgroundColor.map(el => this._convertToCssColor(el));
|
||||
el.borderColor = el.borderColor.map(el => this._convertToCssColor(el));
|
||||
} else {
|
||||
el.backgroundColor = this._convertToCssColor(el.backgroundColor);
|
||||
el.borderColor = this._convertToCssColor(el.borderColor);
|
||||
}
|
||||
el.borderWidth = this.el.dataset.borderWidth;
|
||||
});
|
||||
|
||||
// Make chart data
|
||||
const chartData = {
|
||||
type: this.el.dataset.type,
|
||||
data: data,
|
||||
options: {
|
||||
legend: {
|
||||
display: this.el.dataset.legendPosition !== 'none',
|
||||
position: this.el.dataset.legendPosition,
|
||||
},
|
||||
tooltips: {
|
||||
enabled: this.el.dataset.tooltipDisplay === 'true',
|
||||
},
|
||||
title: {
|
||||
display: !!this.el.dataset.title,
|
||||
text: this.el.dataset.title,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Add type specific options
|
||||
if (this.el.dataset.type === 'radar') {
|
||||
chartData.options.scale = {
|
||||
ticks: {
|
||||
beginAtZero: true,
|
||||
}
|
||||
};
|
||||
} else if (['pie', 'doughnut'].includes(this.el.dataset.type)) {
|
||||
chartData.options.tooltips.callbacks = {
|
||||
label: (tooltipItem, data) => {
|
||||
const label = data.datasets[tooltipItem.datasetIndex].label;
|
||||
const secondLabel = data.labels[tooltipItem.index];
|
||||
let final = label;
|
||||
if (label) {
|
||||
if (secondLabel) {
|
||||
final = label + ' - ' + secondLabel;
|
||||
}
|
||||
} else if (secondLabel) {
|
||||
final = secondLabel;
|
||||
}
|
||||
return final + ':' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
|
||||
},
|
||||
};
|
||||
} else {
|
||||
chartData.options.scales = {
|
||||
xAxes: [{
|
||||
stacked: this.el.dataset.stacked === 'true',
|
||||
ticks: {
|
||||
beginAtZero: true
|
||||
},
|
||||
}],
|
||||
yAxes: [{
|
||||
stacked: this.el.dataset.stacked === 'true',
|
||||
ticks: {
|
||||
beginAtZero: true
|
||||
},
|
||||
}],
|
||||
};
|
||||
}
|
||||
const canvas = this.el.querySelector('canvas');
|
||||
this.chart = new window.Chart(canvas, chartData);
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
* Discard all library changes to reset the state of the Html.
|
||||
*/
|
||||
destroy: function () {
|
||||
if (this.chart) { // The widget can be destroyed before start has completed
|
||||
this.chart.destroy();
|
||||
this.el.querySelectorAll('.chartjs-size-monitor').forEach(el => el.remove());
|
||||
}
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {string} color A css color or theme color string
|
||||
* @returns {string} Css color
|
||||
*/
|
||||
_convertToCssColor: function (color) {
|
||||
if (!color) {
|
||||
return 'transparent';
|
||||
}
|
||||
return this.style.getPropertyValue(`--${color}`).trim() || color;
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
Widget: publicWidget.Widget,
|
||||
Animation: Animation,
|
||||
|
||||
@@ -2,6 +2,7 @@ odoo.define('website.editor.snippets.options', function (require) {
|
||||
'use strict';
|
||||
|
||||
var core = require('web.core');
|
||||
const ColorpickerDialog = require('web.ColorpickerDialog');
|
||||
var Dialog = require('web.Dialog');
|
||||
var weWidgets = require('wysiwyg.widgets');
|
||||
const wUtils = require('website.utils');
|
||||
@@ -2072,6 +2073,459 @@ options.registry.SnippetMove = options.Class.extend({
|
||||
},
|
||||
});
|
||||
|
||||
options.registry.InnerChart = options.Class.extend({
|
||||
custom_events: _.extend({}, options.Class.prototype.custom_events, {
|
||||
'get_custom_colors': '_onGetCustomColors',
|
||||
}),
|
||||
events: _.extend({}, options.Class.prototype.events, {
|
||||
'click we-button.add_column': '_onAddColumnClick',
|
||||
'click we-button.add_row': '_onAddRowClick',
|
||||
'click we-button.o_we_matrix_remove_col': '_onRemoveColumnClick',
|
||||
'click we-button.o_we_matrix_remove_row': '_onRemoveRowClick',
|
||||
'blur we-matrix input': '_onMatrixInputFocusOut',
|
||||
'focus we-matrix input': '_onMatrixInputFocus',
|
||||
}),
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this.themeArray = ['alpha', 'beta', 'gamma', 'delta', 'epsilon'],
|
||||
this.style = window.getComputedStyle(document.documentElement);
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
start: function () {
|
||||
// Get the 2 color pickers to hide in updateUI
|
||||
this.backSelectEl = this.el.querySelector('we-select[data-attribute-name="backgroundColor"]');
|
||||
this.borderSelectEl = this.el.querySelector('we-select[data-attribute-name="borderColor"]');
|
||||
|
||||
// Build matrix content
|
||||
this.tableEl = this.el.querySelector('we-matrix table');
|
||||
const data = JSON.parse(this.$target[0].dataset.data);
|
||||
data.labels.forEach(el => {
|
||||
this._addRow(el);
|
||||
});
|
||||
data.datasets.forEach((el, i) => {
|
||||
if (this._isPieChart()) {
|
||||
// Add header colors in case the user changes the type of graph
|
||||
const headerBackgroundColor = this.themeArray[i] || this._randomColor();
|
||||
const headerBorderColor = this.themeArray[i] || this._randomColor();
|
||||
this._addColumn(el.label, el.data, headerBackgroundColor, headerBorderColor, el.backgroundColor, el.borderColor);
|
||||
} else {
|
||||
this._addColumn(el.label, el.data, el.backgroundColor, el.borderColor);
|
||||
}
|
||||
});
|
||||
this._displayRemoveColButton();
|
||||
this._displayRemoveRowButton();
|
||||
this._setDefaultSelectedInput();
|
||||
return this._super(...arguments);
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
updateUI: function () {
|
||||
// Selected input might not be in dom anymore if col/row removed
|
||||
// Done before _super because _computeWidgetState of colorChange
|
||||
if (!this.lastEditableSelectedInput.closest('table') || this.colorPaletteSelectedInput && !this.colorPaletteSelectedInput.closest('table')) {
|
||||
this._setDefaultSelectedInput();
|
||||
}
|
||||
this._super(...arguments);
|
||||
// prevent the columns from becoming too small.
|
||||
this.tableEl.classList.toggle('o_we_matrix_five_col', this.tableEl.querySelectorAll('tr:first-child th').length > 5);
|
||||
this.el.querySelector('[data-attribute-name="stacked"]').classList.toggle('d-none', !this._isStackableChart() || this._getColumnCount() === 1);
|
||||
// Disable color on inputs that don't use them
|
||||
const notDisplayed = !this.colorPaletteSelectedInput;
|
||||
this.backSelectEl.classList.toggle('d-none', notDisplayed);
|
||||
this.borderSelectEl.classList.toggle('d-none', notDisplayed);
|
||||
this.backSelectEl.querySelector('we-title').textContent = this._isPieChart() ? _t("Data Background Color") : _t("Dataset Background Color");
|
||||
this.borderSelectEl.querySelector('we-title').textContent = this._isPieChart() ? _t("Data Border Color") : _t("Dataset Border Color");
|
||||
// Dataset/Cell color
|
||||
this.tableEl.querySelectorAll('input').forEach(el => el.style.border = '');
|
||||
const selector = this._isPieChart() ? 'td input' : 'tr:first-child input';
|
||||
this.tableEl.querySelectorAll(selector).forEach(el => {
|
||||
const color = el.dataset.backgroundColor || el.dataset.borderColor;
|
||||
if (color) {
|
||||
el.style.border = '2px solid';
|
||||
el.style.borderColor = ColorpickerDialog.isCSSColor(color) ? color : this.style.getPropertyValue(`--${color}`).trim();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Options
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Set the color on the selected input.
|
||||
*/
|
||||
colorChange: function (previewMode, widgetValue, params) {
|
||||
if (widgetValue) {
|
||||
this.colorPaletteSelectedInput.dataset[params.attributeName] = widgetValue;
|
||||
} else {
|
||||
delete this.colorPaletteSelectedInput.dataset[params.attributeName];
|
||||
}
|
||||
this._reloadGraph();
|
||||
// To focus back the input that is edited we have to wait for the color picker to be fully reloaded.
|
||||
setTimeout(() => this.lastEditableSelectedInput.focus());
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
selectDataAttribute: function (previewMode, widgetValue, params) {
|
||||
this._super(...arguments);
|
||||
// Data might change if going from or to a pieChart.
|
||||
if (params.attributeName === 'type') {
|
||||
this._setDefaultSelectedInput();
|
||||
this._reloadGraph();
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_computeWidgetState: function (methodName, params) {
|
||||
if (methodName === 'colorChange') {
|
||||
return this.colorPaletteSelectedInput && this.colorPaletteSelectedInput.dataset[params.attributeName] || '';
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
/**
|
||||
* Sets and reloads the data on the canvas if it has changed.
|
||||
* Used in matrix related method.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_reloadGraph: function () {
|
||||
const jsonValue = this._matrixToChartData();
|
||||
if (this.$target[0].dataset.data !== jsonValue) {
|
||||
this.$target[0].dataset.data = jsonValue;
|
||||
this._refreshPublicWidgets();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Return a stringifyed chart.js data object from the matrix
|
||||
* Pie charts have one color per data while other charts have one color per dataset.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_matrixToChartData: function () {
|
||||
const data = {
|
||||
labels: [],
|
||||
datasets: [],
|
||||
};
|
||||
this.tableEl.querySelectorAll('tr:first-child input').forEach(el => {
|
||||
data.datasets.push({
|
||||
label: el.value || '',
|
||||
data: [],
|
||||
backgroundColor: this._isPieChart() ? [] : el.dataset.backgroundColor || '',
|
||||
borderColor: this._isPieChart() ? [] : el.dataset.borderColor || '',
|
||||
});
|
||||
});
|
||||
this.tableEl.querySelectorAll('tr:not(:first-child):not(:last-child)').forEach((el) => {
|
||||
const title = el.querySelector('th input').value || '';
|
||||
data.labels.push(title);
|
||||
el.querySelectorAll('td input').forEach((el, i) => {
|
||||
data.datasets[i].data.push(el.value || 0);
|
||||
if (this._isPieChart()) {
|
||||
data.datasets[i].backgroundColor.push(el.dataset.backgroundColor || '');
|
||||
data.datasets[i].borderColor.push(el.dataset.borderColor || '');
|
||||
}
|
||||
});
|
||||
});
|
||||
return JSON.stringify(data);
|
||||
},
|
||||
/**
|
||||
* Return a td containing a we-button with minus icon
|
||||
*
|
||||
* @param {...string} classes Classes to add to the we-button
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
_makeDeleteButton: function (...classes) {
|
||||
const rmbuttonEl = options.buildElement('we-button', null, {
|
||||
classes: ['fa', 'fa-fw', 'fa-minus', ...classes],
|
||||
});
|
||||
const newEl = document.createElement('td');
|
||||
newEl.appendChild(rmbuttonEl);
|
||||
return newEl;
|
||||
},
|
||||
/**
|
||||
* Add a column to the matrix
|
||||
* The th (dataset label) of a column hold the colors for the entire dataset if the graph is not a pie chart
|
||||
* If the graph is a pie chart the color of the td (data) are used.
|
||||
*
|
||||
* @private
|
||||
* @param {String} title The title of the column
|
||||
* @param {Array} values The values of the column input
|
||||
* @param {String} heardeBackgroundColor The background color of the dataset
|
||||
* @param {String} headerBorderColor The border color of the dataset
|
||||
* @param {string[]} cellBackgroundColors The background colors of the datas inputs, random color if missing
|
||||
* @param {string[]} cellBorderColors The border color of the datas inputs, no color if missing
|
||||
*/
|
||||
_addColumn: function (title, values, heardeBackgroundColor, headerBorderColor, cellBackgroundColors = [], cellBorderColors = []) {
|
||||
const firstRow = this.tableEl.querySelector('tr:first-child');
|
||||
const headerInput = this._makeCell('th', title, heardeBackgroundColor, headerBorderColor);
|
||||
firstRow.insertBefore(headerInput, firstRow.lastElementChild);
|
||||
|
||||
this.tableEl.querySelectorAll('tr:not(:first-child):not(:last-child)').forEach((el, i) => {
|
||||
const newCell = this._makeCell('td', values ? values[i] : null, cellBackgroundColors[i] || this._randomColor(), cellBorderColors[i - 1]);
|
||||
el.insertBefore(newCell, el.lastElementChild);
|
||||
});
|
||||
|
||||
const lastRow = this.tableEl.querySelector('tr:last-child');
|
||||
const removeButton = this._makeDeleteButton('o_we_matrix_remove_col');
|
||||
lastRow.appendChild(removeButton);
|
||||
},
|
||||
/**
|
||||
* Add a row to the matrix
|
||||
* The background color of the datas are random
|
||||
*
|
||||
* @private
|
||||
* @param {String} tilte The title of the row
|
||||
*/
|
||||
_addRow: function (tilte) {
|
||||
const trEl = document.createElement('tr');
|
||||
trEl.appendChild(this._makeCell('th', tilte));
|
||||
this.tableEl.querySelectorAll('tr:first-child input').forEach(() => {
|
||||
trEl.appendChild(this._makeCell('td', null, this._randomColor()));
|
||||
});
|
||||
trEl.appendChild(this._makeDeleteButton('o_we_matrix_remove_row'));
|
||||
const tbody = this.tableEl.querySelector('tbody');
|
||||
tbody.insertBefore(trEl, tbody.lastElementChild);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {string} tag tag of the HTML Element (td/th)
|
||||
* @param {string} value The current value of the cell input
|
||||
* @param {string} backgroundColor The background Color of the data on the graph
|
||||
* @param {string} borderColor The border Color of the the data on the graph
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
_makeCell: function (tag, value, backgroundColor, borderColor) {
|
||||
const newEl = document.createElement(tag);
|
||||
const contentEl = document.createElement('input');
|
||||
contentEl.type = 'text';
|
||||
contentEl.value = value || '';
|
||||
if (backgroundColor) {
|
||||
contentEl.dataset.backgroundColor = backgroundColor;
|
||||
}
|
||||
if (borderColor) {
|
||||
contentEl.dataset.borderColor = borderColor;
|
||||
}
|
||||
newEl.appendChild(contentEl);
|
||||
return newEl;
|
||||
},
|
||||
/**
|
||||
* Display the remove button coresponding to the colIndex
|
||||
*
|
||||
* @private
|
||||
* @param {Int} colIndex Can be undefined, if so the last remove button of the column will be shown
|
||||
*/
|
||||
_displayRemoveColButton: function (colIndex) {
|
||||
if (this._getColumnCount() > 1) {
|
||||
this._displayRemoveButton(colIndex, 'o_we_matrix_remove_col');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Display the remove button coresponding to the rowIndex
|
||||
*
|
||||
* @private
|
||||
* @param {Int} rowIndex Can be undefined, if so the last remove button of the row will be shown
|
||||
*/
|
||||
_displayRemoveRowButton: function (rowIndex) {
|
||||
//Nbr of row minus header and button
|
||||
const rowCount = this.tableEl.rows.length - 2;
|
||||
if (rowCount > 1) {
|
||||
this._displayRemoveButton(rowIndex, 'o_we_matrix_remove_row');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Int} tdIndex Can be undefined, if so the last remove button will be shown
|
||||
* @param {String} btnClass Either o_we_matrix_remove_col or o_we_matrix_remove_row
|
||||
*/
|
||||
_displayRemoveButton: function (tdIndex, btnClass) {
|
||||
const removeBtn = this.tableEl.querySelectorAll(`td we-button.${btnClass}`);
|
||||
removeBtn.forEach(el => el.style.display = ''); //hide all
|
||||
const index = tdIndex < removeBtn.length ? tdIndex : removeBtn.length - 1;
|
||||
removeBtn[index].style.display = 'inline-block';
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @return {boolean}
|
||||
*/
|
||||
_isPieChart: function () {
|
||||
return ['pie', 'doughnut'].includes(this.$target[0].dataset.type);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @return {boolean}
|
||||
*/
|
||||
_isStackableChart: function () {
|
||||
return ['bar', 'horizontalBar'].includes(this.$target[0].dataset.type);
|
||||
},
|
||||
/**
|
||||
* Return the number of column minus header and button
|
||||
* @private
|
||||
* @return {integer}
|
||||
*/
|
||||
_getColumnCount: function () {
|
||||
return this.tableEl.rows[0].cells.length - 2;
|
||||
},
|
||||
/**
|
||||
* Select the first data input
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_setDefaultSelectedInput: function () {
|
||||
this.lastEditableSelectedInput = this.tableEl.querySelector('td input');
|
||||
if (this._isPieChart()) {
|
||||
this.colorPaletteSelectedInput = this.lastEditableSelectedInput;
|
||||
} else {
|
||||
this.colorPaletteSelectedInput = this.tableEl.querySelector('th input');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Return a random hexadecimal color.
|
||||
*
|
||||
* @private
|
||||
* @return {string}
|
||||
*/
|
||||
_randomColor: function () {
|
||||
return '#' + ('00000' + (Math.random() * (1 << 24) | 0).toString(16)).slice(-6).toUpperCase();
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Used by colorPalette to retrieve the custom colors used on the chart
|
||||
* Make an array with all the custom colors used on the chart
|
||||
* and apply it to the onSuccess method provided by the trigger_up.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_onGetCustomColors: function (ev) {
|
||||
const data = JSON.parse(this.$target[0].dataset.data || '');
|
||||
let customColors = [];
|
||||
data.datasets.forEach(el => {
|
||||
if (this._isPieChart()) {
|
||||
customColors = customColors.concat(el.backgroundColor).concat(el.borderColor);
|
||||
} else {
|
||||
customColors.push(el.backgroundColor);
|
||||
customColors.push(el.borderColor);
|
||||
}
|
||||
});
|
||||
customColors = customColors.filter((el, i, array) => {
|
||||
return !this.style.getPropertyValue(`--${el}`) && array.indexOf(el) === i && el !== ''; // unique non class not transparent
|
||||
});
|
||||
ev.data.onSuccess(customColors);
|
||||
},
|
||||
/**
|
||||
* Add a row at the end of the matrix and display it's remove button
|
||||
* Choose the color of the column from the theme array or a random color if they are already used
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_onAddColumnClick: function () {
|
||||
const usedColor = Array.from(this.tableEl.querySelectorAll('tr:first-child input')).map(el => el.dataset.backgroundColor);
|
||||
const color = this.themeArray.filter(el => !usedColor.includes(el))[0] || this._randomColor();
|
||||
this._addColumn(null, null, color, color);
|
||||
this._reloadGraph();
|
||||
this._displayRemoveColButton();
|
||||
this.updateUI();
|
||||
},
|
||||
/**
|
||||
* Add a column at the end of the matrix and display it's remove button
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_onAddRowClick: function () {
|
||||
this._addRow();
|
||||
this._reloadGraph();
|
||||
this._displayRemoveRowButton();
|
||||
this.updateUI();
|
||||
},
|
||||
/**
|
||||
* Remove the column and show the remove button of the next column or the last if no next.
|
||||
*
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onRemoveColumnClick: function (ev) {
|
||||
const cell = ev.currentTarget.parentElement;
|
||||
const cellIndex = cell.cellIndex;
|
||||
this.tableEl.querySelectorAll('tr').forEach((el) => {
|
||||
el.children[cellIndex].remove();
|
||||
});
|
||||
this._displayRemoveColButton(cellIndex - 1);
|
||||
this._reloadGraph();
|
||||
this.updateUI();
|
||||
},
|
||||
/**
|
||||
* Remove the row and show the remove button of the next row or the last if no next.
|
||||
*
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onRemoveRowClick: function (ev) {
|
||||
const row = ev.currentTarget.parentElement.parentElement;
|
||||
const rowIndex = row.rowIndex;
|
||||
row.remove();
|
||||
this._displayRemoveRowButton(rowIndex - 1);
|
||||
this._reloadGraph();
|
||||
this.updateUI();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onMatrixInputFocusOut: function (ev) {
|
||||
// Sometimes, an input is focusout for internal reason (like an undo
|
||||
// recording) then focused again manually in the same JS stack
|
||||
// execution. In that case, the blur should not trigger an option
|
||||
// selection as the user did not leave the input. We thus defer the blur
|
||||
// handling to then check that the target is indeed still blurred before
|
||||
// executing the actual option selection.
|
||||
setTimeout(() => {
|
||||
if (ev.currentTarget === document.activeElement) {
|
||||
return;
|
||||
}
|
||||
this._reloadGraph();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Set the selected cell/header and display the related remove button
|
||||
*
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onMatrixInputFocus: function (ev) {
|
||||
this.lastEditableSelectedInput = ev.target;
|
||||
const col = ev.target.parentElement.cellIndex;
|
||||
const row = ev.target.parentElement.parentElement.rowIndex;
|
||||
if (this._isPieChart()) {
|
||||
this.colorPaletteSelectedInput = ev.target.parentNode.tagName === 'TD' ? ev.target : null;
|
||||
} else {
|
||||
this.colorPaletteSelectedInput = this.tableEl.querySelector(`tr:first-child th:nth-of-type(${col + 1}) input`);
|
||||
}
|
||||
if (col > 0) {
|
||||
this._displayRemoveColButton(col - 1);
|
||||
}
|
||||
if (row > 0) {
|
||||
this._displayRemoveRowButton(row - 1);
|
||||
}
|
||||
this.updateUI();
|
||||
},
|
||||
});
|
||||
|
||||
const InputUserValueWidget = options.userValueWidgetsRegistry['we-input'];
|
||||
const UrlPickerUserValueWidget = InputUserValueWidget.extend({
|
||||
custom_events: _.extend({}, InputUserValueWidget.prototype.custom_events || {}, {
|
||||
|
||||
@@ -747,6 +747,24 @@
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<template id="s_chart" name="Chart">
|
||||
<div class="s_chart" data-type="bar" data-legend-position="top" data-tooltip-display="true" data-stacked="false" data-border-width="2"
|
||||
data-data="{
|
||||
"labels":["First","Second","Third","Fourth","Fifth"],
|
||||
"datasets":[
|
||||
{
|
||||
"label":"One",
|
||||
"data":["12","24","18","17","10"],
|
||||
"backgroundColor":"alpha",
|
||||
"borderColor":"alpha"
|
||||
}
|
||||
]
|
||||
}">
|
||||
<h2>A Chart Title</h2>
|
||||
<canvas/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="s_parallax" name="Parallax">
|
||||
<section class="s_parallax parallax s_parallax_is_fixed bg-black-50 pt160 pb160" data-scroll-background-ratio="1">
|
||||
<span class="s_parallax_bg oe_img_bg oe_custom_bg" style="background-image: url('/web/image/website.s_parallax_default_image'); background-position: 50% 75%;"/>
|
||||
@@ -965,6 +983,7 @@
|
||||
<t t-snippet="website.s_share" t-thumbnail="/website/static/src/img/snippets_thumbs/s_share.png"/>
|
||||
<t t-snippet="website.s_facebook_page" t-thumbnail="/website/static/src/img/snippets_thumbs/s_facebook_page.png"/>
|
||||
<t t-snippet="website.s_rating" t-thumbnail="/website/static/src/img/snippets_thumbs/s_rating.png"/>
|
||||
<t t-snippet="website.s_chart" t-thumbnail="/website/static/src/img/snippets_thumbs/s_chart.png"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1345,8 +1364,8 @@
|
||||
|
||||
<!-- Main content drop -->
|
||||
<div id="so_content_addition"
|
||||
data-selector="blockquote, .s_btn, .s_card, .s_alert, .o_facebook_page, .s_share, .s_rating, .s_hr, .s_countdown"
|
||||
data-drop-near="p, h1, h2, h3, blockquote, .s_btn, .s_card, .s_alert, .o_facebook_page, .s_share, .s_rating, .s_hr"
|
||||
data-selector="blockquote, .s_btn, .s_card, .s_alert, .o_facebook_page, .s_share, .s_rating, .s_hr, .s_countdown, .s_chart"
|
||||
data-drop-near="p, h1, h2, h3, blockquote, .s_btn, .s_card, .s_alert, .o_facebook_page, .s_share, .s_rating, .s_hr, .s_chart"
|
||||
data-drop-in=".content, nav"/>
|
||||
|
||||
<div data-js="ul"
|
||||
@@ -1494,5 +1513,51 @@
|
||||
</we-select>
|
||||
<we-colorpicker string="Progress Bar Color" data-attribute-name="progressBarColor" data-select-data-attribute=""/>
|
||||
</div>
|
||||
|
||||
<!-- Chart Inner -->
|
||||
<div data-js="InnerChart" string="Chart"
|
||||
data-selector=".s_chart">
|
||||
<we-colorpicker string="Background Color"
|
||||
data-select-style="true"
|
||||
data-css-property="background-color"
|
||||
data-color-prefix="bg-"/>
|
||||
<we-select string="Type" data-attribute-name="type" data-attribute-default-value="bar" data-no-preview="true">
|
||||
<we-button data-select-data-attribute="bar">Bar Vertical</we-button>
|
||||
<we-button data-select-data-attribute="horizontalBar">Bar Horizontal</we-button>
|
||||
<we-button data-select-data-attribute="line">Line</we-button>
|
||||
<we-button data-select-data-attribute="pie">Pie</we-button>
|
||||
<we-button data-select-data-attribute="doughnut">Doughnut</we-button>
|
||||
<we-button data-select-data-attribute="radar">Radar</we-button>
|
||||
</we-select>
|
||||
<we-checkbox string="Stacked" data-select-data-attribute="false|true" data-attribute-name="stacked" data-no-preview="true"/>
|
||||
<we-select string="Legend" data-attribute-name="legendPosition" data-attribute-default-value="top" data-no-preview="true">
|
||||
<we-button data-select-data-attribute="none">None</we-button>
|
||||
<we-button data-select-data-attribute="top">Top</we-button>
|
||||
<we-button data-select-data-attribute="left">Left</we-button>
|
||||
<we-button data-select-data-attribute="bottom">Bottom</we-button>
|
||||
<we-button data-select-data-attribute="right">Right</we-button>
|
||||
</we-select>
|
||||
<we-checkbox string="Tooltip" data-select-data-attribute="false|true" data-attribute-name="tooltipDisplay" data-no-preview="true"/>
|
||||
<we-matrix>
|
||||
<table>
|
||||
<tr>
|
||||
<th/>
|
||||
<th><we-button class="fa fa-fw fa-plus add_column"/></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><we-button class="fa fa-fw fa-plus add_row"/></th>
|
||||
</tr>
|
||||
</table>
|
||||
</we-matrix>
|
||||
<we-colorpicker string="Background"
|
||||
data-color-change=""
|
||||
data-attribute-name="backgroundColor"
|
||||
data-no-preview="true"/>
|
||||
<we-colorpicker string="Border"
|
||||
data-color-change=""
|
||||
data-attribute-name="borderColor"
|
||||
data-no-preview="true"/>
|
||||
<we-input class="o_we_inline" string="Border Width" data-select-data-attribute="2px" data-attribute-name="borderWidth" data-unit="px"/>
|
||||
</div>
|
||||
</template>
|
||||
</odoo>
|
||||
|
||||
Reference in New Issue
Block a user