nested_array 2.4.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
39
+ # Выводить html теги от раскрывающегося списка на основе тега details.
40
+ details: false,
74
41
 
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?
89
-
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)
258
191
 
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
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
271
+
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,18 +319,21 @@ 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 = {})
300
323
  options = NESTED_OPTIONS.merge options
301
324
  ret = []
325
+
302
326
  last = []
303
- each_nested do |node, parents, level, is_last|
304
- last[level+1] = is_last
305
- node_text = node[options[:option_text]]
306
- node_level = (1..level).map{|l| last[l] == true ? '&nbsp;' : '┃'}.join
307
- node_last = is_last ? '┗' : '┣'
308
- node_children = node[options[:children]].present? && node[options[:children]].length > 0 ? '┳' : '━'
309
- option_text = "#{node_level}#{node_last}#{node_children}╸".html_safe + "#{node_text}"
310
- option_value = node[options[:option_value]]
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)
311
337
  ret.push [option_text, option_value]
312
338
  end
313
339
  ret
@@ -1,3 +1,3 @@
1
1
  module NestedArray
2
- VERSION = "2.4.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
+ }