comboSearch.ex 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. defmodule NITRO.Combo.Search do
  2. require NITRO
  3. require Record
  4. Record.defrecord(:state, lastMsg: [], timer: [], uid: [], pid: [], chunks: 0, value: [], reader: [], opts: [], feed: [])
  5. def start(uid, value, field, feed0, opts) do
  6. feed = :nitro.to_binary(feed0)
  7. state = state(uid: uid, pid: self(), value: value, opts: opts, feed: feed)
  8. stop(uid, field)
  9. pi =
  10. NITRO.pi(
  11. module: NITRO.Combo.Search,
  12. table: :async,
  13. state: state,
  14. name: "comboSearch#{uid}",
  15. timeout: :brutal_kill,
  16. restart: :temporary
  17. )
  18. pid = :erlang.spawn_link(:nitro_pi, :start_link, [pi])
  19. :nitro_pi.cache(:async,{:async,"comboSearch#{uid}"},pid,:infinity)
  20. Process.unlink(pid)
  21. end
  22. def stop(uid, _field) do
  23. case :nitro_pi.pid(:async, "comboSearch#{uid}") do
  24. [] -> :ok
  25. pid ->
  26. :erlang.exit(pid, :kill)
  27. :nitro_pi.cache(:async, {:async, "comboSearch#{uid}"}, :undefined)
  28. end
  29. end
  30. def comboScroll(NITRO.comboScroll(uid: uid)) do
  31. case :nitro_pi.pid(:async, "comboSearch#{uid}") do
  32. [] -> []
  33. pid -> send(pid, {:filterComboValues, :append, []})
  34. end
  35. end
  36. def keyUp(NITRO.comboKey(uid: uid, value: "stop")) do
  37. case :nitro_pi.pid(:async, "comboSearch#{uid}") do
  38. [] -> []
  39. pid ->
  40. send pid, {:stop, uid, self()}
  41. end
  42. end
  43. def keyUp(NITRO.comboKey(uid: uid, value: value, dom: field, feed: feed, delegate: module)) do
  44. opts = [index: NITRO.Combo.index(module), field: field, delegate: module]
  45. comboContainer = :nitro.atom([:comboContainer, :nitro.to_list(field)])
  46. :nitro.display(:nitro.atom([:comboContainer, field]), :block)
  47. :nitro.clear(comboContainer)
  48. :nitro.wire("comboCloseFormById('#{:nitro.atom([:nitro.to_list(field), 'form'])}');")
  49. :nitro.wire("comboLookupChange('#{field}');")
  50. :nitro.wire(NITRO.bind(target: :nitro.to_binary(comboContainer), type: :scroll, postback: onscroll(uid, field, module)))
  51. send(self(), {:direct, NITRO.comboLoader(dom: field, delegate: module, status: :finished)})
  52. start(uid, value, field, feed, opts)
  53. end
  54. def onscroll(uid, field, delegate), do:
  55. :erlang.iolist_to_binary([
  56. "if (event.target && (event.target.scrollTop + event.target.offsetHeight + 10 >= event.target.scrollHeight)) {",
  57. "ws.send(enc(tuple(atom('direct'),
  58. tuple(atom('comboScroll'),",
  59. "bin('#{uid}'),",
  60. "bin('#{field}'),",
  61. "atom('#{delegate}'))",
  62. ")));",
  63. "}"
  64. ])
  65. def proc(:init, NITRO.pi(state: state(value: v, opts: opts, feed: feed, pid: pid) = st) = pi) do
  66. reader = :erlang.apply(:kvs, :reader, [feed])
  67. m = Keyword.get(opts, :delegate, [])
  68. field = Keyword.get(opts, :field, [])
  69. index = Keyword.get(opts, :index, [])
  70. cpx = case NITRO.Combo.has_function(m, :patterns) do
  71. true ->
  72. m.patterns(:string.lexemes(v, [32]))
  73. false ->
  74. value_cps = :binary.split(:string.lowercase(v), [" "], [:global,:trim_all])
  75. |> Enum.map(&:binary.compile_pattern(&1))
  76. input_cp = index
  77. |> Enum.flat_map(
  78. fn (i) when is_function(i) -> [i];
  79. (i) -> [fn o -> :erlang.apply(:kvs, :field, [o,i]) end]
  80. end)
  81. |> Enum.map(fn i -> {i, value_cps} end)
  82. {input_cp, []}
  83. end
  84. opts = Keyword.put(opts, :cpx, cpx)
  85. v = :string.lowercase(v)
  86. send self(), {:filterComboValues, :init, v}
  87. send(pid, {:direct, NITRO.comboLoader(dom: field, delegate: m)})
  88. {:ok, NITRO.pi(pi, state: state(st, opts: opts, reader: reader, lastMsg: :erlang.timestamp(), timer: ping(100)))}
  89. end
  90. def proc({:check}, NITRO.pi(state: state(timer: t, lastMsg: {_, sec, _}) = st) = pi) do
  91. :erlang.cancel_timer(t)
  92. case :erlang.timestamp() do
  93. {_, x, _} when x - sec >= 60 -> {:stop, :normal, NITRO.pi(pi, state: state(st, timer: []))}
  94. _ -> {:noreply, NITRO.pi(pi, state: state(st, timer: ping(10000)))}
  95. end
  96. end
  97. def proc({:stop, _uid, _ref}, NITRO.pi(state: state(opts: opts, pid: pid)) = pi) do
  98. m = Keyword.get(opts, :delegate, [])
  99. field = Keyword.get(opts, :field, [])
  100. send(pid, {:direct, NITRO.comboLoader(dom: field, delegate: m, status: :finished)})
  101. {:stop, :normal, pi}
  102. end
  103. def proc({:filterComboValues, cmd, value0}, NITRO.pi(state: state(chunks: chunks, value: prev) = st) = pi) do
  104. state(uid: uid, feed: feed, reader: r, pid: pid, opts: opts) = st
  105. m = Keyword.get(opts, :delegate, [])
  106. field = Keyword.get(opts, :field, [])
  107. {cpv, cpe} = Keyword.get(opts, :cpx, {[],[]})
  108. value = case cmd do :init -> value0; _ -> prev end
  109. if cmd in [:append] do
  110. send(pid, {:direct, NITRO.comboLoader(dom: field, delegate: m, status: :finished)})
  111. send(pid, {:direct, NITRO.comboLoader(dom: field, delegate: m)})
  112. end
  113. r1 = :erlang.apply(:kvs, :take, [:erlang.apply(:kvs, :setfield, [r, :args, 50])])
  114. case :erlang.apply(:kvs, :field, [r1, :args]) do
  115. [] ->
  116. send(pid, {:direct, NITRO.comboInsert(uid: uid, dom: field, delegate: m, chunks: chunks, status: :finished)})
  117. send(pid, {:direct, NITRO.comboLoader(dom: field, delegate: m, status: :finished)})
  118. {:stop, :normal, NITRO.pi(pi, state: state(st, reader: :erlang.apply(:kvs, :setfield, [r1, :args, []])))}
  119. rows when cpe != [] or cpv != []->
  120. filter_val = fn patterns, r ->
  121. if patterns
  122. |> Enum.map(&{elem(&1,0).(r), elem(&1,1)})
  123. |> Enum.map(&{:nitro.to_binary(elem(&1,0)),elem(&1,1)})
  124. |> Enum.map(&{:string.lowercase(elem(&1,0)),elem(&1,1)})
  125. |> Enum.filter(fn {v1,cps} ->
  126. cps |> Enum.all?(&:binary.match(v1,&1,[]) != :nomatch)
  127. end)
  128. |> Enum.empty?, do: [], else: [r]
  129. end
  130. filtered = if value == "all", do: rows, else: rows |>
  131. Enum.flat_map(fn (row) when cpe == [] -> filter_val.(cpv, row)
  132. (row) ->
  133. if cpe
  134. |> Enum.reduce_while([row], fn {f,c}, acc ->
  135. if :binary.match(f.(row), c, []) == :nomatch, do: {:halt, []}, else: {:cont,acc}
  136. end)
  137. |> Enum.flat_map(&filter_val.(cpv, &1))
  138. |> Enum.empty?, do: [], else: [row]
  139. end)
  140. newChunks = chunks + length(filtered)
  141. send(pid, {:direct, NITRO.comboInsert(uid: uid, dom: field, delegate: m, chunks: newChunks, feed: feed, rows: filtered)})
  142. if chunks < 50, do: send(self(), {:filterComboValues, :continue, value0}),
  143. else: send(pid, {:direct, NITRO.comboLoader(dom: field, delegate: m, status: :finished)})
  144. reader = :erlang.apply(:kvs, :setfield, [r1, :args, []])
  145. {:noreply, NITRO.pi(pi, state: state(st, lastMsg: :erlang.timestamp(), value: value, chunks: newChunks, reader: reader))}
  146. _ ->
  147. send(pid, {:direct, NITRO.comboLoader(dom: field, delegate: m, status: :finished)})
  148. {:noreply, NITRO.pi(pi, state: state(st, lastMsg: :erlang.timestamp(), value: value))}
  149. end
  150. end
  151. def proc(_, pi), do: {:noreply, pi}
  152. def ping(milliseconds), do: :erlang.send_after(milliseconds, self(), {:check})
  153. end