diff --git a/ckanext/scheming/assets/js/scheming-repeating-subfields.js b/ckanext/scheming/assets/js/scheming-repeating-subfields.js index 449e8f06..7dee1f00 100644 --- a/ckanext/scheming/assets/js/scheming-repeating-subfields.js +++ b/ckanext/scheming/assets/js/scheming-repeating-subfields.js @@ -6,9 +6,15 @@ ckan.module('scheming-repeating-subfields', function($) { var $template = this.el.children('div[name="repeating-template"]'); this.template = $template.html(); $template.remove(); + this._findClosestDescendants('a[name="repeating-add"]').on("click", this._onCreateGroup); + this._findClosestDescendants('a[name="repeating-remove"]').on('click', this._onRemoveGroup); + }, - this.el.find('a[name="repeating-add"]').on("click", this._onCreateGroup); - this.el.on('click', 'a[name="repeating-remove"]', this._onRemoveGroup); + _findClosestDescendants: function(selector) { + const thisEl = this.el; + return thisEl.find(selector).filter(function(index) { + return this.closest('[data-module="scheming-repeating-subfields"]') === thisEl[0] + }) }, /** @@ -26,19 +32,21 @@ ckan.module('scheming-repeating-subfields', function($) { * ... */ _onCreateGroup: function(e) { - var $last = this.el.find('.scheming-subfield-group').last(); - var group = ($last.data('groupIndex') + 1) || 0; - var $copy = $( + var $last = this.el.find('.scheming-subfield-group').last(); + var group = ($last.data('groupIndex') + 1) || 0; + var $copy = $( this.template.replace(/REPEATING-INDEX0/g, group) - .replace(/REPEATING-INDEX1/g, group + 1)); - this.el.find('.scheming-repeating-subfields-group').append($copy); + .replace(/REPEATING-INDEX1/g, group + 1)); - this.initializeModules($copy); - $copy.hide().show(100); - $copy.find('input').first().focus(); - // hook for late init when required for rendering polyfills - this.el.trigger('scheming.subfield-group-init'); - e.preventDefault(); + this._findClosestDescendants('.scheming-repeating-subfields-group').append($copy); + + this.initializeModules($copy); + $copy.hide().show(100); + $copy.find('input').first().focus(); + // hook for late init when required for rendering polyfills + this.el.trigger('scheming.subfield-group-init'); + e.preventDefault(); + e.stopPropagation(); }, /** diff --git a/ckanext/scheming/helpers.py b/ckanext/scheming/helpers.py index 25ba3969..20e7b665 100644 --- a/ckanext/scheming/helpers.py +++ b/ckanext/scheming/helpers.py @@ -5,6 +5,7 @@ import pytz import json import six +import copy from jinja2 import Environment from ckantoolkit import config, _ @@ -445,3 +446,42 @@ def scheming_flatten_subfield(subfield, data): for k in record: flat[prefix + k] = record[k] return flat + +def get_at_depth(data, path): + if len(path) == 0: + return data + if isinstance(data, dict): + found_data = data.get(path[0]) + elif isinstance(data, list): + if isinstance(path[0], int) and path[0] < len(data): + found_data = data[path[0]] + else: + found_data = None + else: + found_data = None + if found_data is None: + return None + if len(path) > 1: + return get_at_depth(found_data, path[1:]) + else: + return found_data + +def set_at_depth(data, path, value): + if len(path) == 0: + raise ValueError("Cannot set a value at an empty path") + data_to_modify = get_at_depth(data, path[0:len(path) - 1]) + last_path = path[len(path)-1] + data_to_modify[last_path] = value + return data + +@helper +def get_subfield_group_data(data, subfield_data_path): + subfield_data = get_at_depth(data, subfield_data_path) + if subfield_data is None: + return [] + else: + return subfield_data + +@helper +def deep_copy(data): + return copy.deepcopy(data) diff --git a/ckanext/scheming/plugins.py b/ckanext/scheming/plugins.py index e947d602..7c3e123b 100644 --- a/ckanext/scheming/plugins.py +++ b/ckanext/scheming/plugins.py @@ -394,27 +394,50 @@ def expand_form_composite(data, fieldnames): fieldnames -= set(data) if not fieldnames: return - indexes = {} - for key in sorted(data): - if '-' not in key: - continue - parts = key.split('-') - if parts[0] not in fieldnames: - continue - if parts[1] not in indexes: - indexes[parts[1]] = len(indexes) - comp = data.setdefault(parts[0], []) - parts[1] = indexes[parts[1]] - try: - try: - comp[int(parts[1])]['-'.join(parts[2:])] = data[key] - del data[key] - except IndexError: - comp.append({}) - comp[int(parts[1])]['-'.join(parts[2:])] = data[key] - del data[key] - except (IndexError, ValueError): - pass # best-effort only + + IDX_KEY = '__scheming_idx' + + for fieldname in fieldnames: + keys_from_data = [key for key in data.keys() if key.startswith(fieldname)] + indexes = {} + field_data = [] + for fieldname_key in sorted(keys_from_data): + if '-' not in fieldname_key: + continue + parts = fieldname_key.split('-') + path_to_field_data = parts[1:] + comp = field_data + parts_grouped = [[]] + for part in path_to_field_data: + last_part = parts_grouped[len(parts_grouped)-1] + if len(last_part) < 2: + last_part.append(part) + continue + else: + parts_grouped.append([part]) + stacked_keys = [fieldname] + data_to_set = comp + + for parts_idx, part in enumerate(parts_grouped): + marked_index, part_key = part + if idx_map := helpers.get_at_depth(indexes, stacked_keys): + marked_idx_map = idx_map.setdefault(marked_index, {IDX_KEY: len(idx_map)}) + idx = marked_idx_map[IDX_KEY] + else: + idx = 0 + helpers.set_at_depth(indexes, stacked_keys, {marked_index: {IDX_KEY: idx}}) + path_to_field_data[parts_idx*2] = idx + stacked_keys.append(marked_index) + stacked_keys.append(part_key) + if len(data_to_set) > idx: + data_at_idx = data_to_set[idx] + else: + data_at_idx = {} + data_to_set.append(data_at_idx) + data_to_set = data_at_idx.setdefault(part_key, []) + helpers.set_at_depth(field_data, path_to_field_data, data[fieldname_key]) + del data[fieldname_key] + data[fieldname] = field_data diff --git a/ckanext/scheming/templates/scheming/form_snippets/repeating_subfields.html b/ckanext/scheming/templates/scheming/form_snippets/repeating_subfields.html index ef25a9d9..0ae25d12 100644 --- a/ckanext/scheming/templates/scheming/form_snippets/repeating_subfields.html +++ b/ckanext/scheming/templates/scheming/form_snippets/repeating_subfields.html @@ -17,9 +17,15 @@ {% endblock %}