Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allowing more functions #243

Merged
merged 14 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/lib/utils/propCategories.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ export const GRID_MAYBE_FUNCTIONS = {
sendToClipboard: 1,
processDataFromClipboard: 1,

// Exporting
shouldRowBeSkipped: 1,

// Filtering
isExternalFilterPresent: 1,
doesExternalFilterPass: 1,
Expand Down Expand Up @@ -124,6 +127,8 @@ export const GRID_MAYBE_FUNCTIONS = {
**/
export const GRID_MAYBE_FUNCTIONS_NO_PARAMS = {
frameworkComponents: 1,
setPopupParent: 1,
popupParent: 1,
};

/**
Expand Down Expand Up @@ -161,6 +166,7 @@ export const GRID_NESTED_FUNCTIONS = {
**/
export const COLUMN_MAYBE_FUNCTIONS_NO_PARAMS = {
cellEditor: 1,
filter: 1,

// Columns: Sort
comparator: 1,
Expand Down
168 changes: 167 additions & 1 deletion tests/assets/dashAgGridFunctions.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,170 @@ function monthToComparableNumber(date) {
const monthNumber = parseInt(date.split('/')[1]);
const dayNumber = parseInt(date.split('/')[0]);
return yearNumber * 10000 + monthNumber * 100 + dayNumber;
}
}

const {useImperativeHandle, useState, useEffect, forwardRef} = React;

// This example was adapted from https://www.ag-grid.com/react-data-grid/component-filter/
// The only differences are:
// - React.createElement instead of JSX
// - setProps, which all Dash components use to report user interactions,
// instead of a plain js event handler
dagfuncs.YearFilter = forwardRef((props, ref) => {
BSd3v marked this conversation as resolved.
Show resolved Hide resolved
const [year, setYear] = useState('All');

useImperativeHandle(ref, () => {
return {
doesFilterPass(params) {
return params.data.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() {
},

setModel() {
}
}
});

useEffect(() => {
props.filterChangedCallback()
}, [year]);

setProps = ({value}) => {
if (value) {
setYear(value)
}
}

return React.createElement(
window.dash_core_components.RadioItems,
{
options:[
{'label': 'All', 'value': 'All'},
{'label': 'Since 2010', 'value': '2010'},
],
value: year,
setProps
}
)
});

dagfuncs.setBody = () => {
return document.querySelector('body')
}

// cell editor custom component - dmc.Select
dagfuncs.DMC_Select = class {
// gets called once before the renderer is used
init(params) {
// create the cell
this.params = params;

// function for when Dash is trying to send props back to the component / server
var setProps = (props) => {
if (typeof props.value != typeof undefined) {
// updates the value of the editor
this.value = props.value;

// re-enables keyboard event
delete params.colDef.suppressKeyboardEvent;

// tells the grid to stop editing the cell
params.api.stopEditing();

// sets focus back to the grid's previously active cell
this.prevFocus.focus();
}
};
this.eInput = document.createElement('div');

// renders component into the editor element
ReactDOM.render(
React.createElement(window.dash_mantine_components.Select, {
data: params.options,
value: params.value,
setProps,
style: {width: params.column.actualWidth-2, ...params.style},
className: params.className,
clearable: params.clearable,
searchable: params.searchable || true,
creatable: params.creatable,
debounce: params.debounce,
disabled: params.disabled,
filterDataOnExactSearchMatch:
params.filterDataOnExactSearchMatch,
limit: params.limit,
maxDropdownHeight: params.maxDropdownHeight,
nothingFound: params.nothingFound,
placeholder: params.placeholder,
required: params.required,
searchValue: params.searchValue,
shadow: params.shadow,
size: params.size,
styles: params.styles,
switchDirectionOnFlip: params.switchDirectionOnFlip,
variant: params.variant,
}),
this.eInput
);

// allows focus event
this.eInput.tabIndex = '0';

// sets editor value to the value from the cell
this.value = params.value;
}

// gets called once when grid ready to insert the element
getGui() {
return this.eInput;
}

focusChild() {
// needed to delay and allow the component to render
setTimeout(() => {
var inp = this.eInput.getElementsByClassName(
'mantine-Select-input'
)[0];
inp.tabIndex = '1';

// disables keyboard event
this.params.colDef.suppressKeyboardEvent = (params) => {
const gridShouldDoNothing = params.editing;
return gridShouldDoNothing;
};
// shows dropdown options
inp.focus();
}, 100);
}

// focus and select can be done after the gui is attached
afterGuiAttached() {
// stores the active cell
this.prevFocus = document.activeElement;

// adds event listener to trigger event to go into dash component
this.eInput.addEventListener('focus', this.focusChild());

// triggers focus event
this.eInput.focus();
}

// returns the new value after editing
getValue() {
return this.value;
}

// any cleanup we need to be done here
destroy() {
// sets focus back to the grid's previously active cell
this.prevFocus.focus();
}
};
2 changes: 2 additions & 0 deletions tests/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
# pip install -r requirements.txt

dash[ci,dev,testing]>=2.0

dash_mantine_components==0.12.1
59 changes: 59 additions & 0 deletions tests/test_custom_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,62 @@ def test_fi002_custom_filter(dash_duo):
grid.set_filter(2, "e")
grid.wait_for_cell_text(0, 1, "11-Sault-au-Récollet")
grid.wait_for_rendered_rows(8)

def test_fi003_custom_filter(dash_duo):
app = Dash(__name__)

df = pd.read_json('https://www.ag-grid.com/example-assets/olympic-winners.json', convert_dates=False)

rowData = df.to_dict('records')

columnDefs = [
{'field': 'age', 'filter': 'agNumberColumnFilter'},
{'field': 'country', 'minWidth': 150},
{'field': 'year', 'filter': {'function': 'YearFilter'}},
{
'field': 'date',
'minWidth': 130,
'filter': 'agDateColumnFilter',
},
{'field': 'sport'},
{'field': 'gold', 'filter': 'agNumberColumnFilter'},
{'field': 'silver', 'filter': 'agNumberColumnFilter'},
{'field': 'bronze', 'filter': 'agNumberColumnFilter'},
{'field': 'total', 'filter': 'agNumberColumnFilter'},
]

defaultColDef = {
'editable': True,
'sortable': True,
'flex': 1,
'minWidth': 100,
'filter': True,
'resizable': True,
}

app.layout = html.Div(
[
dag.AgGrid(
id="grid",
columnDefs=columnDefs,
rowData=rowData,
defaultColDef=defaultColDef
),
]
)

dash_duo.start_server(app)

grid = utils.Grid(dash_duo, "grid")

grid.wait_for_cell_text(0, 0, "23")

dash_duo.find_element('.ag-header-cell[aria-colindex="3"] .ag-icon-menu').click()

dash_duo.find_element('.ag-filter label:nth-child(2)').click()

grid.wait_for_cell_text(0, 0, "27")

dash_duo.find_element('.ag-filter label:nth-child(1)').click()

grid.wait_for_cell_text(0, 0, "23")
36 changes: 36 additions & 0 deletions tests/test_popupparent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import dash_ag_grid as dag
from dash import Dash, html, dcc
from . import utils
import time
import dash_mantine_components


def test_pp001_popupParent(dash_duo):
app = Dash(__name__)

app.layout = html.Div(
[
dag.AgGrid(
rowData=[{'cities': ''}, {'cities': 'dallas'}, {'cities': 'houston'}],
columnDefs=[{'field': 'cities', 'cellEditor': {'function': 'DMC_Select'},
'cellEditorParams': {'options': ['dallas', 'houston']},
'cellEditorPopup': True, 'editable': True}],
style={'resize': 'both', 'overflow': 'auto'},
dashGridOptions={'popupParent': {'function': 'setBody()'}},
id='grid'
)
],

)

dash_duo.start_server(app)

grid = utils.Grid(dash_duo, "grid")

grid.wait_for_cell_text(0, 0, "")

### testing animations
action = utils.ActionChains(dash_duo.driver)
action.double_click(grid.get_cell(0, 0)).perform()

dash_duo.find_element('body > .ag-popup')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice test - and nice use of > in the selector to verify that it's a direct child! Do you want to include something to assert that there's a dmc.Select in the popup? Like dash_duo.find_element('body > .ag-popup .mantine-select') or whatever class it should have?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we can definitely do this.