* @default false
*/
+ /**
+ * Set to `true`, a clone button is added to the button column, allowing
+ * the user to clone section instances mapped by the section form element.
+ * The default is `false`.
+ *
+ * @name LuCI.form.TypedSection.prototype#cloneable
+ * @type boolean
+ * @default false
+ */
+
/**
* Enables a per-section instance row `Edit` button which triggers a certain
* action when clicked. If set to a string, the string value is used
throw 'Tabs are not supported by TableSection';
},
+
+ /**
+ * Clone the section_id, putting the clone immediately after if put_next
+ * is true. Optionally supply a name for the new section_id.
+ */
+ /** @private */
+ handleClone: function(section_id, put_next, name) {
+ let config_name = this.uciconfig || this.map.config;
+
+ this.map.data.clone(config_name, this.sectiontype, section_id, put_next, name);
+ return this.map.save(null, true);
+ },
+
/** @private */
renderContents: function(cfgsections, nodes) {
var section_id = null,
config_name = this.uciconfig || this.map.config,
max_cols = isNaN(this.max_cols) ? this.children.length : this.max_cols,
+ cloneable = this.cloneable,
has_more = max_cols < this.children.length,
drag_sort = this.sortable && !('ontouchstart' in window),
touch_sort = this.sortable && ('ontouchstart' in window),
dom.content(trEl.lastElementChild, opt.title);
}
- if (this.sortable || this.extedit || this.addremove || has_more || has_action)
+ if (this.sortable || this.extedit || this.addremove || has_more || has_action || this.cloneable)
trEl.appendChild(E('th', {
'class': 'th cbi-section-table-cell cbi-section-actions'
}));
(typeof(opt.width) == 'number') ? opt.width+'px' : opt.width;
}
- if (this.sortable || this.extedit || this.addremove || has_more || has_action)
+ if (this.sortable || this.extedit || this.addremove || has_more || has_action || this.cloneable)
trEl.appendChild(E('th', {
'class': 'th cbi-section-table-cell cbi-section-actions'
}));
renderRowActions: function(section_id, more_label) {
var config_name = this.uciconfig || this.map.config;
- if (!this.sortable && !this.extedit && !this.addremove && !more_label)
+ if (!this.sortable && !this.extedit && !this.addremove && !more_label && !this.cloneable)
return E([]);
var tdEl = E('td', {
);
}
+ if (this.cloneable) {
+ var btn_title = this.titleFn('clonebtntitle', section_id);
+
+ dom.append(tdEl.lastElementChild,
+ E('button', {
+ 'title': btn_title || _('Clone') + '⿻',
+ 'class': 'btn cbi-button cbi-button-neutral',
+ 'click': ui.createHandlerFn(this, 'handleClone', section_id, true),
+ 'disabled': this.map.readonly || null
+ }, [ btn_title || _('Clone') + '⿻' ])
+ );
+ }
+
if (this.addremove) {
var btn_title = this.titleFn('removebtntitle', section_id);
return sid;
},
+ /**
+ * Clones an existing section of the given type to the given configuration,
+ * optionally named according to the given name.
+ *
+ * @param {string} conf
+ * The name of the configuration into which to clone the section.
+ *
+ * @param {string} type
+ * The type of the section to clone.
+ *
+ * @param {string} srcsid
+ * The source section id to clone.
+ *
+ * @param {boolean} [put_next]
+ * Whether to put the cloned item next (true) or last (false: default).
+ *
+ * @param {string} [name]
+ * The name of the new cloned section. If the name is omitted, an anonymous
+ * section will be created instead.
+ *
+ * @returns {string}
+ * Returns the section ID of the newly cloned section which is equivalent
+ * to the given name for non-anonymous sections.
+ */
+ clone: function(conf, type, srcsid, put_next, name) {
+ let n = this.state.creates;
+ let sid = this.createSID(conf);
+ let v = this.state.values;
+ put_next = put_next || false;
+
+ if (!n[conf])
+ n[conf] = { };
+
+ n[conf][sid] = {
+ ...v[conf][srcsid],
+ '.type': type,
+ '.name': sid,
+ '.create': name,
+ '.anonymous': !name,
+ '.index': 1000 + this.state.newidx++
+ };
+
+ if (put_next)
+ this.move(conf, sid, srcsid, put_next);
+ return sid;
+ },
+
/**
* Removes the section with the given ID from the given configuration.
*