nested_array 2.3.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,22 +11,19 @@ module NestedArray::Nested
11
11
  # Имена полей для получения/записи информации, чувствительны к string/symbol
12
12
  id: 'id',
13
13
  parent_id: 'parent_id',
14
+
14
15
  children: 'children',
15
16
  level: 'level',
16
17
 
17
18
  # Параметры для преобразования в nested
18
19
  hashed: false,
19
- add_level: false,
20
20
  root_id: nil,
21
+ branch_id: nil,
21
22
 
22
23
  # Параметры для преобразования в html
23
24
  tabulated: true,
24
25
  inline: false,
25
26
  tab: "\t",
26
- ul: '<ul>',
27
- _ul: '</ul>',
28
- li: '<li>',
29
- _li: '</li>',
30
27
 
31
28
  # Параматры для "склеивания" вложенных структур
32
29
  path_separator: '-=path_separator=-',
@@ -35,140 +32,74 @@ module NestedArray::Nested
35
32
  # Настройки формирования массива для опций тега <select>
36
33
  option_value: 'id', # Что брать в качестве значений при формировании опций селекта.
37
34
  option_text: 'name',
38
- }
39
- end
35
+ thin_option: false,
36
+ pseudographics: %w(┳ ━ ╸ ┣ ┗ &nbsp; ┃),
37
+ thin_pseudographics: %w(┬ ─ ╴ ├ └ &nbsp; │),
40
38
 
41
- #
42
- # Перебирает вложенную стуктуру.
43
- #
44
- def each_nested options={}
45
- options = NESTED_OPTIONS.merge options
46
- level = 0
47
- cache = []
48
- cache[level] = self.clone
49
- parents = []
50
- parents[level] = nil
51
- i = []
52
- i[level] = 0
53
- while level >= 0
54
- node = cache[level][i[level]]
55
- i[level]+= 1
56
- if node != nil
57
- is_last_children = cache[level][i[level]].blank?
58
-
59
- yield(node.clone, parents.clone, level, is_last_children, node.origin)
60
-
61
- if !node[options[:children]].nil? && node[options[:children]].length > 0
62
- level+= 1
63
- parents[level] = node.clone
64
- cache[level] = node[options[:children]]
65
- i[level] = 0
66
- end
67
- else
68
- parents[level] = nil
69
- level-= 1
70
- end
71
- end
72
- self
73
- end
74
-
75
- def each_nested! options={}
76
- options = NESTED_OPTIONS.merge options
77
- level = 0
78
- cache = []
79
- cache[level] = self
80
- parents = []
81
- parents[level] = nil
82
- i = []
83
- i[level] = 0
84
- while level >= 0
85
- node = cache[level][i[level]]
86
- i[level]+= 1
87
- if node != nil
88
- is_last_children = cache[level][i[level]].blank?
39
+ # Выводить html теги от раскрывающегося списка на основе тега details.
40
+ details: false,
89
41
 
90
- yield(node, parents, level, is_last_children, node.origin)
42
+ ul: '<ul>',
43
+ _ul: '</ul>',
44
+ li: '<li>',
45
+ _li: '</li>',
91
46
 
92
- if !node[options[:children]].nil? && node[options[:children]].length > 0
93
- level+= 1
94
- parents[level] = node
95
- cache[level] = node[options[:children]]
96
- i[level] = 0
97
- end
98
- else
99
- parents[level] = nil
100
- level-= 1
101
- end
102
- end
103
- self
47
+ uld: '<details><summary></summary><ul>',
48
+ uldo: '<details open><summary></summary><ul>',
49
+ _uld: '</ul></details>'
50
+ }
104
51
  end
105
52
 
106
- def to_nested options={}
53
+ def to_nested(options = {})
107
54
  options = NESTED_OPTIONS.merge options
55
+ # Зарезервированные поля узла.
108
56
  fields = {
109
57
  id: options[:id],
110
58
  parent_id: options[:parent_id],
111
59
  level: options[:level],
112
60
  children: options[:children],
113
61
  }
114
- fields.delete :level if !options[:add_level]
115
62
  cache = {}
116
63
  nested = options[:hashed] ? {} : []
117
64
  # Перебираем элементы в любом порядке!
118
- self.each do |value|
119
- origin = value
120
- value = value.serializable_hash if !value.is_a? Hash
65
+ self.each do |origin|
66
+ value = origin.is_a?(Hash) ? origin : origin.serializable_hash
121
67
  # 1. Если нет родителя текущего элемента, и текущий элемент не корневой, то:
122
68
  # 1.1 создадим родителя
123
69
  # 1.2 поместим в кэш
124
- if !(cache.key? value[fields[:parent_id]]) && (value[fields[:parent_id]] != options[:root_id])
70
+ if !(cache.key? value[options[:parent_id]]) && (value[options[:parent_id]] != options[:root_id])
125
71
  # 1.1
126
72
  temp = OpenStruct.new
127
- fields.each do |key, field|
128
- case key
129
- when :id
130
- temp[field] = value[fields[:parent_id]]
131
- when :children
132
- # не создаём поле
133
- else
134
- temp[field] = nil
135
- end
136
- end
73
+ temp[options[:id]] = value[options[:parent_id]]
74
+ temp[options[:parent_id]] = nil
75
+ temp[options[:level]] = nil
137
76
  # 1.2
138
- cache[value[fields[:parent_id]]] = temp
77
+ cache[value[options[:parent_id]]] = temp
139
78
  end
140
79
  # 2. Если текущий элемент уже был создан, значит он был чьим-то родителем, тогда:
141
- # 2.1 обновим в нем информацию
80
+ # 2.1 обновим в нем информацию о parent_id и другие не зарезервированные поля.
142
81
  # 2.2 поместим в родителя
143
- if cache.key? value[fields[:id]]
82
+ if cache.key? value[options[:id]]
144
83
  # 2.1
145
- fields.each do |key, field|
146
- case key
147
- when :id, :children
148
- # не обновляем информацию
149
- else
150
- cache[value[fields[:id]]][field] = value[field]
151
- end
152
- end
153
- value.keys.each do |field|
154
- cache[value[fields[:id]]][field] = value[field] if !(field.in? fields)
155
- end
156
- cache[value[fields[:id]]].origin = origin
84
+ cache[value[options[:id]]][options[:parent_id]] = value[options[:parent_id]]
85
+ cache[value[options[:id]]].origin = origin
157
86
  # 2.2
158
87
  # Если текущий элемент не корневой - поместим в родителя, беря его из кэш
159
- if value[fields[:parent_id]] != options[:root_id]
160
- cache[value[fields[:parent_id]]][fields[:children]] ||= options[:hashed] ? {} : []
88
+ if value[options[:parent_id]] != options[:root_id]
89
+ cache[value[options[:parent_id]]][options[:children]] ||= options[:hashed] ? {} : []
161
90
  if options[:hashed]
162
- cache[value[fields[:parent_id]]][fields[:children]][value[fields[:id]]] = nested[value[fields[:id]]]
91
+ cache[value[options[:parent_id]]][options[:children]][value[options[:id]]] = nested[value[options[:id]]]
163
92
  else
164
- cache[value[fields[:parent_id]]][fields[:children]] << cache[value[fields[:id]]]
93
+ cache[value[options[:parent_id]]][options[:children]] << cache[value[options[:id]]]
165
94
  end
166
95
  # иначе, текущий элемент корневой, поместим в nested
167
96
  else
168
- if options[:hashed]
169
- nested[value[fields[:id]]] = cache[value[fields[:id]]]
170
- else
171
- nested << cache[value[fields[:id]]]
97
+ if options[:branch_id].nil? || options[:branch_id] == value[options[:id]]
98
+ if options[:hashed]
99
+ nested[value[options[:id]]] = cache[value[options[:id]]]
100
+ else
101
+ nested << cache[value[options[:id]]]
102
+ end
172
103
  end
173
104
  end
174
105
  # 3. Иначе, текущий элемент не создан, тогда:
@@ -178,72 +109,65 @@ module NestedArray::Nested
178
109
  else
179
110
  # 3.1
180
111
  temp = OpenStruct.new
181
- fields.each do |key, field|
182
- case key
183
- when :id
184
- temp[field] = value[field]
185
- when :parent_id
186
- temp[field] = value[field]
187
- when :children
188
- # ничего не делаем
189
- else
190
- temp[field] = value[field]
191
- end
192
- end
193
- value.keys.each do |field|
194
- temp[field] = value[field] if !(field.in? fields)
195
- end
112
+ temp[options[:id]] = value[options[:id]]
113
+ temp[options[:parent_id]] = value[options[:parent_id]]
114
+ temp[options[:level]] = nil
196
115
  temp.origin = origin
197
116
  # 3.2
198
- cache[value[fields[:id]]] = temp
117
+ cache[value[options[:id]]] = temp
199
118
  # 3.3
200
119
  # Если текущий элемент не корневой - поместим в родителя, беря его из кэш
201
- if value[fields[:parent_id]] != options[:root_id]
202
- cache[value[fields[:parent_id]]][fields[:children]] ||= options[:hashed] ? {} : []
120
+ if value[options[:parent_id]] != options[:root_id]
121
+ cache[value[options[:parent_id]]][options[:children]] ||= options[:hashed] ? {} : []
203
122
  if options[:hashed]
204
- cache[value[fields[:parent_id]]][fields[:children]][value[fields[:id]]] = cache[value[fields[:id]]]
123
+ cache[value[options[:parent_id]]][options[:children]][value[options[:id]]] = cache[value[options[:id]]]
205
124
  else
206
- cache[value[fields[:parent_id]]][fields[:children]] << cache[value[fields[:id]]]
125
+ cache[value[options[:parent_id]]][options[:children]] << cache[value[options[:id]]]
207
126
  end
208
127
  # иначе, текущий элемент корневой, поместим в nested
209
128
  else
210
- if options[:hashed]
211
- nested[value[fields[:id]]] = cache[value[fields[:id]]]
212
- else
213
- nested << cache[value[fields[:id]]]
129
+ if options[:branch_id].nil? || options[:branch_id] == value[options[:id]]
130
+ if options[:hashed]
131
+ nested[value[options[:id]]] = cache[value[options[:id]]]
132
+ else
133
+ nested << cache[value[options[:id]]]
134
+ end
214
135
  end
215
136
  end
216
137
  end
217
138
  end
218
- if options[:add_level]
219
- level = 0
220
- cache = []
221
- cache[level] = nested
222
- i = []
223
- i[level] = 0
224
- while level >= 0
225
- node = cache[level][i[level]]
226
- i[level]+= 1
227
- if node != nil
228
-
229
- node[options[:level]] = level
230
-
231
- if !node[options[:children]].nil? && node[options[:children]].length > 0
232
- level+= 1
233
- cache[level] = node[options[:children]]
234
- i[level] = 0
235
- end
236
- else
237
- level-= 1
139
+
140
+ # Добавление level к узлу.
141
+ level = 0
142
+ cache = []
143
+ cache[level] = nested
144
+ i = []
145
+ i[level] = 0
146
+ while level >= 0
147
+ node = cache[level][i[level]]
148
+ i[level] += 1
149
+ if node != nil
150
+
151
+ node[options[:level]] = level
152
+
153
+ if !node[options[:children]].nil? && node[options[:children]].length > 0
154
+ level += 1
155
+ cache[level] = node[options[:children]]
156
+ i[level] = 0
238
157
  end
158
+ else
159
+ level -= 1
239
160
  end
240
161
  end
162
+
241
163
  nested
242
164
  end
243
165
 
244
- def nested_to_html options={}
166
+ #
167
+ # Перебирает вложенную стуктуру.
168
+ #
169
+ def each_nested(options = {})
245
170
  options = NESTED_OPTIONS.merge options
246
- html = ''
247
171
  level = 0
248
172
  cache = []
249
173
  cache[level] = self.clone
@@ -251,43 +175,142 @@ module NestedArray::Nested
251
175
  parents[level] = nil
252
176
  i = []
253
177
  i[level] = 0
178
+ prev_level = nil
254
179
  while level >= 0
255
180
  node = cache[level][i[level]]
256
- i[level]+= 1
181
+ i[level] += 1
257
182
  if node != nil
183
+ clone_node = node.clone
184
+
185
+ # Текущий узел является последним ребёнком своего родителя:
186
+ clone_node.is_last_children = cache[level][i[level]].blank?
187
+ # Текущий узел имеет детей:
188
+ clone_node.is_has_children = !node[options[:children]].nil? && node[options[:children]].length > 0
189
+ # Текущий узел последний в дереве:
190
+ clone_node.is_last = clone_node.is_last_children && !clone_node.is_has_children && (0..(clone_node.level)).to_a.map{|l| cache[l][i[l]].blank?}.all?(true)
191
+
192
+ next_level = if clone_node.is_has_children
193
+ level + 1
194
+ elsif clone_node.is_last_children
195
+ nl = nil
196
+ (0..clone_node.level).to_a.reverse.each do |l|
197
+ if cache[l][i[l]].present?
198
+ nl = l
199
+ break
200
+ end
201
+ end
202
+ nl
203
+ else
204
+ level
205
+ end
206
+
207
+ clone_node.parents = parents.clone
208
+
209
+ # В текущем узле всегда есть li
210
+ clone_node.before = options[:li].html_safe
211
+ clone_node.li = clone_node.before
212
+ # Следующий уровень тот же? — текущий закрываем просто.
213
+ if next_level.present? && next_level == clone_node.level
214
+ clone_node._ = options[:_li].html_safe
215
+ end
216
+ # Следующий уровень понизится? - текущий закрываем сложно.
217
+ if next_level.present? && next_level < clone_node.level
218
+ clone_node._ = options[:_li]
219
+ (clone_node.level - next_level).times do |t|
220
+ clone_node._ += options[:details] ? options[:_uld] + options[:_li] : options[:_ul] + options[:_li]
221
+ end
222
+ clone_node._ = clone_node._.html_safe
223
+ end
224
+ # Следующий уровень повысится? — открываем подуровень.
225
+ if clone_node.is_has_children
226
+ clone_node.ul = if options[:details]
227
+ options[:uld].html_safe
228
+ else
229
+ options[:ul].html_safe
230
+ end
231
+ end
232
+ # Последний в дереве? — последние закрывающие теги.
233
+ if clone_node.is_last
234
+ clone_node._ = options[:_li]
235
+ clone_node.level.times do |t|
236
+ clone_node._ += options[:details] ? options[:_uld] + options[:_li] : options[:_ul] + options[:_li]
237
+ end
238
+ clone_node._ = clone_node._.html_safe
239
+ end
240
+
241
+ clone_node.define_singleton_method(:after) do |*args|
242
+ ret = ''
243
+ # Следующий уровень тот же? — текущий закрываем просто.
244
+ if next_level.present? && next_level == clone_node.level
245
+ ret += options[:_li]
246
+ end
247
+ # Следующий уровень понизится? - текущий закрываем сложно.
248
+ if next_level.present? && next_level < clone_node.level
249
+ ret += options[:_li]
250
+ (clone_node.level - next_level).times do |t|
251
+ ret += options[:details] ? options[:_uld] + options[:_li] : options[:_ul] + options[:_li]
252
+ end
253
+ end
254
+ # Следующий уровень повысится? — открываем подуровень.
255
+ if self.is_has_children
256
+ if options[:details]
257
+ ret += args.present? && args[0]&.[](:open) == true ? options[:uldo] : options[:uld]
258
+ else
259
+ ret += options[:ul]
260
+ end
261
+ end
262
+ # Последний в дереве? — последние закрывающие теги.
263
+ if self.is_last
264
+ ret += options[:_li]
265
+ self.level.times do |t|
266
+ ret += options[:details] ? options[:_uld] + options[:_li] : options[:_ul] + options[:_li]
267
+ end
268
+ end
269
+ ret.html_safe
270
+ end
258
271
 
259
- node_html, node_options = yield(node.clone, parents.clone, level)
260
- html+= options[:tab] * (level * 2 + 1) if options[:tabulated]
261
- html+= node_options&.[](:li) || options[:li]
262
- html+= node_html.to_s
272
+ yield(clone_node, clone_node.origin)
273
+
274
+ prev_level = node.level
263
275
 
264
276
  if !node[options[:children]].nil? && node[options[:children]].length > 0
265
- level+= 1
266
- html+= "\n" if !options[:inline]
267
- html+= options[:tab] * (level * 2) if options[:tabulated]
268
- html+= node_options&.[](:ul) || options[:ul]
269
- html+= "\n" if !options[:inline]
270
- parents[level] = node.clone
277
+ level += 1
278
+ parents[level] = clone_node
271
279
  cache[level] = node[options[:children]]
272
280
  i[level] = 0
273
- else
274
- html+= options[:_li]
275
- html+= "\n" if !options[:inline]
276
281
  end
277
282
  else
278
283
  parents[level] = nil
279
- if level > 0
280
- html+= options[:tab] * (level * 2) if options[:tabulated]
281
- html+= options[:_ul]
282
- html+= "\n" if !options[:inline]
283
- html+= options[:tab] * (level * 2 - 1) if options[:tabulated]
284
- html+= options[:_li]
285
- html+= "\n" if !options[:inline]
284
+ level -= 1
285
+ end
286
+ end
287
+ self
288
+ end
289
+
290
+ def to_flat(options = {})
291
+ ret = []
292
+ options = NESTED_OPTIONS.merge options
293
+ level = 0
294
+ cache = []
295
+ cache[level] = self.clone
296
+ i = []
297
+ i[level] = 0
298
+ while level >= 0
299
+ node = cache[level][i[level]]
300
+ i[level] += 1
301
+ if node != nil
302
+ ret.push node.origin
303
+
304
+ if !node[options[:children]].nil? && node[options[:children]].length > 0
305
+ level += 1
306
+ cache[level] = node[options[:children]]
307
+ i[level] = 0
286
308
  end
287
- level-= 1
309
+ else
310
+ level -= 1
288
311
  end
289
312
  end
290
- html.html_safe
313
+ ret
291
314
  end
292
315
 
293
316
  #
@@ -296,11 +319,36 @@ module NestedArray::Nested
296
319
  # ```
297
320
  # [['option_text1', 'option_value1'],['option_text2', 'option_value2'],…]
298
321
  # ```
299
- def nested_to_options options={}
322
+ def nested_to_options(origin_text, origin_value, options = {})
323
+ options = NESTED_OPTIONS.merge options
324
+ ret = []
325
+
326
+ last = []
327
+ downhorizontal, horizontal, left, rightvertical, rightup, space, vertical = options[:thin_pseudographic] ? options[:thin_pseudographics] : options[:pseudographics]
328
+
329
+ each_nested do |node, origin|
330
+ last[node.level + 1] = node.is_last_children
331
+ node_text = origin.send(origin_text)
332
+ node_level = (1..node.level).map{|l| last[l] == true ? space : vertical}.join
333
+ node_last = node.is_last_children ? rightup : rightvertical
334
+ node_children = node[options[:children]].present? && node[options[:children]].length > 0 ? downhorizontal : horizontal
335
+ option_text = "#{node_level}#{node_last}#{node_children}#{left}".html_safe + "#{node_text}"
336
+ option_value = origin.send(origin_value)
337
+ ret.push [option_text, option_value]
338
+ end
339
+ ret
340
+ end
341
+
342
+ # Преобразует вложенную структуру данных в плоскую, но добавляет в значение
343
+ # поля отвечающего за текстовое представление (:name) псевдографику
344
+ # древовидной структуры.
345
+ # Это позволяет вывести тэг select в сносном виде для использования с
346
+ # вложенными структурами.
347
+ def nested_to_collection_select(options={})
300
348
  options = NESTED_OPTIONS.merge options
301
349
  ret = []
302
350
  last = []
303
- each_nested do |node, parents, level, is_last|
351
+ each_nested do |node, parents, level, is_last, origin|
304
352
  last[level+1] = is_last
305
353
  node_text = node[options[:option_text]]
306
354
  node_level = (1..level).map{|l| last[l] == true ? '&nbsp;' : '┃'}.join
@@ -308,7 +356,8 @@ module NestedArray::Nested
308
356
  node_children = node[options[:children]].present? && node[options[:children]].length > 0 ? '┳' : '━'
309
357
  option_text = "#{node_level}#{node_last}#{node_children}╸".html_safe + "#{node_text}"
310
358
  option_value = node[options[:option_value]]
311
- ret.push [option_text, option_value]
359
+ node[options[:option_text]] = option_text
360
+ ret.push node
312
361
  end
313
362
  ret
314
363
  end
@@ -1,3 +1,3 @@
1
1
  module NestedArray
2
- VERSION = "2.3.0"
2
+ VERSION = "3.0.0"
3
3
  end
data/lib/nested_array.rb CHANGED
@@ -1,17 +1,17 @@
1
1
  require "active_support/all"
2
- require "nested_array/version"
3
2
 
3
+ require_relative "nested_array/version"
4
4
  require_relative "nested_array/nested"
5
5
 
6
6
  module NestedArray
7
-
8
7
  class Array < ::Array
9
-
10
8
  include NestedArray::Nested
11
9
  end
10
+
11
+ class Engine < ::Rails::Engine
12
+ end
12
13
  end
13
14
 
14
15
  class Array
15
-
16
16
  include ::NestedArray::Nested
17
17
  end
@@ -0,0 +1,92 @@
1
+ .nested_array-details {
2
+ font-size: 1em;
3
+ margin: 0;
4
+ padding: 0 0 0 6px;
5
+ list-style-type: none;
6
+ line-height: 1.5em;
7
+ }
8
+ .nested_array-details ul {
9
+ margin: 0;
10
+ padding: 0 0 0 6px;
11
+ list-style-type: none;
12
+ }
13
+ .nested_array-details li {
14
+ margin: 0;
15
+ padding: 0 0 0 30px;
16
+ border-left: solid 1px gray;
17
+
18
+ position: relative;
19
+ }
20
+ .nested_array-details li > * {
21
+ margin-top: 0;
22
+ margin-bottom: 0;
23
+ }
24
+ .nested_array-details details {
25
+ position: relative;
26
+ top: 0;
27
+ /* -li.padding + ((li:before.width - 1) / 2) - ((summary.width - 1) / 2) */
28
+ /* -30 + ((27 - 1) / 2) - ((13 - 1) / 2) = -23 */
29
+ left: -23px;
30
+ }
31
+ .nested_array-details details::before {
32
+ content: none;
33
+ }
34
+ .nested_array-details details[open]::before {
35
+ content: '';
36
+ display: block;
37
+ position: absolute;
38
+ width: 1px;
39
+ height: 1em;
40
+ background-color: grey;
41
+ top: -0.75em;
42
+ left: 6px;
43
+ }
44
+ .nested_array-details summary {
45
+ position: absolute;
46
+ width: 13px;
47
+ height: 13px;
48
+ box-sizing: border-box;
49
+ top: calc(-0.75em - 6px);
50
+ left: auto;
51
+ list-style-type: none;
52
+ border: solid 1px gray;
53
+ cursor:pointer;
54
+ background:
55
+ linear-gradient(#000, #000),
56
+ linear-gradient(#000, #000),
57
+ #FFF;
58
+ background-position: center;
59
+ background-size: 7px 1px, 1px 7px;
60
+ background-repeat: no-repeat;
61
+ }
62
+ .nested_array-details details[open] > summary {
63
+ background:
64
+ linear-gradient(#000, #000),
65
+ #FFF;
66
+ background-position: center;
67
+ background-size: 7px 1px, 1px 7px;
68
+ background-repeat: no-repeat;
69
+ }
70
+ .nested_array-details li::before {
71
+ content: '';
72
+ display: block;
73
+ border-bottom: solid 1px gray;
74
+ position: absolute;
75
+ width: 27px;
76
+ height: 0.75em;
77
+ left: -1px;
78
+ top: 0;
79
+ }
80
+ .nested_array-details li:last-child {
81
+ border-left: 1px solid transparent;
82
+ }
83
+ .nested_array-details li:last-child::before {
84
+ border-left: solid 1px gray;
85
+ }
86
+
87
+ .nested_array-select {
88
+ option {
89
+ padding: 0;
90
+ font-family: monospace;
91
+ }
92
+ }