ex_admin icon indicating copy to clipboard operation
ex_admin copied to clipboard

Array type doesn't seem to allow clearing all options

Open jeffdeville opened this issue 8 years ago • 7 comments
trafficstars

When I clear the contents of a form array, an 'empty' array isn't posted back to the controller, so the clear doesn't happen. In fact, nothing is posted for the form value at all. This only happens when I'm removing every entry. Removing 1 of n works fine. Adding items works fine as well.

It appears that there's some javascript in here deciding not to post the empty results, but I'm having trouble tracking down where the problem is.

jeffdeville avatar Mar 19 '17 22:03 jeffdeville

@jeffdeville Can you post your model and admin resource file? I need a bit more context to understand. Thanks...

smpallen99 avatar Mar 20 '17 00:03 smpallen99

Sure: Model

defmodule NHWeb.Customer do
  use NHWeb.Web, :model
  import Ecto.Query

  alias NHWeb.{Licensee,License,Company,Repo}

  schema "customers" do
    field :name, :string
    field :about, :string
    field :website, :string
    field :facebook, :string
    field :twitter, :string
    field :urls, {:array, :string}
    field :video, :string
    field :phones, {:array, :string} # unknown phone
    field :office_phone, :string
    field :mobile_phone, :string
    field :fax, :string
    field :featured_image, :string
    field :images, {:array, :string}
    field :valid, :boolean
    field :review_status, :string
    field :reviewed_by, :string
    field :editor_notes, :string

    has_many :customer_terms, NHWeb.CustomerTerm
    has_many :licenses, NHWeb.License
    belongs_to :company, NHWeb.Company
    has_one :listing, NHWeb.Listing
    # many_to_many :terms, NHWeb.Term, through: NHWeb.CustomerTerm

    timestamps()
  end

  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:name, :about, :website, :facebook,
                     :twitter, :video, :phones, :fax, :featured_image,
                     :images, :review_status, :reviewed_by, :editor_notes,
                     :urls])
    |> cast_assoc(:customer_terms)
    |> cast_assoc(:licenses)
    |> cast_assoc(:company)
    |> set_valid
    |> validate_required([:name])
    |> validate_inclusion(:review_status, ~w(new invalid in_process ready))
  end

  def set_valid(changeset) do
    put_change(changeset, :valid, valid?(changeset))
  end

  defp valid?(changeset) do
    do_valid?(Map.merge(changeset.data, changeset.changes))
  end
  defp do_valid?(%{featured_image: image}) when is_nil(image), do: false
  defp do_valid?(%{featured_image: image}), do: String.length(image) > 0
  defp do_valid?(_), do: false

  # **************************************
  # Move this to a context in 1.3 migration
  # **************************************
  @spec existing_licenses(list(map)) :: {:ok, list(NHVerify.License.t)}
  def existing_licenses([]), do: {:ok, []}
  def existing_licenses(licenses) do
    # I guess I break these up by region, otherwise my 'in' queries are messy
    region_lists = group_by_regions(licenses)

    existing_region_licenses = region_lists
    |> Enum.reduce(%{}, fn({region, license_ids}, acc) ->
      query = from l in License,
              where: l.region == ^region,
              where: l.num in ^license_ids,
              select: l
      Map.put(acc, region, Repo.all(query))
    end)
    |> Enum.reduce([], fn({_, vals}, acc) -> acc ++ vals end)
    {:ok, existing_region_licenses}
  end

  defp group_by_regions(licenses) do
    licenses
    |> Enum.reduce(%{}, fn (%{region: region, num: num}, acc) ->
      Map.put(acc, region, Map.get(acc, region, []) ++ [num])
    end)
  end
end

Resource File

defmodule NHWeb.ExAdmin.Customer do
  use ExAdmin.Register
  import Ecto.Query

  register_resource NHWeb.Customer do
    scope :all
    scope :new, fn(q) ->
      from c in q,
      where: c.review_status == "new"
    end
    scope :ready, fn(q) ->
      from c in q,
      where: c.review_status == "ready"
    end

    index do
      selectable_column()
      column :id
      column :name
      column :website
      column :phones
      column :office_phone
      column :featured_image
      # column :licenses, fields: [:num]
      column :review_status
      column :reviewed_by
    end

    form customer do
      inputs do
        input customer, :name
        input customer, :about, type: :text
        input customer, :website
        input customer, :facebook
        input customer, :twitter
        input customer, :urls
        input customer, :video
        input customer, :phones
        input customer, :office_phone
        input customer, :mobile_phone
        input customer, :fax
        input customer, :featured_image
        input customer, :images
        input customer, :editor_notes, type: :text
      end
    end

    member_action :ready, &__MODULE__.ready_action/2, label: "Ready!"

    def ready_action(conn, params) do
      current_user = conn.assigns.current_user

      # changeset_fn = Customer.changeset_fn(defn, :update)

      updated_params = %{review_status: "ready", reviewed_by: current_user.email}
      customer = NHWeb.Repo.get(NHWeb.Customer, params[:id])
      changeset = NHWeb.Customer.changeset(customer, updated_params)

      case NHWeb.Repo.update(changeset) do
        {:error, changeset} ->
          Phoenix.Controller.put_flash(conn, :error, "Customer could not be updated.")
        {:ok, resource} ->
          Phoenix.Controller.put_flash(conn, :notice, "Customer was successfully updated.")
      end
      |> Phoenix.Controller.redirect(to: ExAdmin.Utils.admin_resource_path(conn, :index))
    end
  end
end

And here's a screenshot. If I try and delete this phone number, it won't work, because the phones field isn't posted with the params image

jeffdeville avatar Mar 20 '17 01:03 jeffdeville

Ok, I get how. Thanks for posting. I believe your best bet is to handle this in your changeset. If the data in the changeset has something different than the params, you know its a delete and could add a change to the changeset.

What do you think?

smpallen99 avatar Mar 21 '17 23:03 smpallen99

@smpallen99 The downside to that is that you would have to send back the current array with every single update, which isn't very REST-y.

I guess this works with HTML responders, but for this to be "correct", the client should only patch changed values. In this case, the empty array should be sent with the data.

(Just my unsolicited 2¢)

nkezhaya avatar Mar 22 '17 00:03 nkezhaya

I need to look a little deeper into this, but not tonight.

smpallen99 avatar Mar 22 '17 00:03 smpallen99

seems to be quick fixed in changeset with this code

  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:title, :section, :array1, :array2, :order])
    |> put_change(:array1, Map.get(params, "array1", []))
    |> put_change(:array2, Map.get(params, "array2", []))
  end

d4rk5eed avatar Mar 28 '17 10:03 d4rk5eed

@d4rk5eed your solution won't work, as if we call the changeset like on action edit, the params of that field be always be set to [], and when you just get to edit form, you can't see your selected value because it's always be blank

hoangvn2404 avatar Apr 06 '18 09:04 hoangvn2404