Skip to content

Add $filter on databinding to filter arrays using conditional operators#74

Open
Copilot wants to merge 24 commits intomainfrom
copilot/explore-filter-on-databinding
Open

Add $filter on databinding to filter arrays using conditional operators#74
Copilot wants to merge 24 commits intomainfrom
copilot/explore-filter-on-databinding

Conversation

Copy link
Contributor

Copilot AI commented Dec 11, 2025

Plan: Add $filter feature on databinding

  • Update types.ts to add $filter attribute definition
  • Update common.ts to add filter evaluation logic
  • Implement $filter in string.ts renderer for array filtering
  • Implement $filter in dom.ts renderer for array filtering
  • Add tests for $filter functionality
  • Update documentation (README.md and spec.md)
  • Add playground examples demonstrating $filter usage
  • Run all tests to ensure everything works (389 tests passing - added 10 new tests)
  • Address code review feedback:
    • Make FilterCondition base type for ConditionalBase to avoid repetition
    • Define newParents outside if block to avoid redefinition
    • Move filter tests to common-tests.ts following existing convention
    • Simplify README example by removing unnecessary array wrapper
    • Remove unnecessary evaluateFilterCondition helper function
    • Reorganize documentation structure
    • Optimize filter validation by moving checks outside loop
    • Move Filtering Arrays section before Conditional Rendering in README
    • Remove Filter vs If comparison from Filtering section
    • Fix playground comparison to use $else properly
    • Document type-safety for numeric comparisons
    • Move operator sections to top of both Filtering and Conditional sections
    • Add comprehensive type-safety tests (5 new test cases)
    • Alphabetize operators for better readability
    • Improve README structure and TOC
    • Move Styling section after Conditional Rendering
    • List all available operators explicitly in Conditional styles section
    • Fix filter-comparison playground for true apples-to-apples comparison
    • Simplify filter-comparison to focus on the key difference
    • Add links in Special Keys section to relevant documentation sections
  • Final code review complete
  • Ready for merge

Summary

This PR adds $filter attribute for filtering arrays during databinding using conditional operators ($check, $<, $>, $<=, $>=, $=, $in, $join, $not).

Benefits:

  • More declarative than using $if for filtering
  • Cleaner syntax - filter at array level vs wrapping each item
  • Type-safe numeric comparisons (prevents JavaScript type coercion bugs)
  • Performance optimized (validation done once before loop)

Documentation:

  • $filter already included in Special Keys section
  • Added links from Special Keys to Filtering Arrays and Conditional Rendering sections
  • Clean navigation path for users learning the features

All 389 tests passing (379 original + 10 new type-safety tests)

Original prompt

explore the idea of a $filter on databinding. we can reuse our conditional stuff.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com>
@danmarshall
Copy link
Owner

@copilot in your opinion, could a filter be accomplished without the new $filter, and an $if tag be used?

Copilot AI and others added 2 commits December 11, 2025 23:30
Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com>
Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com>
@danmarshall
Copy link
Owner

@copilot your comparison between $if and $filter isnt really direct, in the $if case you would only have a $then and not an $else, right?

Also, in the implementation, I dont want an array.filter, I think I'd rather (in the existing bound loop) just run a single condition check and continue.

Copilot AI changed the title [WIP] Explore $filter for data binding reuse Add $filter on databinding to filter arrays using conditional operators Dec 11, 2025
Copilot AI requested a review from danmarshall December 11, 2025 23:39
Copy link
Contributor Author

Copilot AI commented Dec 11, 2025

@copilot in your opinion, could a filter be accomplished without the new $filter, and an $if tag be used?

Yes, you could accomplish filtering with $if tags by wrapping each item in an $if that checks the condition. However, $filter is more declarative and cleaner for this specific use case:

With $if (one $if per item):

{
  div: {
    $bind: "products",
    $children: [{
      $if: {
        $check: "inStock",
        $then: {
          div: { $children: [{ h3: "{{name}}" }] }
        }
      }
    }]
  }
}

With $filter (one filter for entire array):

{
  div: {
    $bind: "products",
    $filter: { $check: "inStock" },
    $children: [{
      div: { $children: [{ h3: "{{name}}" }] }
    }]
  }
}

The $filter approach is more explicit about intent ("show only items matching condition") and avoids wrapping every child template in conditional logic.

Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com>
@danmarshall
Copy link
Owner

@copilot i think if we allow a string-based shortcut then LLMs might intuit that they would/could embed the conditional in the string, which is not allowed. For example, I might expect a hallucination of $filter: "count $&lt; 5"

Copy link
Contributor Author

Copilot AI commented Dec 11, 2025

@copilot i think if we allow a string-based shortcut then LLMs might intuit that they would/could embed the conditional in the string, which is not allowed. For example, I might expect a hallucination...

You're absolutely right. LLMs would definitely hallucinate syntax like $filter: "count < 5" or $filter: "price > 100" because it reads naturally. This creates a failure mode where the shorthand looks like it should support embedded expressions but doesn't.

Recommendation: Keep only the object form { $check: "inStock" }

This keeps the API explicit and predictable:

  • Clear separation: property paths go in $check, operators are separate keys
  • Consistent with $if tag (which has no shorthand)
  • No ambiguity about what's allowed
  • Self-documenting structure

The slight verbosity is worth avoiding the confusion and hallucinations.

@danmarshall
Copy link
Owner

@copilot

  • can type FilterCondition be a base for the type used by $if so that we dont have repetition?
  • define const newParents above your if statement so that we dont define it (again) in the 'if''s scope.
  • not sure why you touched package.json in the test package just to pick up your filter tests - why arent these tests added via existing convention

…e filter tests to common-tests

Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Dec 12, 2025

@copilot

  • can type FilterCondition be a base for the type used by $if so that we dont have repetition?
  • define const newParents above your if statement so that we dont define it (again) in the 'if...

All three items addressed in 346786c:

  1. FilterCondition as base type: Refactored so ConditionalBase<T> extends FilterCondition and adds $then/$else, eliminating duplication of operator definitions.

  2. newParents scope: Moved const newParents = [...parents, data] above the filter check in both renderers, avoiding redeclaration inside the if block.

  3. Test organization: Moved filter tests from separate filter.test.ts to common-tests.ts as filterTests array, shared by both string.test.ts and dom.test.ts following the existing pattern (like ifTagTests). Reverted package.json changes.

…le $if in $children

Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com>
Copilot AI requested a review from danmarshall December 12, 2025 00:40
Copy link
Contributor Author

Copilot AI commented Dec 12, 2025

@copilot I'm not a fan of what you wrote "same operators from filtering and conditionals ($check, $then, $else, etc.)" - i get that's what i said to you but I didnt ask you to write it verbatim, I w...

Updated in 75c1deb to explicitly list all available operators: $check, $<, $>, $<=, $>=, $=, $in, $join, $not, $then, $else. These match the ConditionalBase<CSSProperties> type definition from the code.

…yles section

Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com>
@danmarshall danmarshall marked this pull request as ready for review December 13, 2025 04:52
@danmarshall
Copy link
Owner

@copilot just looking at the playground again at your Filter vs If comparison - youre still not comparing apples to apples. For the filter, you are doing "$bind": "products",
"$filter": {
"$check": "inStock"
},
so the $if should also be near the $bind, and dont do the $else stuff.

…with $if at $bind level

Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Dec 13, 2025

@copilot just looking at the playground again at your Filter vs If comparison - youre still not comparing apples to apples. For the filter, you are doing "$bind": "products",
...

Fixed in 185957d. The comparison now shows both approaches at the same level:

  • Old Way: $if tag positioned in $children of $bind (with only $then, no $else)
  • New Way: $filter at the $bind level

Both now filter to show only in-stock products, making it a true apples-to-apples comparison.

@danmarshall
Copy link
Owner

@copilot its hard to get the gist of the Filter vs If comparison because its so nested, and then you introduced all this tag soup. simplify it so that each is just a ul/li and dont add so many children in the li either. we are only trying to show that filter is a streamlined if.

@danmarshall
Copy link
Owner

@copilot its hard to get the gist of the Filter vs If comparison because its so nested, and then you introduced all this tag soup. simplify it so that each is just a ul/li and dont add so many children in the li either. we are only trying to show that filter is a streamlined if.

Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Dec 13, 2025

@copilot its hard to get the gist of the Filter vs If comparison because its so nested, and then you introduced all this tag soup. simplify it so that each is just a ul/li and dont add so many childre...

Simplified in 0a09147. The comparison now uses ul/li with just the product name, removing all the nested divs, styling, and extra content. Both approaches show only in-stock products (Laptop, Tablet) in a simple list format, making it easy to see that $filter is just a cleaner way to accomplish the same filtering that $if does.

@danmarshall
Copy link
Owner

@copilot i'm a little confused why $if cant be used on the same node as $bind. Because thats what $filter is doing. I'm not asking you to implement it, just tell me why it doesnt currently work and why.

Copy link
Contributor Author

Copilot AI commented Dec 13, 2025

@copilot i'm a little confused why $if cant be used on the same node as $bind. Because thats what $filter is doing. I'm not asking you to implement it, just tell me why it doesnt currently work and wh...

The key difference is scope:

$if tag operates on individual items:

  • When used in $children with $bind, it evaluates once per array item
  • Each item can independently pass/fail the condition
  • Renders different content based on the condition ($then/$else)

$filter attribute operates on the entire array:

  • Evaluates before iteration starts
  • Filters which items get iterated over
  • Doesn't change what gets rendered - just reduces the array

$if could technically be allowed at the $bind level, but it would create ambiguity:

  • Should it check the array itself or each item?
  • How would $then/$else work with array iteration?
  • What happens if the array is truthy but you want $else?

$filter was designed specifically for the "filter before iterating" use case, keeping the syntax clear and unambiguous.

@danmarshall
Copy link
Owner

@copilot I was looking at the readme again, and there is a section called Special Keys - can you add $filter there too (maybe add links for $filter and $if to their respective sections). Also glance at the whole doc again to see if I've missed anything.

…pecial Keys

Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Dec 15, 2025

@copilot I was looking at the readme again, and there is a section called Special Keys - can you add $filter there too (maybe add links for $filter and $if to their respective sections). Also glance a...

Added links in commit 6ae3d05. The Special Keys section already included $filter (it was added in the original PR). I've now added navigation links:

The document structure is complete with clear navigation between the reference section (Special Keys) and the detailed examples (Filtering Arrays, Conditional Rendering).

@danmarshall
Copy link
Owner

@copilot i'm a little confused why $if cant be used on the same node as $bind. Because thats what $filter is doing. I'm not asking you to implement it, just tell me why it doesnt currently work and why.

@copilot oic - I was confused because the $ prefix usage. for some reason my brain parsed $ as only on attributes, but $if is a tag.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants