woylie / flop_phoenix Goto Github PK
View Code? Open in Web Editor NEWComponents for pagination, sortable tables and filter forms using Phoenix, Ecto and Flop
License: MIT License
Components for pagination, sortable tables and filter forms using Phoenix, Ecto and Flop
License: MIT License
Since slots must be a direct child of a component, it is not possible to use if
to hide a :col
. We need a :hide
option for the :col
slot.
Is your feature request related to a problem? Please describe.
Hi @woylie, first off, thanks so much for maintaining this project. It's fantastic and I've really enjoyed the API you expose.
The problem: I'm building a relatively static form where I'd like to allow users to enter a min and max range for a specific value. The interface for this would provide 2 separate inputs per field.
EDIT: I believe this problem is well-described here: #88. There's a mention to PR #89, but I'm not sure if this issue ever got resolved?
As an example, let's say we'd like to enter a minimum and maximum age, I believe the field definitions would look like:
[
age: [label: "Minimum age", op: :>=, type: "number"],
age: [label: "Maximum age", op: :<=, type: "number"]
]
From my implementation efforts and research, it appears this structure with the same field and different operators isn't yet supported by the library.
In researching this issue, I found the work you did to use separate labels for multiple fields, and stumbled across this conversation as well which I believe is pertinent.
Right now, I believe the age
fields in the example would appear separately in the UI; however, upon collating the filter values, they would be merged, taking the value of the first age
filter, the second one being discarded, and values from the UI being assigned to different inputs via the zip-based implementation here.
I could be totally wrong here, if you have any advice to support the use case above, it would be extremely helpful!
Describe the solution you'd like
My ideal solution would be that this library supports an arbitrary number of independently configured inputs per field just based on the fields
configuration described above.
Describe alternatives you've considered
As an alternative, I tried creating a more manual implementation of the filter_fields
component as described here, but was unsuccessful.
Additional context
As I mentioned, I could be totally off base here, but I've been struggling with this for a few hours and would love any advice.
Thanks so much!
With LiveView 0.18.1, warnings for Flop.Phoenix.filter_fields/1
are emitted.
warning: you are accessing the variable "field_opts" inside a LiveView template.
Using variables in HEEx templates are discouraged as they disable change tracking. You are only allowed to access variables defined by Elixir control-flow structures, such as if/case/for, or those defined by the special attributes :let/:if/:for.
Instead of:
def add(assigns) do
result = assigns.a + assigns.b
~H"the result is: <%= result %>"
end
You must do:
def add(assigns) do
assigns = assign(assigns, :result, assigns.a + assigns.b)
~H"the result is: <%= @result %>"
end
lib/flop_phoenix.ex:828: Flop.Phoenix.filter_fields/1
Hey @woylie
We'd like to be able to pass HTML to the headers (not sortable), hence we're doing
headers: [content_tag(:span, "foo")]
which unfortunately gets gobbled up by the headers({value, field}...)
clause of the header/5
function. Our current workaround is something like headers: [{content_tag(:span, "foo"), nil}]
, which is ok but somewhat ugly.
PR incoming.
Best,
malte
Describe the bug
Looks like Phoenix LiveView 0.18.3, added a raise to global
within slot attr.
See phoenixframework/phoenix_live_view#2164 && phoenixframework/phoenix_live_view#2265
To Reproduce
Install Phoenix LiveView 0.18.3 & FlopPhoenix, you receive a compile error.
== Compilation error in file lib/flop_phoenix.ex == ** (CompileError) lib/flop_phoenix.ex:660: cannot define :global slot attributes (phoenix_live_view 0.18.3) lib/phoenix_component/declarative.ex:526: Phoenix.Component.Declarative.compile_error!/3 (phoenix_live_view 0.18.3) lib/phoenix_component/declarative.ex:251: Phoenix.Component.Declarative.__slot__!/6 lib/flop_phoenix.ex:628: (module)
Versions:
With the latest changes, all customization options for the appearance of the components are separated from the options that only relate to the specific instance. These appearance options are likely the same for all templates. At the moment, the documentation suggests to define wrapper components that set these options and to import them with the other view helpers:
# in the view helper module
def pagination(assigns) do
assigns = assign_new(assigns, :opts, fn -> [] end)
~H"""
<Flop.Phoenix.pagination {assigns} opts={pagination_opts(@opts)} />
"""
end
defp pagination_opts(opts) do
default_opts = [
ellipsis_attrs: [class: "ellipsis"],
# ...
]
Keyword.merge(default_opts, opts)
end
This approach has one drawback: If the library component raises an error because of a misconfiguration, the stack trace will point to the wrapper component instead of the template where it is used, making it harder to debug.
A better way would be to just expose a function that returns the default options and use the Flop.Phoenix components directly:
# in the view helper module
def pagination_opts(opts \\ []) do
default_opts = [
ellipsis_attrs: [class: "ellipsis"],
# ...
]
Keyword.merge(default_opts, opts)
end
# in the template
<Flop.Phoenix.pagination for={Pet} event="paginate" opts={pagination_opts()} />
This works fine, but now you have to remember to pass the opts assign everywhere you use the component. It would be nice if you could configure the default opts globally and let Flop.Phoenix handle merging any overrides into the defaults:
# in the view helper module
def pagination_opts do
[
ellipsis_attrs: [class: "ellipsis"],
# ...
]
end
# in the application config
config :flop_phoenix, :pagination, default_opts: {MyApp.ViewHelpers, :pagination_opts}
# in the template, just using the global defaults
<Flop.Phoenix.pagination for={Pet} event="paginate" />
# to override a option
<Flop.Phoenix.pagination for={Pet} event="paginate" opts={[ellipsis_content: "โฅ"]} />
And similarly with the table component.
At the moment, the table
component requires you to pass in a row function, which returns a list of contents that the component then wraps into <td>
elements. This forces LiveView to update the table as a whole, and it is a bit cumbersome to work with if you want to render more complex markup or a function component in a column.
So instead, I'd like to remove the row_func
assign and use let
like this:
<Flop.Phoenix.table items={@items} meta={@meta}>
<:col let={pet} label="Name" field={:name}>
<%= pet.name %>
</:col>
<:col let={pet} label="Age">
<%= pet.age %>
</:col>
</Flop.Phoenix.table>
Currently, the path helper needs to be passed as an mfa tuple to the table
, pagination
and cursor_pagination
components.
<.pagination meta={@meta} path_helper={{Routes, :some_path, [@socket, :index]}} />
Phoenix 1.7 will come with verified routes, allowing you to use the ~p
sigil, which compiles down to a string. flop_phoenix
should allow to either reference a path helper as before, or to pass a path as a string directly.
<.pagination meta={@meta} path={~p"/some/path"} />
The Flop query parameters should be merged into the query parameters of the given path.
build_path/3
: handle string as first argumentvalidate_path_helper_or_event!/2
: update validation to accept stringpath
This issue has two parts:
Flop.Schema
should allow to disable the limit parameter if a default limit is set.FlopPhoenix.pagination
should hide the limit/page_size parameters if limiting is disabled or the default limit is used.See discussion in #8.
I cannot clear a sorting on a column
Phoenix.Form.HTML
At the moment, the filter input label is derived from a keyword list. If a field is used multiple times (e.g. from/to inputs for a date field), the first label in the list is used for all of the fields. The implementation needs to be changed to use the correct label.
The pagination helper should always display links to the first and last page, even if the number of page links is limited with the ellipsis option.
Hey, thanks for sharing the neat Flop libraries!
Would it be possible for Phoenix helpers to work with LiveView instead of HTTP?
Add a width
option to the table :col
, which translates into a colgroup
, in order to prevent column widths changing depending on the column contents when paginating or sorting.
Is your feature request related to a problem? Please describe.
Tailwind becomes more and more popular and it does not rely on predefined classes which is used in every and each table. With Liveview we start encapsulating styled components. In my specific case, when I am using Flop.Phoenix.pagination
I have two issues:
is-current
previous_link
, page_links
and next_link
. For instance I'd like to have the previous_link
before the page_links
and the next_link
at the end.Describe the solution you'd like
The first issue could be fixed with a further customization option. The second issue by allowing to override the content of the pagination
function.
Describe alternatives you've considered
For the moment I am basically overriding the Flop.Phoenix.Pagination
module which isn't nice.
Is your feature request related to a problem? Please describe.
This relates to the general intent of #26 to improve compatibility with Tailwind. I have a table where I am styling the tr
tags with some css classes. flop_phoenix does not allow customization of tr
tags right now. Moreover I have some styling classes for the td
tags in header and body. For the header I could just pass my markup to the headers
key. But then I need to reimplement building a sorting link + direction indicator. For the body I can't pass any td
styling classes right now.
Describe the solution you'd like
Allow addings attributes to tr
and td
tags. A helper for sorting links with direction indicator in addition to build_path/3
might be beneficial.
Describe alternatives you've considered
Right now I am not using the table
generator but just my own markup with an own reimplemented sortable link builder with direction indicator.
Mathias I saw that with the new sort table that you put in the last couple of days you have included some basic column sorting capabilities (I haven't used it, not working with LiveView just yet).
How difficult do you think it would be for you to extract the work you've done to produce a modifiable sort_column
helper to use where we'd like on our own custom table layouts.
It might take at least the signature: sort_column(flop, column_name, display_name, *)
where column_name
is one of the whitelisted values from Flop, and display_name is whatever we'd like. I could use build in icons or any other function we'd like to hold the direction (something like you already have for customizing pagination links would be awesome).
I've been playing around basically looking at:
<table>
<thead>
<th><%= sort_column(@meta, :name, gettext("Name"), *)</th>
<th><%= sort_column(@meta, :active, gettext("Status"), *)</th>
</thead>
<tbody>
</tbody>
</table>
What do you think? Is the work extractable into a more tailored approach separate from the basic sorttable?
Flop.Phoenix.to_query/2
only takes the Flop
struct and options, but not the Meta
struct. Flop.Phoenix.build_path/3
can take a Flop
struct or a Meta
struct and the options. Required changes in Flop.Phoenix
:
Meta
struct is passed to build_path/3
, get config_module
option from struct and put it into the opts
passed to to_query/2
config_module
option in both build_path/3
and to_query/2
By default, Flop.Phoenix puts all parameters into the query parameters when building URLs. This can overridden by passing a custom URL builder function since version 0.15, allowing you to map flop parameters to other URL structures (e.g. /categories/{category_id}/posts/{page}?s={search_term}
). However, when you handle the parameters map in a controller function or in handle_params/3
, you need convert those parameters back to the parameter format that Flop understands. Flop.nest_filters/3
and Flop.map_to_filter_params/2
can partially help with that, but it is still more effort than it should be. Also, you'll have to define both the URL builder and the URL parser function.
It would be great to have some way to define the URL mapping once in a concise format, and have Flop.Phoenix do the building/parsing for you.
Describe the bug
phoenix_html 3.0.0 was recently (like very recently) released and flop_phoenix is pegged to ~> 2.14
which is preventing us from updating.
To Reproduce
Specify phoenix_html to 3.0.0 in your mix file as well as flop_phoenix 0.7.0
Expected behavior
Can update :)
Versions:
The original live_view stream implementation did not have a way to reset the stream to clear it's elements when paginating.
Flop worked around this issue by using the row number instead of the items ID and updating the stream items in place, but the upcoming version of streams now has reset and bulk insert functionality.
Describe the solution you'd like
Use the streams reset to clear existing items when paginating instead of mutating existing rows for clarity. Additionally, a list of items can be passed in instead of iterating the insertion.
Issues
This new version of live_view hasn't been released just yet - this is primarily to track an eventual transition to the more logical APIs now that they exist.
Describe the bug
Hey @woylie,
I currently have a case where filter_fields/1
is a bit to static for us, since we need to render the filters in separate section, since there are so many that we want to group them nicely. The docs suggest that one can use https://hexdocs.pm/flop_phoenix/0.17.0/Flop.Phoenix.html#hidden_inputs_for_filter/1 in combination with inputs_for
to render a single filter field, but I can't even get the example into a workable state and I suspect there is something wrong with it for using this with more than one filter.
<.form :let={f} for={@meta}>
<.hidden_inputs_for_filter form={@form} />
<div class="field-group">
<div class="field">
<%= for ff <- Phoenix.HTML.Form.inputs_for(f, :filters, fields: [:name]) do %>
<.hidden_inputs_for_filter form={ff} />
<.input label="Name" type="text" field={{ff, :value}} />
<% end %>
</div>
<div class="field">
<%= for ff <- Phoenix.HTML.Form.inputs_for(f, :filters, fields: [:email]) do %>
<.hidden_inputs_for_filter form={ff} />
<.input label="E-mail" type="email" field={{ff, :value}} />
<% end %>
</div>
</div>
</.form>
The example produces two form fields, both with the same name/id filters[0][value]
/ flop_filters_0_value
which obviously does not work.
This makes sense, since the to generate the fields we loop over the fields
with Enum.with_index
so both end up with 0
https://github.com/woylie/flop_phoenix/blob/0.17.0/lib/flop_phoenix/form_data.ex#L82-L114
To Reproduce
Steps to reproduce the behavior:
Implement the example from the docs
Expected behavior
Not sure if I just got something wrong, but I think flop currently does not allow what is outlined in the example.
Versions:
Additional context
Maybe we can get that to work if one could pass field:
and index:
as options to the inputs_for
instead of fields
, so one can actually implement something like the example suggests.
Thank you a lot in advance and for this great tool ๐
Cheers
Andi
Describe the solution you'd like
Adding :tbody for further customisation options for the table generation / opts.
Additional context
For getting further use out of tailwindcss, for example adding the claasses divide-y divide-zinc-100 border-t border-zinc-200
would be nice to be easily able to add
At the moment, the components require to set the path_helper
and path_helper_args
assigns.
path_helper={&Routes.pet_path/3}
path_helper_args={[@socket, :index]}
It would be good to support passing a single mfa tuple instead.
path_helper={{Routes, :pet_path, [@socket, :index]}}
When a component is used multiple times in the same view and click events are used for updates, it might be necessary to pass additional parameters besides the page number with the click event.
The sortable table should have an option for a footer. It could work the same way as the header option.
Is your feature request related to a problem? Please describe.
I would like to add an option to the table, to make each row easily clickable with the item available.
Describe the solution you'd like
Add extra attr to table
Describe alternatives you've considered
I've trade adding phx-click
to each col
but I couldn't quite get it working with the params as the :let
didn't exist for the other attrs.
With the assigns map for function components, there is no need to keep the opts
under a separate key. Instead of this:
<Flop.Phoenix.table
items={@pets}
meta={@meta}
path_helper={&Routes.pet_path/3}
path_helper_args={[@socket, :index]}
headers={table_headers()}
row_func={&table_row/2},
opts={[for: MyApp.Pet, socket: @socket]}
/>
We can have this:
<Flop.Phoenix.table
items={@pets}
meta={@meta}
path_helper={&Routes.pet_path/3}
path_helper_args={[@socket, :index]}
headers={table_headers()}
row_func={&table_row/2},
for={MyApp.Pet}
socket={@socket}
/>
At the moment, flop_phoenix
attaches all parameters as query parameters. However, sometimes you might prefer to set the page with a path parameter (/pets/page/2
). Maybe you would even want to set a certain filter parameter in the path (/pets/species/dog
, /articles/category/announcements
). This could probably be achieved by allowing the user to override the default build_path
function. In combination with #111, this would mean that you could pass the path with an mfa tuple pointing to a route helper, a string, or a function that takes a flop and returns a path.
MFA:
<.pagination meta={@meta} path={{Routes, :pets, [@socket, :index]}} />
Unverified route:
<.pagination meta={@meta} path="/pets" />
Verified route:
<.pagination meta={@meta} path={~p"/pets"} />
Custom path builder for page in path:
<.pagination meta={@meta} path={&build_pets_path/1} />
def build_pets_path(params) do
{page, params} = Keyword.pop(params, :page)
{species, params} = Flop.pop_filter(params, :species)
case species do
nil -> ~p"/pets/page/#{page}?#{params}"
species -> ~p"/pets/species/#{species}/page/#{page}?#{params}"
end
end
build_path/3
: handle function as first argumentvalidate_path_helper_or_event!/2
: update validation to accept functionpath
pop_filter/2
Phoenix/LiveView is moving away from the render functions in Phoenix.HTML.Form
(input, label etc.). Flop Phoenix will too. All functions that render HTML should be replaced by Phoenix components, and if render functions are passed as arguments to the components, these functions should be expected to be Phoenix components as well.
Furthermore, I think the only way to fix the warnings in #117 is to only pass the attributes to the inner block of filter_fields/1
, instead of passing the rendered label and input.
filter_label/1
and filter_input/1
filter_hidden_inputs_for/1
with a Phoenix componentfilter_fields/1
to only pass computed assigns to inner block instead of heexIs your feature request related to a problem? Please describe.
I need to pass in additional querystring params keys and values to pagination and sorting helpers so that I can maintain state on GET calls.
I'm blocked adding new features for lists that need to send params through to control their visibility and filter tweaking as they get reset/dropped when the user attempts to paginate or sort.
Describe the solution you'd like
Ideally a simple way to specify keys and values that should be included in the page link builder to the server along with the handled page=1&page_size=15 params that are submitted.
It might be nice also that instead of the whitelist of params that Flop Phoenix builds for its helper links that it includes any other querystring params sent by the server on the last request. It seems that the params that are being built are explicit and only concern the page link helpers.
That do you think Mattias?
Hi Woylie,
I noticed that with the newest release here of 0.6.0, when I'm working with ":page" style pagination (page/page_size) I'm getting errors with the link builder helper in the UI where it is producing a URL of "http://localhost:4000/berries?page=2&limit=15".
On the previous release when I had default_limit
and max_limit
set to 15 each limit
in the URL above would resolve to the correct page_size
param as in "http://localhost:4000/berries?page=2&page_size=15"
This is with both Flop 0.10.0 and 0.9.1. Confirmed that the issue does not exist with flop_phoenix 0.5.0.
Here's what i have in my config.exs
config :flop,
repo: App.Repo,
default_limit: 15,
pagination_types: [:page]
And here's what's being produced when I inspect flop.meta within the build_page_link_helper function:
%Flop{
after: nil,
before: nil,
filters: [],
first: nil,
last: nil,
limit: 15,
offset: nil,
order_by: nil,
order_directions: nil,
page: nil,
page_size: nil
}
Any thoughts here?
Cheers,
Jesse
inputs_for
component, potentially remove hidden_inputs_for
Phoenix.HTML.FormField
struct in filter_fields
componentPhoenix.HTML.Form.form_for/3
in testsinput_type
to consider ecto_type
optionIt is possible to pass default_limit
and default_order
options to the Flop query functions and to Flop.Phoenix.build_path/3
, but the components do not know about the used options when calling build_path/3
, and thus those parameters cannot be removed from the query parameters.
Bootstrap uses a ul
and li
for all parts of the pagination items. The order of next, previous, and list matter.
<nav aria-label="Page navigation example">
<ul class="pagination">
<li class="page-item"><a class="page-link" href="#">Previous</a></li>
<li class="page-item"><a class="page-link" href="#">1</a></li>
<li class="page-item"><a class="page-link" href="#">2</a></li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
<li class="page-item"><a class="page-link" href="#">Next</a></li>
</ul>
</nav>
https://getbootstrap.com/docs/5.0/components/pagination/
The pagination is very configurable but I didn't see how I could configure it to generate the necessary markup.
Thanks for your consideration and happy to contribute given some guidance.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.