JSONPairsWidget
Dynamic length json pairs widget.
by schinckel
HTML
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/uikit/2.27.2/css/uikit.almost-flat.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.5.1/Sortable.js"></script>
<form class="uk-form uk-form-horizontal">
<fieldset class="uk-form-row">
<label class="uk-form-label">Name</label>
<div class="uk-form-controls">
<input name="name" value="Alice" class="uk-form-width-medium">
</div>
</fieldset>
<fieldset class="uk-form-row">
<label class="uk-form-label">Data</label>
<div class="uk-form-controls">
<input type="text" value='{"foo":"bar", "baz":true}' name="values">
</div>
</fieldset>
<fieldset class="uk-form-row">
<div class="uk-form-controls">
<button type="submit" class="uk-button">
<i class="uk-icon-check"></i> submit
</button>
<button type="reset" class="uk-button">
<i class="uk-icon-times"></i> reset
</button>
</div>
</fieldset>
</form>
<template data-array-widget-row>
<fieldset data-array-row>
<input class="uk-form-width-medium" data-key>
<span class="uk-form-icon uk-form-icon-flip">
<input class="uk-form-width-medium" data-value>
<i class="uk-icon-ban" data-array-remove-row></i>
</span>
</fieldset>
</template>
<template data-array-widget-add>
<button class="uk-button" type="button">
<i class="uk-icon-plus"></i> Add new
</button>
</template>
<pre>name=Alice&values=foo,bar,baz</pre>
<!-- spacer to make output larger, since we dynamically add elements -->
<div style="height: 200px"></div>
CSS
form {
padding: 12px;
}
input[name=values] {
opacity: 0.5;
float: right;
}
[data-array-row] [data-array-remove-row] {
pointer-events: initial;
cursor: pointer;
}
input[disabled] + [data-array-remove-row]::before {
content: "\f0e2";
}
fieldset[data-array-row] {
margin-bottom: 2px;
}
JavaScript
function zip(first, second) {
return Array.prototype.map.call(first, function(e, i){ return [e, second[i]]});
}
function ArrayWidget(element) {
// We need to be able to reset the value to the initial, if our form
// gets told to reset.
var initialValue = element.value;
var rowTemplate = document.querySelector('[data-array-widget-row]');
var buttonTemplate = document.querySelector('[data-array-widget-add]');
// Wrap the whole widget inside a fieldset, as it enables us to have event
// listeners here, that we can use for delegation.
var widget = document.createElement('fieldset');
element.replaceWith(widget);
widget.appendChild(element);
// Also use a seperate container for just the input elements.
var valuesList = document.createElement('fieldset');
widget.appendChild(valuesList);
var newValueButton = document.importNode(buttonTemplate.content, true);
newValueButton.querySelector('button').addEventListener('click', addValue);
widget.appendChild(newValueButton);
function recalculateValue(event) {
// Set the element (which in reality would be a hidden field) to have a value
// that is the list of values, but only from widgets that are not disabled.
if (initialValue && initialValue[0] == '{') {
var value = {};
Array.apply(null, valuesList.children).filter(function(fieldset) {
return !fieldset.disabled
}).forEach(function(row, i) {
// How do we know if we should be using the string value of this field, or interpret that
// as a JSON value. For instance, if it's a number or boolean (or null)?
var rowValue = row.querySelector('[data-value]').value;
try {
rowValue = JSON.parse(rowValue);
} catch(e) {}
value[row.querySelector('[data-key]').value] = rowValue;
});
element.value = JSON.stringify(value);
} else {
element.value = Array.apply(null,...