Browse Source

Merge pull request #88 from synrc/cmb

update search combo matching
Andrii Zadorozhnii 2 years ago
parent
commit
1d49da7cbb
2 changed files with 88 additions and 34 deletions
  1. 69 33
      lib/comboSearch.ex
  2. 19 1
      priv/js/comboLookup.js

+ 69 - 33
lib/comboSearch.ex

@@ -6,7 +6,7 @@ defmodule NITRO.Combo.Search do
 
   def start(uid, value, field, feed0, opts) do
     feed = :nitro.to_binary(feed0)
-    state = state(uid: uid, pid: self(), value: value, reader: :erlang.apply(:kvs, :reader, [feed]), opts: opts, feed: feed)
+    state = state(uid: uid, pid: self(), value: value,  opts: opts, feed: feed)
     stop(uid, field)
     pi =
       NITRO.pi(
@@ -38,6 +38,14 @@ defmodule NITRO.Combo.Search do
     end
   end
 
+  def keyUp(NITRO.comboKey(uid: uid, value: "stop")) do 
+    case :nitro_pi.pid(:async, "comboSearch#{uid}") do
+      [] -> []
+      pid ->
+        send pid, {:stop, uid, self()}
+    end
+  end
+
   def keyUp(NITRO.comboKey(uid: uid, value: value, dom: field, feed: feed, delegate: module)) do
     opts = [index: NITRO.Combo.index(module), field: field, delegate: module]
     comboContainer = :nitro.atom([:comboContainer, :nitro.to_list(field)])
@@ -62,9 +70,29 @@ defmodule NITRO.Combo.Search do
       "}"
     ])
 
-  def proc(:init, NITRO.pi(state: state(value: v) = st) = pi) do
-    send(self(), {:filterComboValues, :init, v})
-    {:ok, NITRO.pi(pi, state: state(st, lastMsg: :erlang.timestamp(), timer: ping(100)))}
+  def proc(:init, NITRO.pi(state: state(value: v, opts: opts, feed: feed, pid: pid) = st) = pi) do
+    v = :string.lowercase(v)
+    reader = :erlang.apply(:kvs, :reader, [feed])
+
+    m     = Keyword.get(opts, :delegate, [])
+    field = Keyword.get(opts, :field, [])
+    index = Keyword.get(opts, :index, [])
+      |> Enum.flat_map(
+          fn (i) when is_function(i) -> [i];
+             (i) -> [fn o -> :erlang.apply(:kvs, :field, [o,i]) end]
+          end)
+
+    cps = :binary.split(v, [" "], [:global,:trim_all]) 
+        |> Enum.map(&:binary.compile_pattern(&1))
+
+    opts = Keyword.put(opts, :index, index)
+    opts = Keyword.put(opts, :cps, cps)
+
+    send self(), {:filterComboValues, :init, v}
+
+    send(pid, {:direct, NITRO.comboLoader(dom: field, delegate: m)})
+
+    {:ok, NITRO.pi(pi, state: state(st, opts: opts, reader: reader, lastMsg: :erlang.timestamp(), timer: ping(100)))}
   end
 
   def proc({:check}, NITRO.pi(state: state(timer: t, lastMsg: {_, sec, _}) = st) = pi) do
@@ -75,50 +103,58 @@ defmodule NITRO.Combo.Search do
     end
   end
 
-  def proc({:filterComboValues, cmd, value0}, NITRO.pi(state: state(chunks: chunks) = st) = pi) do
-    state(uid: uid, feed: feed, reader: r, value: prev, pid: pid, opts: opts) = st
-    m = Keyword.get(opts, :delegate, [])
+  def proc({:stop, _uid, _ref}, NITRO.pi(state: state(opts: opts, pid: pid)) = pi) do
+    m  = Keyword.get(opts, :delegate, [])
     field = Keyword.get(opts, :field, [])
-    value = case cmd do :init -> :string.lowercase(:unicode.characters_to_list(value0, :unicode)); _ -> prev end
-    cmd in [:init, :append] and send(pid, {:direct, NITRO.comboLoader(dom: field, delegate: m)})
+
+    send(pid, {:direct, NITRO.comboLoader(dom: field, delegate: m, status: :finished)})
+
+    {:stop, :normal, pi}
+  end
+
+  def proc({:filterComboValues, cmd, value0}, NITRO.pi(state: state(chunks: chunks, value: prev) = st) = pi) do
+    state(uid: uid, feed: feed, reader: r, pid: pid, opts: opts) = st
+    m     = Keyword.get(opts, :delegate, [])
+    field = Keyword.get(opts, :field, [])
+    index = Keyword.get(opts, :index, [])
+    cps   = Keyword.get(opts, :cps, [])
+    value = case cmd do :init -> value0; _ -> prev end
+
+    if cmd in [:append] do
+      send(pid, {:direct, NITRO.comboLoader(dom: field, delegate: m, status: :finished)})
+      send(pid, {:direct, NITRO.comboLoader(dom: field, delegate: m)})
+    end
+
     r1 = :erlang.apply(:kvs, :take, [:erlang.apply(:kvs, :setfield, [r, :args, 10])])
     case :erlang.apply(:kvs, :field, [r1, :args]) do
       [] ->
         send(pid, {:direct, NITRO.comboInsert(uid: uid, dom: field, delegate: m, chunks: chunks, status: :finished)})
         send(pid, {:direct, NITRO.comboLoader(dom: field, delegate: m, status: :finished)})
+
         {:stop, :normal, NITRO.pi(pi, state: state(st, reader: :erlang.apply(:kvs, :setfield, [r1, :args, []])))}
       rows ->
-        filtered =
-          case value do
-            'all' -> rows
-            _ ->
-              vals = :string.split(:string.trim(value), " ", :all)
-              if length(vals) > 1, do:
-                :lists.filter(fn x -> :lists.all(fn v ->:lists.any(&filter(v, x, &1), Keyword.get(opts, :index, [])) end, vals) end, rows),
-              else:
-                :lists.filter(fn x -> :lists.any(&filter(value, x, &1), Keyword.get(opts, :index, [])) end, rows)
-          end
+        filtered = if value == "all", do: rows, else: rows |>
+          Enum.flat_map(fn row ->
+            if index
+              |> Enum.map(&(:string.lowercase(&1.(row))))
+              |> Enum.filter(fn r -> cps |> Enum.all?(&:binary.match(r,&1,[]) != :nomatch) end)
+              |> Enum.empty?, do: [], else: [row]
+          end)
+
         newChunks = chunks + length(filtered)
+
         send(pid, {:direct, NITRO.comboInsert(uid: uid, dom: field, delegate: m, chunks: newChunks, feed: feed, rows: filtered)})
-        if chunks < 100, do:
-          (case :nitro_pi.pid(:async, "comboSearch#{uid}") do [] -> []; pid -> send(pid, {:filterComboValues, :continue, value0}) end),
+
+        if chunks < 50, do:  send(self(), {:filterComboValues, :continue, value0}),
         else: send(pid, {:direct, NITRO.comboLoader(dom: field, delegate: m, status: :finished)})
-        {:noreply, NITRO.pi(pi, state: state(st, lastMsg: :erlang.timestamp(), value: value, chunks: newChunks, reader: :erlang.apply(:kvs, :setfield, [r1, :args, []])))}
+
+        reader = :erlang.apply(:kvs, :setfield, [r1, :args, []])
+
+        {:noreply, NITRO.pi(pi, state: state(st, lastMsg: :erlang.timestamp(), value: value, chunks: newChunks, reader: reader))}
     end
   end
 
   def proc(_, pi), do: {:noreply, pi}
 
   def ping(milliseconds), do: :erlang.send_after(milliseconds, self(), {:check})
-
-  defp filter(val, obj, i) do
-    fld =
-      if is_function(i) do
-        i.(obj)
-      else
-        fldVal = :erlang.apply(:kvs, :field, [obj, i])
-        if fldVal == elem(obj, 0), do: :nitro.to_list(fldVal), else: fldVal
-      end |> NITRO.Combo.to_list()
-    fld != elem(obj, 0) and :string.rstr(:string.lowercase(:nitro.to_list(fld)), val) > 0
-  end
 end

+ 19 - 1
priv/js/comboLookup.js

@@ -171,8 +171,13 @@ function comboLookupClick(uid, dom, feed, mod) {
 }
 
 function comboLookupKeydown(uid, dom, feed, mod) {
+    if (event.key == 'Meta')  { return }
+    if (event.key == 'Alt')   { return }
+    if (event.key == 'Shift') { return }
+    if (event.key == 'Enter') { return }
     var dropdown = qi(dom).closest('.dropdown');
     var char = event.which || event.keyCode;
+    if (qi(dom).value == '' && event.key == 'Backspace') { return }
     if (char == 40 && !activeCombo && qi(dom).value == '') {
         activeCombo = dom;
         currentItem = undefined;
@@ -204,10 +209,23 @@ function comboLookupKeydown(uid, dom, feed, mod) {
 
 function comboLookupKeyup(uid, dom, feed, mod) {
     var dropdown = qi(dom).closest('.dropdown')
+    if (event.key == 'Meta')  { return }
+    if (event.key == 'Alt')   { return }
     if (event.key == 'Shift') { return }
+    if (event.key == 'Enter') { return }
     if (event.key == 'Tab') { dropdown.classList.remove('dropdown-open'); return }
     var char = event.which || event.keyCode;
-    if (char == 27 || (char == 8 || char == 46) && qi(dom).value == '') { clearInput(dom); return }
+    if (char == 27 || (char == 8 || char == 46) && qi(dom).value == '') { 
+      if(event.key == 'Backspace') {
+        direct(tuple(atom('comboKey'),
+                     bin(uid),
+                     bin('stop'),
+                     string(dom),
+                     string(feed),
+                     atom(mod)));
+      }
+      clearInput(dom); return 
+    }
     if (char == 13 && currentItem) { currentItem.click(); return }
     if ([33, 34, 37, 39].includes(char)) { return }
     if (activeCombo && [35, 36, 38, 40].includes(char)) { return }