diff --git a/package-lock.json b/package-lock.json index aacd3451..02dff033 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,21 @@ { "name": "dash-ag-grid", - "version": "31.0.1rc3", + "version": "31.0.1rc5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "dash-ag-grid", - "version": "31.0.1rc3", + "version": "31.0.1rc5", "license": "MIT", "dependencies": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.14.14", "@mui/material": "^5.14.14", - "ag-grid-community": "^31.0.1", - "ag-grid-enterprise": "^31.0.1", - "ag-grid-react": "^31.0.1", + "ag-grid-community": "^31.0.2", + "ag-grid-enterprise": "^31.0.2", + "ag-grid-react": "^31.0.2", "d3-format": "^3.1.0", "d3-time": "^3.1.0", "d3-time-format": "^4.1.0", @@ -3056,24 +3056,24 @@ } }, "node_modules/ag-grid-community": { - "version": "31.0.1", - "resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-31.0.1.tgz", - "integrity": "sha512-RZQlW1DTOJHsUR/tnbnTJQKgAnDlHi05YYyTe5AgNor/1TlX1hoYdcqrGsJjvcHQgTjeEgzWOL0yf+KcqXZzxg==" + "version": "31.0.2", + "resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-31.0.2.tgz", + "integrity": "sha512-gxUdHeAZUV2TDHqnDax5QSQgUxIvJ1zaFxUuPzcfiiPwbN6btz6kxg/KNrDfEjQi70JBfJV46BMR9KTG6iAVmQ==" }, "node_modules/ag-grid-enterprise": { - "version": "31.0.1", - "resolved": "https://registry.npmjs.org/ag-grid-enterprise/-/ag-grid-enterprise-31.0.1.tgz", - "integrity": "sha512-VAL6np/dDBywZSOcIyssSj9IEzN+FsKSOrDD5V97P7fiPLE2RWNPuaj/5cLOUuuelUpffu6KK2/9U6KFBVmXoA==", + "version": "31.0.2", + "resolved": "https://registry.npmjs.org/ag-grid-enterprise/-/ag-grid-enterprise-31.0.2.tgz", + "integrity": "sha512-KVtlvc8kcZVs4nRozUM0Ir7sd1W8I0JqSRSRhScob5lEE5aQnuYfy7LF8ZSic/HYLqZMBMDDsVfX8EfjkWfLlw==", "dependencies": { - "ag-grid-community": "~31.0.1" + "ag-grid-community": "~31.0.2" } }, "node_modules/ag-grid-react": { - "version": "31.0.1", - "resolved": "https://registry.npmjs.org/ag-grid-react/-/ag-grid-react-31.0.1.tgz", - "integrity": "sha512-9nmYPsgH1YUDUDOTiyaFsysoNAx/y72ovFJKuOffZC1V7OrQMadyP6DbqGFWCqzzoLJOY7azOr51dDQzAIXLpw==", + "version": "31.0.2", + "resolved": "https://registry.npmjs.org/ag-grid-react/-/ag-grid-react-31.0.2.tgz", + "integrity": "sha512-QAC5Rsr4AaE+vCafyrnEJa7JWcmoXNzJyzZmln0T6QCLG43IuT2hZ3ULmce/IqwP2Kxf+B4hVJOVitVT9SdXkg==", "dependencies": { - "ag-grid-community": "~31.0.1", + "ag-grid-community": "~31.0.2", "prop-types": "^15.8.1" }, "peerDependencies": { @@ -12875,24 +12875,24 @@ "requires": {} }, "ag-grid-community": { - "version": "31.0.1", - "resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-31.0.1.tgz", - "integrity": "sha512-RZQlW1DTOJHsUR/tnbnTJQKgAnDlHi05YYyTe5AgNor/1TlX1hoYdcqrGsJjvcHQgTjeEgzWOL0yf+KcqXZzxg==" + "version": "31.0.2", + "resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-31.0.2.tgz", + "integrity": "sha512-gxUdHeAZUV2TDHqnDax5QSQgUxIvJ1zaFxUuPzcfiiPwbN6btz6kxg/KNrDfEjQi70JBfJV46BMR9KTG6iAVmQ==" }, "ag-grid-enterprise": { - "version": "31.0.1", - "resolved": "https://registry.npmjs.org/ag-grid-enterprise/-/ag-grid-enterprise-31.0.1.tgz", - "integrity": "sha512-VAL6np/dDBywZSOcIyssSj9IEzN+FsKSOrDD5V97P7fiPLE2RWNPuaj/5cLOUuuelUpffu6KK2/9U6KFBVmXoA==", + "version": "31.0.2", + "resolved": "https://registry.npmjs.org/ag-grid-enterprise/-/ag-grid-enterprise-31.0.2.tgz", + "integrity": "sha512-KVtlvc8kcZVs4nRozUM0Ir7sd1W8I0JqSRSRhScob5lEE5aQnuYfy7LF8ZSic/HYLqZMBMDDsVfX8EfjkWfLlw==", "requires": { - "ag-grid-community": "~31.0.1" + "ag-grid-community": "~31.0.2" } }, "ag-grid-react": { - "version": "31.0.1", - "resolved": "https://registry.npmjs.org/ag-grid-react/-/ag-grid-react-31.0.1.tgz", - "integrity": "sha512-9nmYPsgH1YUDUDOTiyaFsysoNAx/y72ovFJKuOffZC1V7OrQMadyP6DbqGFWCqzzoLJOY7azOr51dDQzAIXLpw==", + "version": "31.0.2", + "resolved": "https://registry.npmjs.org/ag-grid-react/-/ag-grid-react-31.0.2.tgz", + "integrity": "sha512-QAC5Rsr4AaE+vCafyrnEJa7JWcmoXNzJyzZmln0T6QCLG43IuT2hZ3ULmce/IqwP2Kxf+B4hVJOVitVT9SdXkg==", "requires": { - "ag-grid-community": "~31.0.1", + "ag-grid-community": "~31.0.2", "prop-types": "^15.8.1" } }, diff --git a/package.json b/package.json index 26615823..36ce1b29 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dash-ag-grid", - "version": "31.0.1rc3", + "version": "31.0.2rc5", "description": "Dash wrapper around AG Grid, the best interactive data grid for the web.", "repository": { "type": "git", @@ -29,9 +29,9 @@ "dependencies": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", - "ag-grid-community": "^31.0.1", - "ag-grid-enterprise": "^31.0.1", - "ag-grid-react": "^31.0.1", + "ag-grid-community": "^31.0.2", + "ag-grid-enterprise": "^31.0.2", + "ag-grid-react": "^31.0.2", "@mui/icons-material": "^5.14.14", "@mui/material": "^5.14.14", "d3-format": "^3.1.0", diff --git a/src/lib/utils/propCategories.js b/src/lib/utils/propCategories.js index c6a2aa85..ae6c7c37 100644 --- a/src/lib/utils/propCategories.js +++ b/src/lib/utils/propCategories.js @@ -13,6 +13,8 @@ export const COLUMN_DANGEROUS_FUNCTIONS = { filterValueGetter: 1, headerValueGetter: 1, template: 1, + dateParser: 1, + dateFormatter: 1, }; /** @@ -183,7 +185,6 @@ export const COLUMN_MAYBE_FUNCTIONS_NO_PARAMS = { // Columns: Sort comparator: 1, - dataTypeMatcher: 1, // filter params custom option predicate: 1, @@ -265,6 +266,9 @@ export const COLUMN_MAYBE_FUNCTIONS = { // In filterParams filterPlaceholder: 1, + + // In dataTypeDefinitions + dataTypeMatcher: 1, }; /** diff --git a/tests/assets/dashAgGridFunctions.js b/tests/assets/dashAgGridFunctions.js index 557647d8..e115cb35 100644 --- a/tests/assets/dashAgGridFunctions.js +++ b/tests/assets/dashAgGridFunctions.js @@ -1,13 +1,13 @@ var dagfuncs = window.dashAgGridFunctions = window.dashAgGridFunctions || {}; -dagfuncs.Round = function(v, a=2) { - return Math.round(v * (10**a)) / (10**a) +dagfuncs.Round = function (v, a = 2) { + return Math.round(v * (10 ** a)) / (10 ** a) } -dagfuncs.toFixed = function(v, a=2) { +dagfuncs.toFixed = function (v, a = 2) { return Number(v).toFixed(a) } -dagfuncs.addEdits = function(params) { +dagfuncs.addEdits = function (params) { if (params.data.changes) { var newList = JSON.parse(params.data.changes) newList.push(params.colDef.field) @@ -19,77 +19,78 @@ dagfuncs.addEdits = function(params) { return true; } -dagfuncs.highlightEdits = function(params) { +dagfuncs.highlightEdits = function (params) { if (params.data.changes) { - if (JSON.parse(params.data.changes).includes(params.colDef.field)) - {return true} + if (JSON.parse(params.data.changes).includes(params.colDef.field)) { + return true + } } return false; } -dagfuncs.rowTest = function(params) { +dagfuncs.rowTest = function (params) { if (params.data.make == 'Toyota') { return 'testing' } } dagfuncs.ratioValueGetter = function (params) { - if (!(params.node && params.node.group)) { - // no need to handle group levels - calculated in the 'ratioAggFunc' - return createValueObject(params.data.gold, params.data.silver); - } + if (!(params.node && params.node.group)) { + // no need to handle group levels - calculated in the 'ratioAggFunc' + return createValueObject(params.data.gold, params.data.silver); + } } dagfuncs.ratioAggFunc = function (params) { - let goldSum = 0; - let silverSum = 0; - params.values.forEach((value) => { - if (value && value.gold) { - goldSum += value.gold; - } - if (value && value.silver) { - silverSum += value.silver; - } - }); - return createValueObject(goldSum, silverSum); + let goldSum = 0; + let silverSum = 0; + params.values.forEach((value) => { + if (value && value.gold) { + goldSum += value.gold; + } + if (value && value.silver) { + silverSum += value.silver; + } + }); + return createValueObject(goldSum, silverSum); } function createValueObject(gold, silver) { - return { - gold: gold, - silver: silver, - toString: () => `${gold && silver ? gold / silver : 0}`, - }; + return { + gold: gold, + silver: silver, + toString: () => `${gold && silver ? gold / silver : 0}`, + }; } dagfuncs.ratioFormatter = function (params) { - if (!params.value || params.value === 0) return ''; - return '' + Math.round(params.value * 100) / 100; + if (!params.value || params.value === 0) return ''; + return '' + Math.round(params.value * 100) / 100; } dagfuncs.filterParams = () => { return { - filterOptions: [ - 'lessThan', - { - displayKey: 'lessThanWithNulls', - displayName: 'Less Than with Nulls', - predicate: ([filterValue], cellValue) => cellValue == null || cellValue < filterValue, - }, - 'greaterThan', - { - displayKey: 'greaterThanWithNulls', - displayName: 'Greater Than with Nulls', - predicate: ([filterValue], cellValue) => cellValue == null || cellValue > filterValue, - }, - { - displayKey: 'betweenExclusive', - displayName: 'Between (Exclusive)', - predicate: ([fv1, fv2], cellValue) => cellValue == null || fv1 < cellValue && fv2 > cellValue, - numberOfInputs: 2, - } - ], - defaultOption: 'lessThanWithNulls', + filterOptions: [ + 'lessThan', + { + displayKey: 'lessThanWithNulls', + displayName: 'Less Than with Nulls', + predicate: ([filterValue], cellValue) => cellValue == null || cellValue < filterValue, + }, + 'greaterThan', + { + displayKey: 'greaterThanWithNulls', + displayName: 'Greater Than with Nulls', + predicate: ([filterValue], cellValue) => cellValue == null || cellValue > filterValue, + }, + { + displayKey: 'betweenExclusive', + displayName: 'Between (Exclusive)', + predicate: ([fv1, fv2], cellValue) => cellValue == null || fv1 < cellValue && fv2 > cellValue, + numberOfInputs: 2, + } + ], + defaultOption: 'lessThanWithNulls', } }; @@ -100,77 +101,77 @@ dagfuncs.getDataPath = function (data) { dagfuncs.DatePicker = class { // gets called once before the renderer is used - init(params) { - // create the cell - this.eInput = document.createElement('input'); - this.eInput.value = params.value; - this.eInput.classList.add('ag-input'); - this.eInput.style.height = 'var(--ag-row-height)'; - this.eInput.style.fontSize = 'calc(var(--ag-font-size) + 1px)'; - - // https://jqueryui.com/datepicker/ - $(this.eInput).datepicker({ - dateFormat: 'yy-mm-dd', - onSelect: () => { + init(params) { + // create the cell + this.eInput = document.createElement('input'); + this.eInput.value = params.value; + this.eInput.classList.add('ag-input'); + this.eInput.style.height = 'var(--ag-row-height)'; + this.eInput.style.fontSize = 'calc(var(--ag-font-size) + 1px)'; + + // https://jqueryui.com/datepicker/ + $(this.eInput).datepicker({ + dateFormat: 'yy-mm-dd', + onSelect: () => { + this.eInput.focus(); + }, + }); + } + + // gets called once when grid ready to insert the element + getGui() { + return this.eInput; + } + + // focus and select can be done after the gui is attached + afterGuiAttached() { this.eInput.focus(); - }, - }); - } - - // gets called once when grid ready to insert the element - getGui() { - return this.eInput; - } - - // focus and select can be done after the gui is attached - afterGuiAttached() { - this.eInput.focus(); - this.eInput.select(); - } - - // returns the new value after editing - getValue() { - return this.eInput.value; - } - - // any cleanup we need to be done here - destroy() { - // but this example is simple, no cleanup, we could - // even leave this method out as it's optional - } - - // if true, then this editor will appear in a popup - isPopup() { - // and we could leave this method out also, false is the default - return false; - } + this.eInput.select(); + } + + // returns the new value after editing + getValue() { + return this.eInput.value; + } + + // any cleanup we need to be done here + destroy() { + // but this example is simple, no cleanup, we could + // even leave this method out as it's optional + } + + // if true, then this editor will appear in a popup + isPopup() { + // and we could leave this method out also, false is the default + return false; + } } dagfuncs.dateComparator = function (date1, date2) { - const date1Number = monthToComparableNumber(date1); - const date2Number = monthToComparableNumber(date2); - if (date1Number === null && date2Number === null) { - return 0; - } - if (date1Number === null) { - return -1; - } - if (date2Number === null) { - return 1; - } - return date1Number - date2Number; + const date1Number = monthToComparableNumber(date1); + const date2Number = monthToComparableNumber(date2); + if (date1Number === null && date2Number === null) { + return 0; + } + if (date1Number === null) { + return -1; + } + if (date2Number === null) { + return 1; + } + return date1Number - date2Number; } // eg 29/08/2004 gets converted to 20040829 function monthToComparableNumber(date) { - if (date === undefined || date === null) { - return null; - } - const yearNumber = parseInt(date.split('/')[2]); - const monthNumber = parseInt(date.split('/')[1]); - const dayNumber = parseInt(date.split('/')[0]); - return yearNumber * 10000 + monthNumber * 100 + dayNumber; + if (date === undefined || date === null) { + return null; + } + const yearNumber = parseInt(date.split('/')[2]); + const monthNumber = parseInt(date.split('/')[1]); + const dayNumber = parseInt(date.split('/')[0]); + return yearNumber * 10000 + monthNumber * 100 + dayNumber; } const {useImperativeHandle, useState, useEffect, forwardRef} = React; @@ -181,31 +182,31 @@ const {useImperativeHandle, useState, useEffect, forwardRef} = React; // - setProps, which all Dash components use to report user interactions, // instead of a plain js event handler dagfuncs.YearFilter = forwardRef((props, ref) => { - const [year, setYear] = useState('All'); + const [year, setYear] = useState('All'); - useImperativeHandle(ref, () => { - return { - doesFilterPass(params) { - return params.data.year >= 2010; - }, + useImperativeHandle(ref, () => { + return { + doesFilterPass(params) { + return params.data.year >= 2010; + }, - isFilterActive() { - return year === '2010' - }, + isFilterActive() { + return year === '2010' + }, - // this example isn't using getModel() and setModel(), - // so safe to just leave these empty. don't do this in your code!!! - getModel() { - }, + // this example isn't using getModel() and setModel(), + // so safe to just leave these empty. don't do this in your code!!! + getModel() { + }, - setModel() { - } - } - }); + setModel() { + } + } + }); - useEffect(() => { - props.filterChangedCallback() - }, [year]); + useEffect(() => { + props.filterChangedCallback() + }, [year]); setProps = ({value}) => { if (value) { @@ -216,14 +217,14 @@ dagfuncs.YearFilter = forwardRef((props, ref) => { return React.createElement( window.dash_core_components.RadioItems, { - options:[ + options: [ {'label': 'All', 'value': 'All'}, {'label': 'Since 2010', 'value': '2010'}, ], value: year, setProps } - ) + ) }); dagfuncs.setBody = () => { @@ -261,7 +262,7 @@ dagfuncs.DMC_Select = class { data: params.options, value: params.value, setProps, - style: {width: params.column.actualWidth-2, ...params.style}, + style: {width: params.column.actualWidth - 2, ...params.style}, className: params.className, clearable: params.clearable, searchable: params.searchable || true, @@ -269,7 +270,7 @@ dagfuncs.DMC_Select = class { debounce: params.debounce, disabled: params.disabled, filterDataOnExactSearchMatch: - params.filterDataOnExactSearchMatch, + params.filterDataOnExactSearchMatch, limit: params.limit, maxDropdownHeight: params.maxDropdownHeight, nothingFound: params.nothingFound, @@ -341,61 +342,61 @@ dagfuncs.DMC_Select = class { dagfuncs.contextTest = (params) => { var result = [ - { - // custom item - name: 'Alert ' + params.value, - action: () => { - window.alert('Alerting about ' + params.value); + { + // custom item + name: 'Alert ' + params.value, + action: () => { + window.alert('Alerting about ' + params.value); + }, + cssClasses: ['redFont', 'bold'], }, - cssClasses: ['redFont', 'bold'], - }, - 'copy', - 'separator', - 'chartRange', + 'copy', + 'separator', + 'chartRange', ]; return result; }; // FOR test_custom_filter.py dagfuncs.myTextFormatter = (text) => { - if (text == null) return null; - return text - .toLowerCase() - .replace(/[àáâãäå]/g, 'a') - .replace(/æ/g, 'ae') - .replace(/ç/g, 'c') - .replace(/[èéêë]/g, 'e') - .replace(/[ìíîï]/g, 'i') - .replace(/ñ/g, 'n') - .replace(/[òóôõö]/g, 'o') - .replace(/œ/g, 'oe') - .replace(/[ùúûü]/g, 'u') - .replace(/[ýÿ]/g, 'y'); + if (text == null) return null; + return text + .toLowerCase() + .replace(/[àáâãäå]/g, 'a') + .replace(/æ/g, 'ae') + .replace(/ç/g, 'c') + .replace(/[èéêë]/g, 'e') + .replace(/[ìíîï]/g, 'i') + .replace(/ñ/g, 'n') + .replace(/[òóôõö]/g, 'o') + .replace(/œ/g, 'oe') + .replace(/[ùúûü]/g, 'u') + .replace(/[ýÿ]/g, 'y'); } function contains(target, lookingFor) { - return target && target.indexOf(lookingFor) >= 0; + return target && target.indexOf(lookingFor) >= 0; } dagfuncs.myTextMatcher = ({value, filterText}) => { - const aliases = { - usa: "united states", - holland: "netherlands", - niall: "ireland", - sean: "south africa", - alberto: "mexico", - john: "australia", - xi: "china", - }; - const literalMatch = contains(value, filterText || ""); - return literalMatch || contains(value, aliases[filterText || ""]); + const aliases = { + usa: "united states", + holland: "netherlands", + niall: "ireland", + sean: "south africa", + alberto: "mexico", + john: "australia", + xi: "china", + }; + const literalMatch = contains(value, filterText || ""); + return literalMatch || contains(value, aliases[filterText || ""]); } dagfuncs.myNumberParser = (text) => { - return text === null ? null : parseFloat(text.replace(",", ".").replace("$", "")); + return text === null ? null : parseFloat(text.replace(",", ".").replace("$", "")); } dagfuncs.myNumberFormatter = (value) => { - return value === null ? null : value.toString().replace(".", ","); + return value === null ? null : value.toString().replace(".", ","); } dagfuncs.startWith = ([filterValues], cellValue) => { const name = cellValue ? cellValue.split(" ")[1] : "" @@ -408,3 +409,77 @@ dagfuncs.quickFilterMatcher = (quickFilterParts, rowQuickFilterAggregateText) => return quickFilterParts.every(part => rowQuickFilterAggregateText.match(part)); } // END test_quick_filter.py + +// FOR test_cell_data_type_override.py +dagfuncs.dataTypeDefinitions = { + percentage: { + baseDataType: "number", + extendsDataType: "number", + valueFormatter: (params) => params.value == null ? '' : (Math.round(params.value * 1000) / 10).toFixed(1) + '%' + }, + + dateString: { + baseDataType: 'dateString', + extendsDataType: 'dateString', + valueParser: (params) => { + return params.newValue != null && + !!params.newValue.match(/\d{2}\/\d{2}\/\d{4}/) + ? params.newValue + : null + }, + valueFormatter: (params) => { + return params.value == null ? '' : params.value + }, + dataTypeMatcher: (value) => { + return typeof value === 'string' && !!value.match(/\d{2}\/\d{2}\/\d{4}/) + }, + dateParser: (value) => { + if (value == null || value === '') { + return undefined; + } + const dateParts = value.split('/'); + return dateParts.length === 3 + ? new Date( + parseInt(dateParts[2]), + parseInt(dateParts[1]) - 1, + parseInt(dateParts[0]) + ) + : undefined; + }, + dateFormatter: (value) => { + if (value == null) { + return undefined; + } + const date = String(value.getDate()); + const month = String(value.getMonth() + 1); + return `${date.length === 1 ? '0' + date : date}/${ + month.length === 1 ? '0' + month : month + }/${value.getFullYear()}`; + }, + }, +}; + +dagfuncs.dateParser = (value) => { + if (value == null || value === '') { + return undefined; + } + const dateParts = value.split('/'); + return dateParts.length === 3 + ? new Date( + parseInt(dateParts[2]), + parseInt(dateParts[1]) - 1, + parseInt(dateParts[0]) + ) + : undefined; +} +dagfuncs.dateFormatter = (value) => { + if (value == null) { + return undefined; + } + const date = String(value.getDate()); + const month = String(value.getMonth() + 1); + return `${date.length === 1 ? '0' + date : date}/${ + month.length === 1 ? '0' + month : month + }/${value.getFullYear()}`; +} +// END test_cell_data_type_override.py diff --git a/tests/examples/assets/dashAgGridFunctions.js b/tests/examples/assets/dashAgGridFunctions.js index 87c670e0..1de6f1a6 100644 --- a/tests/examples/assets/dashAgGridFunctions.js +++ b/tests/examples/assets/dashAgGridFunctions.js @@ -55,4 +55,76 @@ dagfuncs.startWith = ([filterValues], cellValue) => { dagfuncs.quickFilterMatcher = (quickFilterParts, rowQuickFilterAggregateText) => { return quickFilterParts.every(part => rowQuickFilterAggregateText.match(part)); -} \ No newline at end of file +} + +dagfuncs.dateParser = (value) => { + if (value == null || value === '') { + return undefined; + } + const dateParts = value.split('/'); + return dateParts.length === 3 + ? new Date( + parseInt(dateParts[2]), + parseInt(dateParts[1]) - 1, + parseInt(dateParts[0]) + ) + : undefined; +} +dagfuncs.dateFormatter = (value) => { + if (value == null) { + return undefined; + } + const date = String(value.getDate()); + const month = String(value.getMonth() + 1); + return `${date.length === 1 ? '0' + date : date}/${ + month.length === 1 ? '0' + month : month + }/${value.getFullYear()}`; +} + +dagfuncs.dataTypeDefinitions = { + percentage: { + baseDataType: "number", + extendsDataType: "number", + valueFormatter: (params) => params.value == null ? '' : (Math.round(params.value * 1000) / 10).toFixed(1) + '%' + }, + + dateString: { + baseDataType: 'dateString', + extendsDataType: 'dateString', + valueParser: (params) => { + return params.newValue != null && + !!params.newValue.match(/\d{2}\/\d{2}\/\d{4}/) + ? params.newValue + : null + }, + valueFormatter: (params) => { + return params.value == null ? '' : params.value + }, + dataTypeMatcher: (value) => { + return typeof value === 'string' && !!value.match(/\d{2}\/\d{2}\/\d{4}/) + }, + dateParser: (value) => { + if (value == null || value === '') { + return undefined; + } + const dateParts = value.split('/'); + return dateParts.length === 3 + ? new Date( + parseInt(dateParts[2]), + parseInt(dateParts[1]) - 1, + parseInt(dateParts[0]) + ) + : undefined; + }, + dateFormatter: (value) => { + if (value == null) { + return undefined; + } + const date = String(value.getDate()); + const month = String(value.getMonth() + 1); + return `${date.length === 1 ? '0' + date : date}/${ + month.length === 1 ? '0' + month : month + }/${value.getFullYear()}`; + }, + }, +}; diff --git a/tests/examples/cell_data_types_override.py b/tests/examples/cell_data_types_override.py new file mode 100644 index 00000000..1515aef8 --- /dev/null +++ b/tests/examples/cell_data_types_override.py @@ -0,0 +1,53 @@ +import dash_ag_grid as dag +from dash import Dash, html + +app = Dash(__name__) + +rowData = [ + {"weight": 0.074657, "date": "01/01/2024"}, + {"weight": 0.06948567, "date": "02/01/2024"}, + {"weight": 0.02730574, "date": "03/01/2024"}, + {"weight": 0.0182345, "date": "04/01/2024"}, +] + +columnDefs = [ + {"field": "weight", "cellDataType": "percentage"}, + {"field": "date", "cellDataType": "dateString"}, +] + +dataTypeDefinitions = { + "percentage": { + "baseDataType": "number", + "extendsDataType": "number", + "valueFormatter": { + "function": "params.value == null ? '' : d3.format(',.1%')(params.value)" + }, + }, + "dateString": { + "baseDataType": 'dateString', + "extendsDataType": 'dateString', + "valueParser": { + "function": r"params.newValue != null && !!params.newValue.match(/\d{2}\/\d{2}\/\d{4}/) ? params.newValue : null" + }, + "valueFormatter": {"function": "params.value == null ? '' : params.value"}, + "dataTypeMatcher": {"function": r"params != null && !!params.match(/\d{2}\/\d{2}\/\d{4}/)"}, + "dateParser": {"function": "dateParser(params)"}, + "dateFormatter": {"function": "dateFormatter(params)"}, + }, +} + +app.layout = html.Div( + [ + dag.AgGrid( + id="grid-cell-data-types-custom", + columnDefs=columnDefs, + rowData=rowData, + defaultColDef={"filter": True, "editable": True}, + dashGridOptions={"dataTypeDefinitions": dataTypeDefinitions}, + ), + ], +) + +if __name__ == "__main__": + app.run(debug=True) + diff --git a/tests/examples/cell_data_types_override_full_JS.py b/tests/examples/cell_data_types_override_full_JS.py new file mode 100644 index 00000000..feb9618f --- /dev/null +++ b/tests/examples/cell_data_types_override_full_JS.py @@ -0,0 +1,31 @@ +import dash_ag_grid as dag +from dash import Dash, html + +app = Dash(__name__) + +rowData = [ + {"weight": 0.074657, "date": "01/01/2024"}, + {"weight": 0.06948567, "date": "02/01/2024"}, + {"weight": 0.02730574, "date": "03/01/2024"}, + {"weight": 0.0182345, "date": "04/01/2024"}, +] + +columnDefs = [ + {"field": "weight", "cellDataType": "percentage"}, + {"field": "date", "cellDataType": "dateString"}, +] + +app.layout = html.Div( + [ + dag.AgGrid( + id="grid-cell-data-types-custom-full-JS", + columnDefs=columnDefs, + rowData=rowData, + defaultColDef={"filter": True, "editable": True}, + dashGridOptions={"dataTypeDefinitions": {"function": "dataTypeDefinitions"}}, + ), + ], +) + +if __name__ == "__main__": + app.run(debug=True) diff --git a/tests/test_cell_data_type_override.py b/tests/test_cell_data_type_override.py new file mode 100644 index 00000000..c1031cee --- /dev/null +++ b/tests/test_cell_data_type_override.py @@ -0,0 +1,83 @@ +from selenium.webdriver import Keys + +import dash_ag_grid as dag +from dash import Dash, html +from . import utils + + +def test_cd001_cell_data_types_override(dash_duo): + app = Dash(__name__) + + rowData = [ + {"weight": 0.074657, "date": "01/01/2024"}, + {"weight": 0.06948567, "date": "02/01/2024"}, + {"weight": 0.02730574, "date": "03/01/2024"}, + {"weight": 0.0182345, "date": "04/01/2024"}, + ] + + columnDefs = [ + {"field": "weight", "cellDataType": "percentage"}, + {"field": "date", "cellDataType": "dateString"}, + ] + + # Only for second grid + dataTypeDefinitions = { + "percentage": { + "baseDataType": "number", + "extendsDataType": "number", + "valueFormatter": { + "function": "params.value == null ? '' : d3.format(',.1%')(params.value)" + }, + }, + "dateString": { + "baseDataType": 'dateString', + "extendsDataType": 'dateString', + "valueParser": { + "function": r"params.newValue != null && !!params.newValue.match(/\d{2}\/\d{2}\/\d{4}/) ? params.newValue : null" + }, + "valueFormatter": {"function": "params.value == null ? '' : params.value"}, + "dataTypeMatcher": {"function": r"params != null && !!params.match(/\d{2}\/\d{2}\/\d{4}/)"}, + "dateParser": {"function": "dateParser(params)"}, + "dateFormatter": {"function": "dateFormatter(params)"}, + }, + } + app.layout = html.Div( + [ + dag.AgGrid( + id="grid-cell-data-types-override-full-JS", + columnDefs=columnDefs, + rowData=rowData, + defaultColDef={"filter": True, "editable": True}, + dashGridOptions={"dataTypeDefinitions": {"function": "dataTypeDefinitions"}}, + ), + dag.AgGrid( + id="grid-cell-data-types-override", + columnDefs=columnDefs, + rowData=rowData, + defaultColDef={"filter": True, "editable": True}, + dashGridOptions={"dataTypeDefinitions": dataTypeDefinitions}, + ), + ], + ) + + dash_duo.start_server(app) + + action = utils.ActionChains(dash_duo.driver) + + # same tests for both grids + for id in ["grid-cell-data-types-override-full-JS", "grid-cell-data-types-override"]: + grid = utils.Grid(dash_duo, id) + + # test overriden number cell data type + action.double_click(grid.get_cell(0, 0)).perform() + date_input_element = dash_duo.find_element(f'#{grid.id} .ag-number-field-input') + date_input_element.send_keys("0.1" + Keys.ENTER) + + grid.wait_for_cell_text(0, 0, "10.0%") + + # test overriden dateString cell data type + action.double_click(grid.get_cell(0, 1)).perform() + date_input_element = dash_duo.find_element(f'#{grid.id} .ag-date-field-input') + date_input_element.send_keys("01172024" + Keys.ENTER) + + grid.wait_for_cell_text(0, 1, "17/01/2024")