lib/nested_array/nested.rb in nested_array-2.4.0 vs lib/nested_array/nested.rb in nested_array-3.0.0

- old
+ new

@@ -9,306 +9,332 @@ NESTED_OPTIONS ||= { # Имена полей для получения/записи информации, чувствительны к string/symbol id: 'id', parent_id: 'parent_id', + children: 'children', level: 'level', # Параметры для преобразования в nested hashed: false, - add_level: false, root_id: nil, + branch_id: nil, # Параметры для преобразования в html tabulated: true, inline: false, tab: "\t", - ul: '<ul>', - _ul: '</ul>', - li: '<li>', - _li: '</li>', # Параматры для "склеивания" вложенных структур path_separator: '-=path_separator=-', path_key: 'text', # Настройки формирования массива для опций тега <select> option_value: 'id', # Что брать в качестве значений при формировании опций селекта. option_text: 'name', - } - end + thin_option: false, + pseudographics: %w(┳ ━ ╸ ┣ ┗ &nbsp; ┃), + thin_pseudographics: %w(┬ ─ ╴ ├ └ &nbsp; │), - # - # Перебирает вложенную стуктуру. - # - def each_nested options={} - options = NESTED_OPTIONS.merge options - level = 0 - cache = [] - cache[level] = self.clone - parents = [] - parents[level] = nil - i = [] - i[level] = 0 - while level >= 0 - node = cache[level][i[level]] - i[level]+= 1 - if node != nil - is_last_children = cache[level][i[level]].blank? + # Выводить html теги от раскрывающегося списка на основе тега details. + details: false, - yield(node.clone, parents.clone, level, is_last_children, node.origin) + ul: '<ul>', + _ul: '</ul>', + li: '<li>', + _li: '</li>', - if !node[options[:children]].nil? && node[options[:children]].length > 0 - level+= 1 - parents[level] = node.clone - cache[level] = node[options[:children]] - i[level] = 0 - end - else - parents[level] = nil - level-= 1 - end - end - self + uld: '<details><summary></summary><ul>', + uldo: '<details open><summary></summary><ul>', + _uld: '</ul></details>' + } end - def each_nested! options={} + def to_nested(options = {}) options = NESTED_OPTIONS.merge options - level = 0 - cache = [] - cache[level] = self - parents = [] - parents[level] = nil - i = [] - i[level] = 0 - while level >= 0 - node = cache[level][i[level]] - i[level]+= 1 - if node != nil - is_last_children = cache[level][i[level]].blank? - - yield(node, parents, level, is_last_children, node.origin) - - if !node[options[:children]].nil? && node[options[:children]].length > 0 - level+= 1 - parents[level] = node - cache[level] = node[options[:children]] - i[level] = 0 - end - else - parents[level] = nil - level-= 1 - end - end - self - end - - def to_nested options={} - options = NESTED_OPTIONS.merge options + # Зарезервированные поля узла. fields = { id: options[:id], parent_id: options[:parent_id], level: options[:level], children: options[:children], } - fields.delete :level if !options[:add_level] cache = {} nested = options[:hashed] ? {} : [] # Перебираем элементы в любом порядке! - self.each do |value| - origin = value - value = value.serializable_hash if !value.is_a? Hash + self.each do |origin| + value = origin.is_a?(Hash) ? origin : origin.serializable_hash # 1. Если нет родителя текущего элемента, и текущий элемент не корневой, то: # 1.1 создадим родителя # 1.2 поместим в кэш - if !(cache.key? value[fields[:parent_id]]) && (value[fields[:parent_id]] != options[:root_id]) + if !(cache.key? value[options[:parent_id]]) && (value[options[:parent_id]] != options[:root_id]) # 1.1 temp = OpenStruct.new - fields.each do |key, field| - case key - when :id - temp[field] = value[fields[:parent_id]] - when :children - # не создаём поле - else - temp[field] = nil - end - end + temp[options[:id]] = value[options[:parent_id]] + temp[options[:parent_id]] = nil + temp[options[:level]] = nil # 1.2 - cache[value[fields[:parent_id]]] = temp + cache[value[options[:parent_id]]] = temp end # 2. Если текущий элемент уже был создан, значит он был чьим-то родителем, тогда: - # 2.1 обновим в нем информацию + # 2.1 обновим в нем информацию о parent_id и другие не зарезервированные поля. # 2.2 поместим в родителя - if cache.key? value[fields[:id]] + if cache.key? value[options[:id]] # 2.1 - fields.each do |key, field| - case key - when :id, :children - # не обновляем информацию - else - cache[value[fields[:id]]][field] = value[field] - end - end - value.keys.each do |field| - cache[value[fields[:id]]][field] = value[field] if !(field.in? fields) - end - cache[value[fields[:id]]].origin = origin + cache[value[options[:id]]][options[:parent_id]] = value[options[:parent_id]] + cache[value[options[:id]]].origin = origin # 2.2 # Если текущий элемент не корневой - поместим в родителя, беря его из кэш - if value[fields[:parent_id]] != options[:root_id] - cache[value[fields[:parent_id]]][fields[:children]] ||= options[:hashed] ? {} : [] + if value[options[:parent_id]] != options[:root_id] + cache[value[options[:parent_id]]][options[:children]] ||= options[:hashed] ? {} : [] if options[:hashed] - cache[value[fields[:parent_id]]][fields[:children]][value[fields[:id]]] = nested[value[fields[:id]]] + cache[value[options[:parent_id]]][options[:children]][value[options[:id]]] = nested[value[options[:id]]] else - cache[value[fields[:parent_id]]][fields[:children]] << cache[value[fields[:id]]] + cache[value[options[:parent_id]]][options[:children]] << cache[value[options[:id]]] end # иначе, текущий элемент корневой, поместим в nested else - if options[:hashed] - nested[value[fields[:id]]] = cache[value[fields[:id]]] - else - nested << cache[value[fields[:id]]] + if options[:branch_id].nil? || options[:branch_id] == value[options[:id]] + if options[:hashed] + nested[value[options[:id]]] = cache[value[options[:id]]] + else + nested << cache[value[options[:id]]] + end end end # 3. Иначе, текущий элемент не создан, тогда: # 3.1 создадим элемент # 3.2 поместим в кэш # 3.3 поместим в родителя else # 3.1 temp = OpenStruct.new - fields.each do |key, field| - case key - when :id - temp[field] = value[field] - when :parent_id - temp[field] = value[field] - when :children - # ничего не делаем - else - temp[field] = value[field] - end - end - value.keys.each do |field| - temp[field] = value[field] if !(field.in? fields) - end + temp[options[:id]] = value[options[:id]] + temp[options[:parent_id]] = value[options[:parent_id]] + temp[options[:level]] = nil temp.origin = origin # 3.2 - cache[value[fields[:id]]] = temp + cache[value[options[:id]]] = temp # 3.3 # Если текущий элемент не корневой - поместим в родителя, беря его из кэш - if value[fields[:parent_id]] != options[:root_id] - cache[value[fields[:parent_id]]][fields[:children]] ||= options[:hashed] ? {} : [] + if value[options[:parent_id]] != options[:root_id] + cache[value[options[:parent_id]]][options[:children]] ||= options[:hashed] ? {} : [] if options[:hashed] - cache[value[fields[:parent_id]]][fields[:children]][value[fields[:id]]] = cache[value[fields[:id]]] + cache[value[options[:parent_id]]][options[:children]][value[options[:id]]] = cache[value[options[:id]]] else - cache[value[fields[:parent_id]]][fields[:children]] << cache[value[fields[:id]]] + cache[value[options[:parent_id]]][options[:children]] << cache[value[options[:id]]] end # иначе, текущий элемент корневой, поместим в nested else - if options[:hashed] - nested[value[fields[:id]]] = cache[value[fields[:id]]] - else - nested << cache[value[fields[:id]]] + if options[:branch_id].nil? || options[:branch_id] == value[options[:id]] + if options[:hashed] + nested[value[options[:id]]] = cache[value[options[:id]]] + else + nested << cache[value[options[:id]]] + end end end end end - if options[:add_level] - level = 0 - cache = [] - cache[level] = nested - i = [] - i[level] = 0 - while level >= 0 - node = cache[level][i[level]] - i[level]+= 1 - if node != nil - node[options[:level]] = level + # Добавление level к узлу. + level = 0 + cache = [] + cache[level] = nested + i = [] + i[level] = 0 + while level >= 0 + node = cache[level][i[level]] + i[level] += 1 + if node != nil - if !node[options[:children]].nil? && node[options[:children]].length > 0 - level+= 1 - cache[level] = node[options[:children]] - i[level] = 0 - end - else - level-= 1 + node[options[:level]] = level + + if !node[options[:children]].nil? && node[options[:children]].length > 0 + level += 1 + cache[level] = node[options[:children]] + i[level] = 0 end + else + level -= 1 end end + nested end - def nested_to_html options={} + # + # Перебирает вложенную стуктуру. + # + def each_nested(options = {}) options = NESTED_OPTIONS.merge options - html = '' level = 0 cache = [] cache[level] = self.clone parents = [] parents[level] = nil i = [] i[level] = 0 + prev_level = nil while level >= 0 node = cache[level][i[level]] - i[level]+= 1 + i[level] += 1 if node != nil + clone_node = node.clone - node_html, node_options = yield(node.clone, parents.clone, level) - html+= options[:tab] * (level * 2 + 1) if options[:tabulated] - html+= node_options&.[](:li) || options[:li] - html+= node_html.to_s + # Текущий узел является последним ребёнком своего родителя: + clone_node.is_last_children = cache[level][i[level]].blank? + # Текущий узел имеет детей: + clone_node.is_has_children = !node[options[:children]].nil? && node[options[:children]].length > 0 + # Текущий узел последний в дереве: + 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) + next_level = if clone_node.is_has_children + level + 1 + elsif clone_node.is_last_children + nl = nil + (0..clone_node.level).to_a.reverse.each do |l| + if cache[l][i[l]].present? + nl = l + break + end + end + nl + else + level + end + + clone_node.parents = parents.clone + + # В текущем узле всегда есть li + clone_node.before = options[:li].html_safe + clone_node.li = clone_node.before + # Следующий уровень тот же? — текущий закрываем просто. + if next_level.present? && next_level == clone_node.level + clone_node._ = options[:_li].html_safe + end + # Следующий уровень понизится? - текущий закрываем сложно. + if next_level.present? && next_level < clone_node.level + clone_node._ = options[:_li] + (clone_node.level - next_level).times do |t| + clone_node._ += options[:details] ? options[:_uld] + options[:_li] : options[:_ul] + options[:_li] + end + clone_node._ = clone_node._.html_safe + end + # Следующий уровень повысится? — открываем подуровень. + if clone_node.is_has_children + clone_node.ul = if options[:details] + options[:uld].html_safe + else + options[:ul].html_safe + end + end + # Последний в дереве? — последние закрывающие теги. + if clone_node.is_last + clone_node._ = options[:_li] + clone_node.level.times do |t| + clone_node._ += options[:details] ? options[:_uld] + options[:_li] : options[:_ul] + options[:_li] + end + clone_node._ = clone_node._.html_safe + end + + clone_node.define_singleton_method(:after) do |*args| + ret = '' + # Следующий уровень тот же? — текущий закрываем просто. + if next_level.present? && next_level == clone_node.level + ret += options[:_li] + end + # Следующий уровень понизится? - текущий закрываем сложно. + if next_level.present? && next_level < clone_node.level + ret += options[:_li] + (clone_node.level - next_level).times do |t| + ret += options[:details] ? options[:_uld] + options[:_li] : options[:_ul] + options[:_li] + end + end + # Следующий уровень повысится? — открываем подуровень. + if self.is_has_children + if options[:details] + ret += args.present? && args[0]&.[](:open) == true ? options[:uldo] : options[:uld] + else + ret += options[:ul] + end + end + # Последний в дереве? — последние закрывающие теги. + if self.is_last + ret += options[:_li] + self.level.times do |t| + ret += options[:details] ? options[:_uld] + options[:_li] : options[:_ul] + options[:_li] + end + end + ret.html_safe + end + + yield(clone_node, clone_node.origin) + + prev_level = node.level + if !node[options[:children]].nil? && node[options[:children]].length > 0 - level+= 1 - html+= "\n" if !options[:inline] - html+= options[:tab] * (level * 2) if options[:tabulated] - html+= node_options&.[](:ul) || options[:ul] - html+= "\n" if !options[:inline] - parents[level] = node.clone + level += 1 + parents[level] = clone_node cache[level] = node[options[:children]] i[level] = 0 - else - html+= options[:_li] - html+= "\n" if !options[:inline] end else parents[level] = nil - if level > 0 - html+= options[:tab] * (level * 2) if options[:tabulated] - html+= options[:_ul] - html+= "\n" if !options[:inline] - html+= options[:tab] * (level * 2 - 1) if options[:tabulated] - html+= options[:_li] - html+= "\n" if !options[:inline] + level -= 1 + end + end + self + end + + def to_flat(options = {}) + ret = [] + options = NESTED_OPTIONS.merge options + level = 0 + cache = [] + cache[level] = self.clone + i = [] + i[level] = 0 + while level >= 0 + node = cache[level][i[level]] + i[level] += 1 + if node != nil + ret.push node.origin + + if !node[options[:children]].nil? && node[options[:children]].length > 0 + level += 1 + cache[level] = node[options[:children]] + i[level] = 0 end - level-= 1 + else + level -= 1 end end - html.html_safe + ret end # # Возвращает массив для формирования опций html-тега <select> # с псевдографикой, позволяющей вывести древовидную структуру. # ``` # [['option_text1', 'option_value1'],['option_text2', 'option_value2'],…] # ``` - def nested_to_options options={} + def nested_to_options(origin_text, origin_value, options = {}) options = NESTED_OPTIONS.merge options ret = [] + last = [] - each_nested do |node, parents, level, is_last| - last[level+1] = is_last - node_text = node[options[:option_text]] - node_level = (1..level).map{|l| last[l] == true ? '&nbsp;' : '┃'}.join - node_last = is_last ? '┗' : '┣' - node_children = node[options[:children]].present? && node[options[:children]].length > 0 ? '┳' : '━' - option_text = "#{node_level}#{node_last}#{node_children}╸".html_safe + "#{node_text}" - option_value = node[options[:option_value]] + downhorizontal, horizontal, left, rightvertical, rightup, space, vertical = options[:thin_pseudographic] ? options[:thin_pseudographics] : options[:pseudographics] + + each_nested do |node, origin| + last[node.level + 1] = node.is_last_children + node_text = origin.send(origin_text) + node_level = (1..node.level).map{|l| last[l] == true ? space : vertical}.join + node_last = node.is_last_children ? rightup : rightvertical + node_children = node[options[:children]].present? && node[options[:children]].length > 0 ? downhorizontal : horizontal + option_text = "#{node_level}#{node_last}#{node_children}#{left}".html_safe + "#{node_text}" + option_value = origin.send(origin_value) ret.push [option_text, option_value] end ret end