comboFeeds.ex 7.1 KB

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