Skip to content

Built-in filtering and sorting for iterative sections? - RFC 0.10 #2947

@evs-chris

Description

@evs-chris

Iteration is one of the most common constructs in view libraries, and iteration often needs filtering and/or sorting applied to it. You can currently achieve this to some extent with an expression in ractive, but there are some strong disadvantages, as the expression breaks the connection between the source array and the items in the template e.g. {{#each articles}} -> {{#each filtered(articles)}} puts the iterations in a computed context. 0.9 has support for bindings in computed contexts, but they're not cheap for non-trivial arrays or expressions.

To address those issues, I'd propose that we adjust the iteration syntax a little and introduce special aliases to handle filter, sort, limit, and offset. With the special aliases applied, the iterative section would adjust the array before it rendered/updated any of the iterations. It would also subscribe to each of the special aliases, so that any updates to them or their deps would cause the section to update as well. Here's roughly what the render cycle would look like:

Render process

  1. Get start array.
  2. Apply filter resulting in a mapping of index to index or -1.
  3. Apply sort resulting in a further mapping of index to target position.
  4. Scope the mapping using offset and limit.
  5. Unrender any iterations that should no longer be visible and render those that should based on the mapping.
    • Optionally, render transitions could wait on unrender transitions to complete, which would make items appear to leave the list and re-enter at a new position. That could also be done for shuffled sets.

Syntax change

The index and/or key for an iterative section can currently be aliased by using a : after the expression and providing names separated by commas. That doesn't really work with additional things, and it's extra code for a function that's already handled by the normal aliasing that came along later. To accommodate additional special aliases, the index refs would be dropped and replaced with aliases:

{{#each items: i}} ... {{/each}}
{{#each items as item: i}} ... {{/each}}
{{#items: i}} ... {{/}}
{{#each obj: k, i}} ... {{/each}}
{{#each obj as val: k, i}} ... {{/each}}
{{#obj: k}} ... {{/}}
would become
{{#each items with @index as i}} ... {{/each}}
{{#each items as item, @index as i}} ... {{/each}}
{{#items with @index as i}} ... {{/}}
{{#each obj with @key as k, @index as i}} ... {{/each}}
{{#each obj as val, @key as k, @index as i}} ... {{/each}}
{{#obj with @key as k}} ... {{/}}

Everything gets a little longer, but it is also considerably more clear. The additional special aliases could appear anywhere in the alias list e.g.

{{#each items as item, someFilter as filter, 10 as limit, page * 10 as offset, @index as i}} ... {{/each}}

Arrows

There are a number of helper libraries that would greatly benefit from some sort of limited function syntax supported by the expression parsing available to templates. Filtering and sorting would also benefit tremendously, so I'd propose that short-form arrows that compile to plain functions be added to the expression syntax e.g.

() => num * 20; // function() { return num * 20; }, which expressions out to function() { return _0 * 20; } with num as a dep subbed in at _0
x => x * x; // function(x) { return x * x; }
(a, c) => c + (a * num); // function(a, c) { return c + (a * _0); }
item => regex.test(item.prop) || regex.test(item.otherProp); // function(item) { return _0.test(item.prop) || _0.test(item.otherProp); }
(l, r) => l.name.localeCompare(r.name); // function(l, r) { l.name.localeCompare(r.name); }

I haven't fully thought through the implications of inlining functions into the existing expression constructs, but from a first pass point of view, it looks like it would work nicely. Limiting it to the simple arrow syntax also significantly limits the hairier issues that come with function support, and I'd basically ignore binding, as this references get subbed out to context either way.

New special ref

Since @index is relative to the source array, we'd probably need a new special ref that represents the index of the iteration in the final collection after filter/sort is applied. I'm not sure what that would be called at this point.

Thoughts?

Opinions? Observations?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions