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

HTMLs for range filters don't conform to the HTML standards #325

Open
masasakano opened this issue May 29, 2024 · 4 comments
Open

HTMLs for range filters don't conform to the HTML standards #325

masasakano opened this issue May 29, 2024 · 4 comments

Comments

@masasakano
Copy link

The range filter produced by Datagrid generates a HTML that violates the HTML-5 standards:

Attribute multiple not allowed on element input with type="text"

(See HTML5 standards about input for detail of the specification.)

The form in the HTML for a range filter of Datagrid tries to pass a (2-element) Array when submitted, and Rails does interpret them as an Array (and so the filter works). But the HTML standards basically do not allow such pair of fields of input with type=text.

It should be better if the generated HTML for a range filter conforms to the HTML standard.

I asked a question about it in Stackoverflow, and an answer suggests using forms of:

  • my_model[my_attribute][from]
  • my_model[my_attribute][to]

as opposed to the current way of multiple

  • my_model[my_attribute][]

So, this may be a way to implement the (grammatically) correct pair of HTML-form fields for a range filter. Obviously, I suppose there are many other ways.

@bogdan
Copy link
Owner

bogdan commented Jun 27, 2024

I am open for the change to accept attributes values as Hash with {from: , to: } keys. Altering the default input names generation to these is definitely a good idea, but is also a breaking change. So, we need to think how we want to introduce that in a way that allows some to keep the old system.

I imagine the first step in supporting Hash to be assigned as attribute value and make sure built-in partials can be modified to support that Hash.

Besides that, I designed range filter attribute value as an array of two elements because Ruby didn't support infinite ranges (1..) at that moment. I think it needs to be changed at some point and this would be a good opportunity to do so. Obviously, it is impossible to pass any kind of range from frontend to backend because HTML forms are legacy. But I believe if you read the datagrid attribute value of a ranged filter it should return a Range, but not Hash or Array.

Let me know what you think or let me digest my own thoughts for a few days and get back.

@masasakano
Copy link
Author

Thank you very much for taking this up!

Conceptually, I agree with you that a range filter should ideally return a Range, regardless of how it is actually dealt with in HTML forms and Rails built-in processing. Ruby 3 supports both (..5) and (5..), and so I guess it is well possible to implement it in Datagrid? Personally, I would welcome the change.

I understand such changes in Range filters are more or less backward-incompatible. A major-version upgrade in versioning may be necessary? Personally, I would accept and welcome the breaking changes with this matter.

A way to mitigate to minimise the disruption I can think of is this. This is not perfect, but I guess it should accommodate most use-cases.

I suppose two primary, potentially breaking changes for users are the default option and the block given to the filter method. The default option for filter(range: true) is expected to return a 2-element Array. So, I suppose Datagrid can be coded so it accepts both a Range and 2-element Array as the return value of the default option.

The parameter given to the block is more tricky. However, I guess most users access the given object (currently a 2-element Array) through methods of first, last, [0], [1], [-1], although few people might use other ways like values_at. If Datagrid passes to the block a Range-like object that accepts the three methods (first, last, and []), most of the existing user code using Datagrid should need no change. I note that Range accepts the method first (and last) except for begin-less (and end-less, respectively) cases. You may create a sub-class of Range and implement the methods in the sub-class, or alternatively may implement them as singleton methods for the object passed to the block.

Obviously, users may have other use-cases. In particular, their controller tests of Grid, where parameters are directly passed to GET, will fail. But a bright side is all W3C-validation tests (in Controller and System tests) that currently fail with Datagrid with Range filters will pass at last.

The above is just an idea of hopefully reducing (or minimising) the potential pain of Datagrid users associated with the breaking change. Please feel free to take it or leave it!

@bogdan
Copy link
Owner

bogdan commented Jul 7, 2024

I understand such changes in Range filters are more or less backward-incompatible. A major-version upgrade in versioning may be necessary? Personally, I would accept and welcome the breaking changes with this matter.

Phase 1 - minor version release:

Here is how it is usually done: we introduce a new configuration parameter new_ranges_behaviour which can be nil(default), false and true.
false and nil would make ranges behave the old way.
true converts them to new way.
When it is not set (aka nil) it triggers a warning on app boot that this new configuration parameter needs to be set as well as the link to instructions.

Additional optional convenience would be to the ability to override a global configuraiton on per filter basis with the same parameter name. It would help larger projects with gradual migration.

Phase 2 - major version release:

We make the parameter default to true and add a warning for both nil and false that the migration is needed.

Phase 3 - minor version release:

We remove the parameter, saying instead that it is deprecated and new behaviour is enforced.

In particular, their controller tests of Grid, where parameters are directly passed to GET, will fail

I suppose two primary, potentially breaking changes for users are the default option and the block given to the filter method. The default option for filter(range: true) is expected to return a 2-element Array. So, I suppose Datagrid can be coded so it accepts both a Range and 2-element Array as the return value of the default option.

Good point.

I believe we should maintain the ability to assign a range attribute as a 2 elements array. I don't see any problems with that.
Potentially it can be deprecated later, but I see not much reason for that. To me this functionality is built-in typecast: the same as when you would assign a "1" to an integer attribute either ActiveRecord or Datagrid - very helpful.

The other problem I see is serialization: in current version if you put datagrid attributes to database (like Job Queue for CSV export), it would serialize to JSON and deserialize normally. With ranges it would break:

> ActiveSupport::JSON.load((1..2).to_json)
=> "1..2"

So I believe, to make it smoother, we need the ability to assign range in the string format as well.

The parameter given to the block is more tricky. However, I guess most users access the given object (currently a 2-element Array) through methods of first, last, [0], [1], [-1], although few people might use other ways like values_at.

Yeah that is a problem.

If Datagrid passes to the block a Range-like object that accepts the three methods (first, last, and []), most of the existing user code using Datagrid should need no change. I note that Range accepts the method first (and last) except for begin-less (and end-less, respectively) cases. You may create a sub-class of Range and implement the methods in the sub-class, or alternatively may implement them as singleton methods for the object passed to the block.

That seems too crazy: changing built-in object behavior even in a scope of the library doesn't look good to me.

@masasakano
Copy link
Author

The proposed procedure of phases 1--3 sounds definitely ideal!
(if some extra amount of work is needed for developers)

If you introduce a configuration parameter new_ranges_behaviour and if it is accepted also as an optional parameter in each filter, then the block parameter in the associated block to each filter can (and perhaps should) reflect it. That is, if new_ranges_behaviour is nil or false, the block parameter is an Array as it has been, and if it is true, it is a Range. For example,

filter(:year, :integer, range: true, new_ranges_behaviour: false){ |record| }
  # record is a 2-element Array.
filter(:year, :integer, range: true, new_ranges_behaviour: true){ |record| }
  # record is a Range.

This seems to me the cleanest way with the 3-phase gradual migration.

I didn't think of serialization… Very good point!

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

No branches or pull requests

2 participants