Coder Social home page Coder Social logo

alanvardy / exzeitable Goto Github PK

View Code? Open in Web Editor NEW
221.0 9.0 28.0 4.48 MB

Dynamically updating, searchable, sortable datatables with Phoenix LiveView

License: MIT License

Elixir 84.07% JavaScript 4.91% Shell 0.34% CSS 7.57% HTML 3.11%
ecto elixir phoenix

exzeitable's Introduction

Exzeitable

Build Status Build Status Build Status Build StatusBuild Status codecovhex.pm

Dynamic, live updating data tables generated with just a database query and a module. Ideal for quickly adding CRUD interfaces on an admin backend.

Features:

  • Full-text search
  • Sorting
  • Periodic refresh
  • Bootstrap friendly and easily configured for other CSS frameworks.
  • Customizable everything (and if something isn't, open an issue!)
  • Powered by Phoenix LiveView and Postgres.

Find the documentation at https://hexdocs.pm/exzeitable.

Video

Watch the video

Getting Started

See the Exzeitable video on ElixirCasts for a walkthrough.

Dependencies

This package requires a Postgres database, Phoenix, and Phoenix LiveView.

Add Exzeitable and Phoenix Live View to your list of dependencies in mix.exs.

def deps do
  [
    {:exzeitable, "~> 0.6"},
  ]
end

Migration

Search requires the pg_trgm extension for Postgres.

Generate a new migration file and migrate to add it to Postgres.

mix exzeitable.gen.migration
mix ecto.migrate

Module

Add the boilerplate to a new module.

defmodule YourAppWeb.Live.File do
  @moduledoc "User's File table"
  alias YourAppWeb.Router.Helpers, as: Routes
  import Ecto.Query

  use Exzeitable,
    # Required
    repo: YourApp.Repo,
    routes: Routes,
    path: :file_path,
    action_buttons: [:show, :edit, :custom_button],
    query: from(f in File),
    fields: [
      image: [virtual: true],
      title: [hidden: true],
      description: [hidden: true],
    ],
    
    # Optional
    debounce: 300

  # The callback that renders your table
  def render(assigns), do: ~H"<%= build_table(assigns) %>"

  # Field functions, called when virtual: true or function: true
  def image(socket, file) do
    img_tag(file.url, class: "w-100")
    |> link(to: Routes.file_path(socket, :show, file))
  end
end

We can add options to both the module (as seen above) and the template (As seen below). Template options overwrite module options.

Controller

Controllers are an excellent place to define the base query that forms the default data of the table. Then, everything the table does is with a subset of this data.

query = from(f in Files)
render(conn, "index.html", query: query)

Template

Call the table from your template

<h1> My Awesome Files </h1>
<%= YourAppWeb.Live.File.live_table(@conn, query: @query, action_buttons: [:show, :edit], assigns: %{user_id: @current_user.id}) %>

Note that if you are navigating to the live table using Phoenix LiveView live_session/3 the opts in live_table/3 will not be utilized, and only the module options will apply.

Customizing your table

Required module/template options

  • :repo The module for your repository. Example: YourApp.Repo
  • :routes Your route module. Example: YourAppWeb.Router.Helpers
  • :path The base path for your resource. Example: :site_path
  • :fields A keyword list where the atom is the Ecto field and the value is a keyword list of options. Example: metadata: [label: "Additional Information"]
  • :query An Ecto.Query struct, the part before you give it to the Repo. Example: from(s in Site, preload: [:users])

Optional module/template options

  • action_buttons: [:new, :edit, :show, :delete] A list of atoms representing action buttons available for the user to use. Omitting an atom does not affect authorization, as the routes will still be available.
  • per_page: 20 Integer representing the number of entries per page.
  • debounce: 300 Sets how many milliseconds between responding to user input on the search field.
  • refresh: false Re-queries the database every x milliseconds, defaults to false (disabled).
  • disable_hide: false Disable show/hide functionality for columns, including not showing the buttons.
  • pagination: [:top, :bottom] Whether to show the pagination above and below
  • text: Exzeitable.Text.Default The translation that appears on the table, defaults to English.
  • assigns: %{} Passes additional assigns to socket.assigns. Keep your payload small!
  • query_modifier: {MyModule, :my_function} Passes the query to MyModule.my_function/2, where query can then be dynamically altered before being returned. Arguments are the query, and the Exzeitable.Params struct, which is how Exzeitable stores state. Return value is the query.
defmodule MyApp.MyModule do
  def my_function(query, _state) do
     # Make your modifications and return the new query
    query
  end
end

Field options

Under the fields key, you can define a keyword list of atoms with keyword values. The map holds the options for that field. All of these options are optional.

fields: [
          name: [function: true],
          age: [order: false],
          metadata: [label: "Additional Information", virtual: true, hidden: true],
        ]

The following field options are available (with their defaults):

  • label: nil Set a custom string value for the column heading
  • function: false Pass (socket, entry) to a function with the same name as the field
  • hidden: false Hide the column by default (user can click show button to reveal)
  • search: true Whether to include the column in search results. See the important note below.
  • order: true Do not allow the column to be sorted (hide the sort button)
  • formatter: {Exzeitable.HTML.Format, :format} Specifies a formatter function that will be applied to the column content when formatting. The formatter can be specified as either {Mod, fun} or {Mod, fun, args} (The function will have the original content prepended to the list of args). The default formatter passes through the data without modification.
  • virtual: false This is shorthand for [function: true, search: false, order: false] and will override those settings. Virtual fields are for creating fields that are not database-backed.

IMPORTANT NOTE: Search uses ts_vector, which is performed by Postgres inside the database on string fields. This means that you cannot search fields that are not string type (i.e. integer, datetime, associations, virtual fields). Make sure to set search: false or virtual: true on such fields.

Module/template options for nested routes

Needed to build links where more than one struct is needed, i.e. link("Show Post", to: Routes.user_post_path(@conn, :show, @user, @post))

The official docs if you would like to learn more.

To define belongs_to, you must also define parent (and vice versa).

Continuing the example of users and posts:

resources "/users", UserController do
  resources "/posts", PostController
end

The users Exzeitable do not need the two options below, but the posts Exzeitable does. Because all of its routes are different. We will need the following to make the posts Exzeitable work:

  • belongs_to: :user
  • parent: @user

Make sure that you include the :user_id in your query.

In addition, you will need to pass the parent option in from the template.

CSS

I have added generic classes and almost no CSS styling to make the table as CSS framework agnostic as possible, and thus a user of this library should be able to style the tables to their needs.

I have included a Bootstrap SASS example in the CSS Module

Contributing

Opening Issues and Pull Requests

Suggestions, bug reports, and contributions are very welcome! However, please open an issue before starting on a pull request, as I would hate to have any of your efforts be in vain.

Getting set up

This project uses the asdf version manager and docker-compose.

If you would like to contribute, fork the repo on GitHub and then head over to your terminal.

# Clone the project from your GitHub fork
git clone [email protected]:yourname/exzeitable.git
cd exzeitable

# Start postgres
docker-compose up -d

# Install dependencies
asdf install
mix deps.get

# Build assets, and run the test suite
mix check

exzeitable's People

Contributors

alanvardy avatar corneliakelinske avatar dependabot-preview[bot] avatar dependabot[bot] avatar greg-rychlewski avatar kipcole9 avatar zmagod avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

exzeitable's Issues

Getting no function clause matching in Exzeitable.HTML.Search.build/1 when trying to build a table

Describe the bug
When using the example code in the readme I get no function clause matching in Exzeitable.HTML.Search.build/1 whenever I try to render the page.

To Reproduce
Steps to reproduce the behaviour:

  1. Follow example code to create a table replacing all needed information with what is needed
  2. Navigate to page
  3. Get error

Expected behaviour
Page draws table

Screenshots
image

Additional context
Below is the code in my live_view module.

defmodule GlimeshWeb.GctLive.Components.AuditLogTableNew do

  alias GlimeshWeb.Router.Helpers, as: Routes
  import Ecto.Query

  use Exzeitable,
    # Required
    repo: Glimesh.Repo,
    routes: Routes,
    path: :gct_path,
    query: from(a in Glimesh.CommunityTeam.AuditLog),
    fields: [
      target: [label: "Target"]
    ]


  # The callback that renders your table
  def render(assigns) do
    ~L"""
    <%= build_table(assigns) %>
    """
  end
end

Disable hide functionality

Is your feature request related to a problem? Please describe.
The current layout of the table including the allowed configuration options does not specify any option to disable the hide functionality. There are definitely tables where the user does not need to be able to hide columns of a table on the fly.

Describe the solution you'd like
I would like to have an option "disable_hide" which will remove all "hide" button on each column, and also remove the "show columns buttons" where I can reactivate hidden columns.

Describe alternatives you've considered
Currently I am using plain CSS to hide these fields. It is way cleaner to just not render or send this HTML to the user though.

Additional context
It is important that the "disable_hide" option does not disable the functionality to set columns to "hidden". There are tables where I do not show specific columns at all to the user but the search/query send from the table filter/search bar should still be able to query all this column.

Add codecov

Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

Describe the solution you'd like
A clear and concise description of what you want to happen.

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

Additional context
Add any other context or screenshots about the feature request here.

Replace the following with a mix command to generate the migration

Search requires the pg_trgm extension for Postgres.

Create a new migration

mix ecto.gen.migration add_pg_trgm

And add the following code to your migration file

  def up do
    execute("CREATE EXTENSION pg_trgm")
  end

  def down do
    execute("DROP EXTENSION pg_trgm")
  end

Then migrate

mix ecto.migrate

Prefix string with Regex

From: #249 (comment)

@greg-rychlewski said:

I had an alternative idea using regex instead of string splitting/joining that you might be interested in:

  terms
  |> String.trim()
  |> String.replace(~r/[^\w\s]|_/u, "")
  |> String.replace(~r/\s+/u, ":* & ")
  |> Kernel.<>(":*")

I ran some tests using benchee and it looks like it performs much better with small strings, slightly better with medium strings and worse with large strings:

Name                            ips        average  deviation         median         99th %
regex_small_no_space       135.38 K        7.39 μs   ±167.45%           7 μs          21 μs
regex_small_space          114.22 K        8.75 μs    ±87.18%           8 μs          27 μs
enum_small_no_space         78.30 K       12.77 μs    ±44.55%          12 μs          31 μs
enum_small_space            65.22 K       15.33 μs    ±53.12%          14 μs          37 μs
regex_medium_no_space       56.17 K       17.80 μs    ±41.26%          17 μs          38 μs
enum_medium_no_space        46.07 K       21.71 μs    ±40.90%          20 μs          49 μs
regex_medium_space          24.61 K       40.63 μs    ±21.97%          39 μs          79 μs
enum_medium_space           17.01 K       58.78 μs    ±29.01%          52 μs         121 μs
enum_large_no_space          5.65 K      177.05 μs    ±16.47%         170 μs         317 μs
regex_large_no_space         5.28 K      189.29 μs    ±13.17%         182 μs      321.90 μs
enum_large_space             1.34 K      748.72 μs    ±17.60%         713 μs     1263.56 μs
regex_large_space            1.07 K      937.20 μs     ±9.85%         921 μs     1313.00 μs
small_string_no_space = "~roger!federer$"
small_string_space = "  ~roger!   federer$   "

medium_string_no_space =
  "RogerFedererisaSwissprofessionaltennisplayerwhoisrankedworldNo.4inmen'ssinglestennisbytheAssociationofTennisProfessionals(ATP).[3]"

medium_string_space =
  "  Roger Federer is a Swiss professional tennis player who is ranked world No. 4 in men's singles tennis by the Association of Tennis Professionals (ATP).[3]   "

large_string_no_space =
  "RogerFedererisaSwissprofessionaltennisplayerwhoisrankedworldNo.4inmen'ssinglestennisbytheAssociationofTennisProfessionals(ATP).[3]Hehaswon20GrandSlamsinglestitles—themostinhistoryforamaleplayer—andhasheldtheworldNo.1spotintheATPrankingsforarecordtotalof310weeks(includingarecord237consecutiveweeks)andwastheyear-endNo.1fivetimes,includingfourconsecutive.Federer,whoturnedprofessionalin1998,wascontinuouslyrankedinthetop10fromOctober2002toNovember2016.FedererhaswonarecordeightWimbledonmen'ssinglestitles,sixAustralianOpentitles,fiveUSOpentitles(allconsecutive,arecord),andoneFrenchOpentitle.HeisoneofeightmentohaveachievedaCareerGrandSlam.Federerhasreachedarecord31men'ssinglesGrandSlamfinals,including10consecutivelyfromthe2005WimbledonChampionshipstothe2007USOpen.FedererhasalsowonarecordsixATPFinalstitles,28ATPTourMasters1000titles,andarecord24ATPTour500titles.FedererwasamemberofSwitzerland'swinningDavisCupteamin2014.HeisalsotheonlyplayerafterJimmyConnorstohavewon100ormorecareersinglestitles,aswellastoamass1,200winsintheOpenEra.Federer'sall-courtgameandversatilestyleofplayinvolveexceptionalfootworkandshot-making.[4]Effectivebothasabaselinerandavolleyer,hisapparenteffortlessnessandefficientmovementonthecourthavemadeFedererhighlypopularamongtennisfans.HehasreceivedthetourSportsmanshipAward13timesandbeennamedtheATPPlayeroftheYearandITFWorldChampionfivetimes.HehaswontheLaureusWorldSportsmanoftheYearawardarecordfivetimes,includingfourconsecutiveawardsfrom2005to2008.HeisalsotheonlypersontohavewontheBBCOverseasSportsPersonalityoftheYearawardfourtimes."

large_string_space =
  "  Roger Federer is a Swiss professional tennis player who is ranked world No. 4 in men's singles tennis by the Association of Tennis Professionals (ATP).[3] He has won 20 Grand Slam singles titles—the most in history for a male player—and has held the world No. 1 spot in the ATP rankings for a record total of 310 weeks (including a record 237 consecutive weeks) and was the year-end No. 1 five times, including four consecutive. Federer, who turned professional in 1998, was continuously ranked in the top 10 from October 2002 to November 2016. Federer has won a record eight Wimbledon men's singles titles, six Australian Open titles, five US Open titles (all consecutive, a record), and one French Open title. He is one of eight men to have achieved a Career Grand Slam. Federer has reached a record 31 men's singles Grand Slam finals, including 10 consecutively from the 2005 Wimbledon Championships to the 2007 US Open. Federer has also won a record six ATP Finals titles, 28 ATP Tour Masters 1000 titles, and a record 24 ATP Tour 500 titles. Federer was a member of Switzerland's winning Davis Cup team in 2014. He is also the only player after Jimmy Connors to have won 100 or more career singles titles, as well as to amass 1,200 wins in the Open Era. Federer's all-court game and versatile style of play involve exceptional footwork and shot-making.[4] Effective both as a baseliner and a volleyer, his apparent effortlessness and efficient movement on the court have made Federer highly popular among tennis fans. He has received the tour Sportsmanship Award 13 times and been named the ATP Player of the Year and ITF World Champion five times. He has won the Laureus World Sportsman of the Year award a record five times, including four consecutive awards from 2005 to 2008. He is also the only person to have won the BBC Overseas Sports Personality of the Year award four times.   "

regex = fn terms ->
  terms
  |> String.trim()
  |> String.replace(~r/[^\w\s]/u, "")
  |> String.replace(~r/\s+/u, ":* & ")
  |> Kernel.<>(":*")
end

enum = fn terms ->
  terms
  |> String.split()
  |> Enum.map(fn term ->
    String.replace(term, ~r/\W|_/u, "") <> ":*"
  end)
  |> Enum.join(" & ")
end

Benchee.run(%{
  "regex_small_no_space" => fn -> regex.(small_string_no_space) end,
  "enum_small_no_space" => fn -> enum.(small_string_no_space) end,
  "regex_small_space" => fn -> regex.(small_string_space) end,
  "enum_small_space" => fn -> enum.(small_string_space) end,
  "regex_medium_no_space" => fn -> regex.(medium_string_no_space) end,
  "enum_medium_no_space" => fn -> enum.(medium_string_no_space) end,
  "regex_medium_space" => fn -> regex.(medium_string_space) end,
  "enum_medium_space" => fn -> enum.(medium_string_space) end,
  "regex_large_no_space" => fn -> regex.(large_string_no_space) end,
  "enum_large_no_space" => fn -> enum.(large_string_no_space) end,
  "regex_large_space" => fn -> regex.(large_string_space) end,
  "enum_large_space" => fn -> enum.(large_string_space) end
})

Originally posted by @greg-rychlewski in #249 (comment)

Update node

.tool-versions

elixir 1.9.4
erlang 22.1.8
ruby 2.6.3
nodejs 12.6.0

phoenix_static_buildpack.config

# phoenix_static_buildpack.config

# Use phoenix 1.3 executable
phoenix_ex=phx

# Clean out cache contents from previous deploys
clean_cache=true

node_version=12.6.0
npm_version=6.9.0

Support fields from associations

Hi!

This looks like a cool library. Thank you so much for writing it! I think this is something that is badly needed in Elixirland. I was planning to use it until I (alas!) found a show stopper in my use case scenario.

I have a schema with associations, and I would like to show the main schema fields along with some fields from the associations, and make them searchable and sortable.

For example:

schema "user"do 
   field(:name, :string)

   belongs_to(:organization, Organizaton)
end

schema "organization" do 
   field(:name, :string)
end

In my Exzeitable table, I'd like to show both the user's name as well as their organization's names. I was able to make it work with this:

use Exzeitable,
    repo: DataIngestion.Repo,
    routes: Routes,
    query:
      from(u in User
        preload: [:organization]
      ),
    fields: [
      name: [label: "User name"],
      org_name: [function: true, label: "Organizaiton Name"]
   ]

def org_name(_socket, entry) do
   entry.organization.name
end

This allows me to display the organization's name just fine 👍 However, the field can't neither be sorted or searched because Exzeitable generates the search and sort queries assuming that org_name is a field of the user schema 👎

It seems to me that some support for associations would be in order here. I haven't thought carefully about the details, but maybe it would be enough to implement an association option for fields. Something like:

query: (from u in User),
fields: [
      name: [label: "User name"],
      org_name: [association: {:organization, :name},  label: "Organizaiton Name"]
   ]

That way, Exzeitable would know that it would have to preload the organization association and use its name field as the value of the org_name field in the table. It might work, but as I said, I haven't thought it through.

Anyway, let me know what you think.

Clear previous order_by

Using a query that already has the :order_by set results in an error.

Need to clear it and reset in database.ex

Make "Actions" header title of table translatable

Is your feature request related to a problem? Please describe.
As of the new version 0.4.0 nearly all buttons or texts are translatable except for the header bar "Actions" column.

Describe the solution you'd like
Include the "Actions" text in the default text file and make it customisable.

Describe alternatives you've considered
Implement a general option to translate all header columns for several languages. Will definitely be a bigger change than only the "Actions" text.

Add option to hide the top or bottom pagination

Is your feature request related to a problem? Please describe.
For esthetic I want to remove the top pagination as it looks very weird if the table is empty. Normal users are fine with one pagination at the bottom of the table.

Describe the solution you'd like
Add an option "hide_top/bottom_pagination" which will remove the pagination as specified.

Describe alternatives you've considered
I currently hide the top pagination via CSS by using the :first-child but it would be cleaner if they are not rendered into the HTML at all.

A mechanism to pass through assigns when creating a new table

I would like to be able to pass my own map of custom assigns.

That could be referenced in any of the function fields.

Something along the lines of (in my template):

<%= MateTable.live_table(@conn, assigns: %{current_user: get_current_user(@conn)) %>

And then in my table


  use Exzeitable,
    repo: MyApp.Repo,
    routes: Routes,
    path: :mate_path,
    fields: [
      stuff: [function: true]
    ],
    query: preload(Mate, :user),
    action_buttons: []

  def stuff(socket, mate) do
    # I need Socket{assigns: %{current_user: current_user }} to be present.
    View.render("_actions.html", %{socket: socket, mate: mate})
  end

Thank you, kind sir!

Add option to disable / not include the Actions column

I have an exzeitable with no actions.

I can pass actions: [] so that no buttons are rendered, but that actions column is still there.

Could an option be added to not display the actions column at all?

Possibly actions: false

Thanks!

Add tests

  • Create 2 views with loaded livetables
  • Add integration tests for the two views
  • Add cypress for form interactions

Test refresh feature

Find a way to test the refresh feature, preferably using phoenix liveview tests

i18n functionality via gettext

Is your feature request related to a problem? Please describe.
The whole table functionality is great but as many sites are served in multiple languages it is necessary that also the table buttons, fields and placeholders can be translatted.

Describe the solution you'd like
I want to be able to somewhere save separate files for each language to replace the default english text depended on the provided language of Phoenix.

Additional context
It would be great if I could use the normal gettext functionality in form of a further .po file.
If that is not possible it would be great to override maybe the default messages like Pow does it: https://hexdocs.pm/pow/README.html#features

Maybe squish any assigns onto the socket.assigns

As discussed, maybe change those function field functions back to arity/2

And smush any passed assigns into the socket.assigns

So it's just %Socket{assigns: %{current_user_id: 3} } instead of %Socket{assigns: %{assigns: %{current_user_id: 3 } } } when IO.inspect(socket)

Localise the data content (like field names and actions)

Its great to see that the UI chrome can be customised with the :text option which can therefore make it straight forward to localise the table text. However the field names and actions appear to be static text?

This feature request is to support localised labels, actions and formatting for data content:

  1. Localised field labels. One option would be to call gettext on the existing label and add a gettext module to the table configuration to trigger calling gettext for the labels.
  2. Localised actions (edit, delete, ...). Similarly, call gettext if a gettext module is configured
  3. Localised content. I recognise that this can be achieved by defining all the fields as :virtual and implementing a function for each field. But perhaps a :formatter option for the :field definition could be added that would make this configurable.

With these suggestions, a configuration could look like:

defmodule YourAppWeb.Live.File do
  @moduledoc "User's File table"
  alias YourAppWeb.Router.Helpers, as: Routes
  import Ecto.Query

  use Exzeitable,
    # Required
    repo: YourApp.Repo,
    gettext: YourApp.Gettext,
    routes: Routes,
    path: :file_path,
    action_buttons: [:show, :edit, :custom_button],
    query: from(f in File),
    fields: [
      image: [virtual: true],
      title: [hidden: true, formatter: &Cldr.to_string/1],
      description: [hidden: true],
    ]

Which is completely backwards compatible but has the following behaviour because :gettext is specified:

  1. Labels are translated
  2. Action buttons are translated
  3. The :title field is formatted before presentation with Cldr.to_string/1 (which in this case happens to localise dates, times, units of measure, money, .....)

If this is something you see as being in scope for the project, I will consider creating a PR along these lines.

BTW, found your project via elixircasts - what a great find, really happy you built this.

Improve documentation

  • Add phoenix live view to dependency list
  • Remove the massive content from exzeitable.ex
  • Make the module boilerplate smaller
  • Find out why docs are not linking to code

Does not work within an existing live session or live view

Describe the bug

Does not work from within an existing LiveView. I believe this is because Exzeitable is expecting its own mount function to run, when any existing LiveView will likely have implemented this directly. I believe this is exactly the reason they overhauled the lifecycle of mount with on_mount.

To Reproduce

Create a module with use Exzeitable ... per the documentation, but in the router put this under a live_session block and routed using live, using a browser go to the route.

Expected behaviour

Should not fail due to missing assigns

Phoenix 1.6 compatibility

Failed to use "phoenix" (versions 1.6.0 and 1.6.1) because
  exzeitable (version 0.4.6) requires ~> 1.5.0

Not sure if this is a bug or a feature, but please update to be compatible with Phoenix 1.6.
Thx. :-)

[Close Me] Neat, Thanks!

I've been working on something nearly identical but it's going slow as I'm new to elixir, great to see someone is on the same track, great to compare approaches and patterns. Wanted a replacement for ex_admin using live views, seems like I'm not the only one!

Screen Shot 2019-11-16 at 3 42 02 AM

Mine is still WIP and I'll get it on github in the future.

In the meantime thanks for making this and pushing it to hex!

Fix CSS docs

.lt-search-field-wrapper {
  @extend .input-group;
}

Searching for multiple words

Describe the bug
When I search for multiple words, i.e. "post number", it doesn't return entries containing "post number".

To Reproduce

  1. Clone this repo
  2. Download dependencies/run migrations/insert seed data
  3. Start app
  4. Search for "post number"

Expected behaviour
Should see all entries containing "post number" under the column "title".

Screenshots
If I search for "post", it looks fine:

Screen Shot 2020-08-25 at 12 20 17 PM

If I search for "post number", there are no entries:

Screen Shot 2020-08-25 at 12 20 25 PM

Additional context
Looking at the source code, it seems like this is the culprit:

def prefix_search(term) do
  String.replace(term, ~r/\W|_/u, "") <> ":*"
end

It squishes all the words together and searches in the ts_vector for prefixes that match it.

If you want to look for all the words, it looks like you need to first separate the words and then apply :* to each one and separate them by &. I'm happy to work on a PR if this is something you want changed.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.