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

Dynamic filters are not working as expected. The identifier should not be a function parsed to a string. #83

Closed
marioaldairsr opened this issue Jan 19, 2024 · 9 comments
Assignees
Labels
enhancement New feature or request
Milestone

Comments

@marioaldairsr
Copy link

marioaldairsr commented Jan 19, 2024

I have an application that displays a table with the keys name of an object, and it needs to be dynamically filtered based on those keys but it doesn't work as expected.

To replicate:

Main.svelte

<script>
	import { DataHandler } from '@vincjo/datatables';
	import ThFilter from './ThFilter.svelte';

	const data = [
		{
			name: 'Mario',
			status: 'DRAFT',
			ammount: 41
		},
		{
			name: 'George',
			status: 'OPEN',
			ammount: 54
		},
		{
			name: 'Mario',
			status: 'OPEN',
			ammount: 31
		}
	];

	const handler = new DataHandler(data, { rowsPerPage: 3 });
	const rows = handler.getRows();

	const keys = Object.keys(data[0]); // ['name', 'status', 'ammount']
	$: console.log(keys);
</script>

<table>
	<thead>
		<tr>
			{#each keys as key}
				<th>{key}</th>
			{/each}
		</tr>
		<tr>
			{#each keys as key}
				<ThFilter {handler} {key} />
			{/each}
		</tr>
	</thead>
	<tbody>
		{#each $rows as row}
			<tr>
				{#each keys as key}
					<td>{row[key]}</td>
				{/each}
			</tr>
		{/each}
	</tbody>
</table>

ThFilter.svelte

<script lang="ts">
	import type { DataHandler } from '@vincjo/datatables';
	export let handler: DataHandler;
	export let key: any = '';
	let value = '';
</script>

<th>
	<input
		type="text"
		placeholder="Filter"
		bind:value
		on:input={() => handler.filter(value, (row) => row[key])}
	/>
</th>

This is not working as expected; every time I filter, it resets everything.

I was looking into the package's source code and I found that it parses the function to a string and sets it as an identifier. So, if I put the function as (row) => row[key] , that becomes the identifier. When it tries to filter, it replaces the last function.

utils.js
image

There is another solution for this?

@vincjo
Copy link
Owner

vincjo commented Jan 19, 2024

In ThFilter.svelte, since the key is the variable, you should pass it directly as the filter param rather than using a callback:

handler.filter(value, key)

instead of:

handler.filter(value, (row) => row[key])

Then, as you mentioned, the parseField() function will determine that "key" is of type "string", and will create the callback.
The identifier will contain the actual value of "key" instead of "row[key]"

@marioaldairsr
Copy link
Author

marioaldairsr commented Jan 19, 2024

@vincjo, thanks for your reply.

We have a use case where we could have a nested object like this:

const data = [
    {
        name: 'Mario',
        status: 'DRAFT',
        ammount: 41,
        job: {
            id: 5,
            name: 'Developer'
        }
    },
    {
        name: 'George',
        status: 'OPEN',
        ammount: 54,
        job: {
            id: 6,
            name: 'Sales'
        }
    },
    {
        name: 'Mario',
        status: 'OPEN',
        ammount: 31,
        job: {
            id: 7,
            name: 'Engineer'
        }
    }
];

And this is a model that can have more or fewer key properties; it's dynamic, that's why can't have a custom filter for each of them. So we are using the callback like this:

(row) => getPropertyValue(row, key)

where key could be 'jobs.id', 'name', 'jobs.name', etc...

@marioaldairsr marioaldairsr changed the title Dynamic filters are not functioning as expected. The identifier should not be a function parsed to a string. Dynamic filters are not working as expected. The identifier should not be a function parsed to a string. Jan 19, 2024
@marioaldairsr
Copy link
Author

@vincjo, I was able to solve the problem with the following code, creating a dynamic function to ensure that when converted to a string it's always unique:

    function createNamedFunction(key) {
        return new Function('row', `
        function getValueByPath(obj, path) {
            return path.split('.').reduce((currentObject, key) => {
                return currentObject ? currentObject[key] : undefined;
            }, obj);
	    }
        return getValueByPath(row, '${key}');`);
    }
   
   handler.filter(value, createNamedFunction(key), check.contains);

@vincjo
Copy link
Owner

vincjo commented Jan 19, 2024

Oh, ok. Nice

I also added a workaround in version 1.14.3 (this library needs a better way to name filters).
You can choose what best suits your needs.

In ThFilter.svelte

<script lang="ts">
    import type {  DataHandler } from '$lib';
    export let handler: DataHandler;
    export let key: any = '';
    let value = '';
    const filter = handler.createFilter((row) => row[key])
</script>

<th>
    <input
        type="text"
        placeholder="Filter"
        bind:value
        on:input={() => filter.set(value)}
    />
</th>

Instead of handler.filter() shortcut, you declare a new filter instance, by using handler.createFilter().
This instance creates a random string as a unique identifier.

Thanks for raising this point

@marioaldairsr
Copy link
Author

Cool. Thank you so much for your help. I'll close this.

@marioaldairsr
Copy link
Author

@vincjo, sorry for open it again. Just want to ask if is it the same case with the sort function?

@vincjo
Copy link
Owner

vincjo commented Jan 22, 2024

Indeed, didn't think about it.
I added a workaround for column sorting, where you can pass an identifier to the sort method.

Released in 1.14.4.

Examples

Using handler instance:

handler.sort(orderBy, identifier)

Using <Th> component:

<Th orderBy={(row) => row[key]} identifier="th0"/>

Using your code example:

<table>
    <thead>
        <tr>
            {#each keys as key, i}
                <Th {handler} orderBy={key} identifier={'th' + i}>{key}</Th>
            {/each}
        </tr>
[...]

@marioaldairsr
Copy link
Author

Okay, it looks nice. Thank you. Is there a way to have multiple sorts?

For example:

Sort by id asc
Then by amount desc
Then by date asc

To solve this, I just have to create a custom sort function and then reset the rows with handler.setRows(sortedRows)

@vincjo
Copy link
Owner

vincjo commented Jan 22, 2024

There is a way:

handler.sortAsc('id')
handler.sortDesc('amount')
handler.sortAsc('date')

(You can add an identifier as a second parameter)

When you update rows using setRows(), last 3 sorts are played automatically.

@vincjo vincjo added the enhancement New feature or request label Jun 5, 2024
@vincjo vincjo added this to the 2.0 milestone Jun 5, 2024
@vincjo vincjo closed this as completed by moving to Done in svelte simple datatables 2.0 Jun 6, 2024
@vincjo vincjo self-assigned this Jun 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
Development

No branches or pull requests

2 participants