A simple UI for editing key-value pairs
This key-value-editor is based on the bootstrap based framework Patternfly. Patternfly or a similar boostrap based framework should be present for proper layout rendering. Icons are from font awesome. Alternative layouts with a different framework could be achieved by replacing the key-value-editor.html
template which is pre-compiled into compiled-templates.js
for convenience.
See the Test Readme.md file for details about running tests.
Add the key-value-editor
in html and provide it some data via the entries attribute:
<!-- hard coded -->
<key-value-editor entries="[{key: 'foo', value: 'bar'}]"></key-value-editor>
<!-- via scope -->
<key-value-editor entries="entries"></key-value-editor>
Note that entries is required or <key-value-editor>
will log an error!
For configuring the directive as a whole, use the following attributes.
Automatic vs manual rows:
Automatic row creation is the default, however this may not be the most accessible solution depending on the placement of your other form inputs and buttons. For this reason, the attribute add-row-link
is provided. If present, a link will appear that allows the user to manually create new pairs. The link text is set by passing a value to the attribute.
<key-value-editor
entries="entries"
add-row-link="Add another key,value pair"></key-value-editor>
If the automatic row creation feature is used, a user cannot tab past the last input in the <key-value-editor>
. If a mouse or track pad is unavailable, this is not ideal as inputs after the <key-value-editor>
will be unreachable, perhaps including the form submit itself.
Readonly:
<key-value-editor
entries="entries"
is-readonly></key-value-editor>
(is-readonly
can also be an array of string names is-readonly="['foo']"
for selectively making individual entries readonly. In addition, each entry.isReadonly
can be set to true||false
.)
Readonly keys:
<key-value-editor
entries="entries"
is-readonly-keys></key-value-editor>
Makes the keys of the inital set of entries readonly. Does not affect added entries.
(In implementation, this just sets isReadonlyKey: true
on each of the entries in the initial set of entries for you. isReadonlyKey
can be directly controlled if preferred)
Disable adding new entries:
<key-value-editor
entries="entries"
cannot-add></key-value-editor>
Disable sorting entries:
<key-value-editor
entries="entries"
cannot-sort></key-value-editor>
(Sort handle will only appear if there is more than one entry in the list.)
Disable deleting entries:
<key-value-editor
entries="entries"
cannot-delete></key-value-editor>
(cannot-delete
can also be an array of string names cannot-delete="['foo']"
for selectively making individual entries readonly. In addition, each entry.cannotDelete
can be set to true||false
.)
Use the attributes together:
<key-value-editor
entries="entries"
is-readonly
cannot-add
cannot-sort
cannot-delete></key-value-editor>
Some of the above attributes can also be applied to individual entries to control them uniquely within the set:
$scope.entries = [{
key: 'foo',
value: 'bar',
isReadonly: true, // key & value are readonly
isReadonlyKey: true, // only key (name) is readonly
cannotDelete: true
}];
Validator attributes and error messages:
<key-value-editor
entries="entries"
key-validator="[a-zA-Z0-9_]*"
key-validator-error="Invalid name"
value-validator="[a-zA-Z0-9_]*"
value-validator-error="Invalid value"></key-value-editor>
To pass a regex directly (or an object with a .test()
method, simulating a regex), you can do something like the following:
// controller code:
$scope.validation = {
key: new RegExp('^[0-9]+$'), // numbers only
val: {
test: function(val) {
// some complicated test w/multiple regex or other insanity
}
}
}
<!-- view code -->
<key-value-editor
entries="entries"
key-validator-regex="validation.key"
key-validator-error="Invalid name, numbers only plz"
value-validator-regex="validation.val"
value-validator-error="Invalid value, cuz *complicated* things"></key-value-editor>
All attributes for convenient reference.
<key-value-editor
entries="[
{name: 'foo', value: 'stuff'},
{name: 'bar', value: 'things'},
{
name: 'baz',
value: '3',
isReadonly: true,
cannotDelete: true,
isReadonlyKey: true
}
]"
key-placeholder="name"
key-min-length="3"
key-max-length="25"
key-validator="[a-zA-Z0-9_]*"
key-validator-error="Invalid name"
key-validator-error-tooltip="Name must be alphanumeric including - and _"
key-validator-error-tooltip-icon="fa fa-exclamation-circle"
value-placeholder="value"
value-min-length="3"
value-max-length="25"
value-validator="[a-zA-Z0-9_]*"
value-validator-error="Invalid value"
is-readonly="['can','be','a','list']"
is-readonly-keys
cannot-delete="['can','be','a','list']"
cannot-add
cannot-sort
grab-focus></key-value-editor>
Some entry lists may include non-standard key-value pairs. If the value is not a string, or is a different object entirely, such as this:
$scope.entries = [{
name: 'entry_value',
value: 'value'
},{
name: 'valueFrom-valueAlt',
isReadonly: true,
// non-standard
valueFrom: {
"configMapKeyRef": {
"name": "test-configmap",
"key": "data-1"
}
},
// valueAlt to the rescue!
valueAlt: 'valueFrom is a non-standard value',
}];
The valueAlt
attribute can provide the user with some alt text for understanding that this key-value pair will not display properly. It is not necessary to set isReadonly:true
as an input receiving valueAlt
will auto to readonly
. The valueValidator
property, minLength
and maxLength
properties are all ignored as valueAlt
is help text and it is assumed that it will break typical validation rules for the rest of the values.
NOTE: the default template provided with <key-value-editor>
uses bootstrap tooltips via Patternfly/Bootstrap. Be sure to initialize the tooltips somewhere with code such as:
// opt in to the bootstrap tooltips
$('[data-toggle="tooltip"]').tooltip();
General validation rules can be put on the directive as attributes and will run against each of the entries:
<key-value-editor
key-validator="{{regex}}"
value-validator="{{regex2}}"
key-validator-error="The error message if you break it"
value-validator-error="The other error message if you break it"></key-value-editor>
For a more granular approach, each entry provided can have its own custom validation rules that will override the generic set on the directive:
return [{
key: 'foo',
value: 'bar',
keyValidator: '[a-zA-Z0-9]+' // alphanumeric
keyValidatorError: 'Thats not alphanumeric!!',
valueValidator: '[a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,4}', // email address
valueValidatorError: 'Hey, this has to be an email.'
}]
For convenience, here are a few useful regex. Note that the <key-value-editor>
internally uses ng-pattern
which expects string regex that angular will internally new RegExp('^' + regex + '$');
. Therefore be sure to leave off the leading /^
and trailing $/
or your regex will not work.
// <key-value-editor key-validator="regex.digitsOnly"></key-value-editor>
$scope.regex = {
noWhiteSpace: '\S*',
digitsOnly: '[0-9]+',
alphaOnly: '[a-zA-Z]+',
alphaNumeric: '[a-zA-Z0-9]+',
alphaNumericUnderscore: '[a-zA-Z0-9_]*',
alphaNumericDashes: '[a-zA-Z0-9-_]+',
email: '[a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,4}',
}
Global defaults can be set via the provider:
angular
.module('demo')
.config([
'keyValueEditorConfigProvider',
function(keyValueEditorConfigProvider) {
// set a global value here:
keyValueEditorConfigProvider.set('keyValidator', '[0-9]+');
// or, pass an object to set multiple values at once:
keyValueEditorConfigProvider.set({
keyValidator: '[0-9]+',
keyValidatorError: 'This is an invalid key'
});
}
]);
Globals are still overridden via attributes on the <key-value-editor>
directive, or via the entries="entries"
data objects passed to the directive.
There are two useful utility functions provided to help process the entries
. The first will eliminate entries missing the name or value, the second will convert the list of entries to a map (object) of name-values (duplicate keys override values).
angular
.module('app')
.controller([
'keyValueEditorUtils'
function(kveUtils) {
angular.extend($scope, {
entries: [{name: 'foo', value: 'bar'}],
// a 'save'function
onSubmit: function() {
// eliminates entries missing a key or val.
console.log('compact', kveUtils.compactEntries($scope.entries));
// transforms the array into an object.
console.log('map', kveUtils.mapEntries($scope.entries));
}
})
}
]);
If other filtering/mapping abilities are needed user will have to write own utils or use a lib such as lodash.