api-console-assets
Version:
This repo only exists to publish api console components to npm
537 lines (506 loc) • 18.3 kB
HTML
<!--
2016 The Advanced REST client authors <arc@mulesoft.com>
Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.
-->
<link rel="import" href="../polymer/polymer.html">
<link rel="import" href="../iron-flex-layout/iron-flex-layout.html">
<link rel="import" href="../paper-button/paper-button.html">
<link rel="import" href="../paper-icon-button/paper-icon-button.html">
<link rel="import" href="../arc-definitions/arc-definitions.html">
<link rel="import" href="../headers-parser-behavior/headers-parser-behavior.html">
<link rel="import" href="../raml-type-form-behavior/raml-type-form-behavior.html">
<link rel="import" href="../iron-validatable-behavior/iron-validatable-behavior.html">
<link rel="import" href="../events-target-behavior/events-target-behavior.html">
<link rel="import" href="../iron-form/iron-form.html">
<link rel="import" href="../iron-icon/iron-icon.html">
<link rel="import" href="../arc-icons/arc-icons.html">
<link rel="import" href="raml-headers-form-item.html">
<!--
An element that renders a form of headers with pre-populated values.
It uses RAML headers definitions as a data source for data population.
The RAML headers must be an array. RAML javascript parser returns an object by
default. The object must be transformed using `raml-json-enhance` element -
for example.
### Usage
```html
<raml-headers-form></raml-headers-form>
```
```javascript
var form = document.querySelector('raml-headers-form');
form.ramlHeaders = [...];
form.addEventListener('value-changed', function(e) {
var value = e.detail.value;
});
```
## Events
The element listens to `headers-value-changed` custom event. If the event is
handled it changes the `value` of the editor.
### Styling
`<raml-headers-form>` provides the following custom properties and mixins for styling:
Custom property | Description | Default
----------------|-------------|----------
`--raml-headers-form` | Mixin applied to the element | `{}`
`--raml-headers-form-action-button` | Mixin applied to the acrion button | `{}`
`--raml-headers-form-action-button-hover` | Mixin applied to the acrion button when hovering | `{}`
`--raml-headers-form-action-button-color` | Color of the action button. | `--secondary-button-color` or `--accent-color`
`--raml-headers-form-action-button-color-hover` | Color of the action button when hovered | `--secondary-button-color` or `--accent-color`
`--raml-headers-form-action-button-background` | Background of the action button | `--secondary-button-background` or `#fff`
`--raml-headers-form-action-button-background-hover` | Background of the action button when hovered | `--secondary-button-background` or `#fff`
`--raml-headers-form-action-button-hover` | Mixin applied to the secondary button when hovered | `{}`
`--secondary-button` | Theme mixin applied to the secondary button | `{}`
`--secondary-button-hover` | Theme mixin applied to the secondary button when hovered | `{}`
`--from-row-action-icon-color` | Color of the icon button to display help | `--icon-button-color` or `rgba(0, 0, 0, 0.74)`
`--from-row-action-icon-color-hover` | Color of the icon button to display help when hovered | `--accent-color` or `rgba(0, 0, 0, 0.74)`
`--raml-headers-form-action-icon-color` | Color of the add action icon button | `--secondary-button-color` or `--accent-color`
`--raml-headers-form-action-icon-color-hover` | Theme variable, color of the action icon button when hovered | `--secondary-button-color` or `--accent-color`
`--raml-headers-form-action-icon-opacity` | Opacity of the add action icon button | `0.54`
`--raml-headers-form-action-icon-opacity-hover` | Opacity of the add action icon button when hovered | `0.74`
See styles for `raml-headers-form-item` for more styling options.
RAML Elements
raml-headers-form
demo/index.html
-->
<dom-module id="raml-headers-form">
<template strip-whitespace>
<style include="markdown-styles"></style>
<style>
:host {
display: block;
--raml-headers-form;
}
.form-item {
--layout-horizontal;
--layout-start;
}
raml-headers-form-item {
--layout-flex;
}
.delete-icon {
margin-top: 16px;
color: var(--from-row-action-icon-color, var(--icon-button-color, rgba(0, 0, 0, 0.74)));
transition: color 0.2s linear;
}
.delete-icon:hover {
color: var(--from-row-action-icon-color-hover, var(--accent-color, rgba(0, 0, 0, 0.74)));
}
.action-button {
color: var(--raml-headers-form-action-button-color, var(--secondary-button-color, --accent-color));
background: var(--raml-headers-form-action-button-background, var(--secondary-button-background, #fff));
--secondary-button;
--raml-headers-form-action-button;
}
.action-button:hover {
color: var(--raml-headers-form-action-button-color-hover, var(--secondary-button-color, --accent-color));
background: var(--raml-headers-form-action-button-background-hover, var(--secondary-button-background, #fff));
--secondary-button-hover;
--raml-headers-form-action-button-hover;
}
.action-icon {
margin-right: 8px;
color: var(--raml-headers-form-action-icon-color, var(--secondary-button-color, --accent-color));
opacity: var(--raml-headers-form-action-icon-opacity, 0.54);
transition: opacity 0.2s ease-in-out, color 0.2s ease-in-out;
}
.action-button:hover .action-icon,
.action-icon:hover {
color: var(--raml-headers-form-action-icon-color-hover, var(--secondary-button-color, --accent-color));
opacity: var(--raml-headers-form-action-icon-opacity-hover, 0.74);
}
[hidden] {
display: none !important;
}
</style>
<form is="iron-form" id="form">
<template is="dom-repeat" items="{{model}}" id="headersList">
<div class="form-item">
<raml-headers-form-item name="{{item.name}}" value="{{item.value}}" model="[[item]]" required$="[[item.required]]" is-custom="[[item.isCustom]]" is-array="[[item.isArray]]" narrow="[[narrow]]"></raml-headers-form-item>
<span>
<paper-icon-button title="Remove header" class="delete-icon" suffix icon="arc:remove-circle-outline" hidden$="[[!item.isCustom]]" on-tap="_removeHeader"></paper-icon-button>
</span>
</div>
</template>
</form>
<div class="add-action" hidden$="[[disallowCustom]]">
<paper-button class="action-button" on-tap="add">
<iron-icon class="action-icon" icon="arc:add-circle-outline"></iron-icon>
Add custom header
</paper-button>
</div>
<arc-definitions></arc-definitions>
</template>
<script>
Polymer({
is: 'raml-headers-form',
behaviors: [
ArcBehaviors.HeadersParserBehavior,
ArcBehaviors.RamlTypeFormBehavior,
ArcBehaviors.EventsTargetBehavior,
Polymer.IronValidatableBehavior
],
/**
* Fired when the headers value changed.
*
* @event headers-value-changed
* @param {String} value The headers value.
*/
properties: {
// Headers description defined in the RAML file.
ramlHeaders: Array,
/**
* Computed from raml headers view model.
* It can contain custom headers (not defined in RAML type)
*/
model: Array,
// Current value of the headers. Changing the value will update the list
// of the headers.
value: {
type: String,
notify: true
},
// A node on which this element add it's listeners.
_eventTarget: HTMLElement,
/**
* If set, custom headers won't be available.
*/
disallowCustom: Boolean,
/**
* If set it renders a narrow layout
*/
narrow: Boolean
},
/**
* Default options for data model generation.
*/
get _modelOpts() {
return {
valueDelimiter: ':'
};
},
observers: [
'_modelFromRaml(ramlHeaders)',
'_valueChanged(value)',
'_modelChanged(model.*)'
],
_attachListeners: function(node) {
this.listen(node, 'headers-value-changed', '_extValueChangedHandler');
},
_detachListeners: function(node) {
this.unlisten(node, 'headers-value-changed', '_extValueChangedHandler');
},
/**
* Creates and sets the model from headers values.
* @param {Array} headers RAML headers definition.
*/
_modelFromRaml: function(headers) {
if (!headers || !headers.length) {
return this.set('model', undefined);
}
var modelOpts = this._modelOpts;
var model = headers.map(function(value) {
return this._createModelObject(value, modelOpts);
}, this);
this.set('model', model);
},
// Handler for the remove button click.
_removeHeader: function(e) {
if (!e.model) {
return;
}
var i = e.model.get('index');
if (i < 0) {
return;
}
this.splice('model', i, 1);
},
// Appends an empty header to the list.
add: function() {
if (this.disallowCustom) {
return;
}
var obj = {
type: 'string',
value: '',
name: '',
isCustom: true
};
obj = this._createModelObject(obj);
if (!this.model) {
this.set('model', [obj]);
} else {
this.push('model', obj);
}
},
// Handler for model change
_modelChanged: function(record) {
if (this._internalValueChange || !record) {
return;
}
if (['model', 'model.splices'].indexOf(record.path) !== -1) {
this._updateValue();
if (record.path === 'model' && record.value) {
this._autoDescribeModel(record.value);
}
} else if (record.path.match(/model.#\d+.(name|value)/)) {
this._updateValue();
if (record.path.match(/model.#\d+.name/)) {
this._headerNameChanged(record.path, record.value);
}
}
},
/**
* When model change after a value change it iterates over the model
* and adds description of the header if missing from ARC's definition
* element.
*
* @param {Array} model Computed model
*/
_autoDescribeModel: function(model) {
for (var i = 0, len = model.length; i < len; i++) {
if (model[i].hasDescription) {
continue;
}
var type = this._queryHeaderData(name);
if (type) {
model.description = type.desc;
model.examples = [type.example];
type = this._createModelObject(model, this._modelOpts);
this.set(['model', i], type);
}
}
},
/**
* A handler for the name change.
* It updates the description and example of the header is the header is known.
*/
_headerNameChanged: function(changePath, name) {
changePath = changePath.substr(0, changePath.lastIndexOf('.'));
// check if it is one of raml headers
var type = this._getRamlHeader(name);
if (type) {
type = this._createModelObject(type, this._modelOpts);
return this.set(changePath, type);
}
type = this._queryHeaderData(name);
if (type) {
type = this._createModelObject({
name: name,
value: this.get(changePath + '.value'),
examples: [type.example],
description: type.desc,
isCustom: true
}, this._modelOpts);
return this.set(changePath, type);
}
if (this.get(changePath + '.description')) {
type = this._createModelObject({
name: name,
value: this.get(changePath + '.value'),
isCustom: true
}, this._modelOpts);
return this.set(changePath, type);
}
},
/**
* Finds RAML header definition by name.
*
* @param {String} name Name of the header
* @return {Object|undefined} RAML type definition or undefined.
*/
_getRamlHeader: function(name) {
var headers = this.ramlHeaders;
if (!headers || !headers.length || !name) {
return;
}
name = String(name).toLowerCase();
for (var i = 0, len = headers.length; i < len; i++) {
if (headers[i].name === name) {
return headers[i];
}
}
},
/**
* Queries for a header definition.
*
* @param {String} name header name to query
* @return {Object|undefined} Header definition or undefined.
*/
_queryHeaderData: function(name) {
var suggestions = this._queryHeaderNameSuggestions(name);
if (!suggestions) {
return;
}
name = name.toLowerCase();
for (var i = 0, len = suggestions.length; i < len; i++) {
if (suggestions[i].key.toLowerCase() === name) {
return suggestions[i];
}
}
},
/**
* Sends a `query-headers` custom event to query for headers suggestions.
* @param {String} q The query - header name
* @return {Array} List of suggested headers
*/
_queryHeaderNameSuggestions: function(q) {
var event = this.fire('query-headers', {
'type': 'request',
'query': q
}, {
cancelable: true
});
return event.detail.headers;
},
_updateValue: function() {
var h = this.model;
if (!h || !h.length) {
this.value = '';
return;
}
var value = h.map(function(item) {
var n = item.name || '';
var v = item.value || '';
if (!n && !v) {
return;
}
if (v instanceof Array) {
v = v.join(',');
}
return n + ': ' + v;
}).filter(function(item) {
return !!item;
}).join('\n');
this._internalValueChange = true;
this.set('value', value);
this._internalValueChange = false;
},
/**
* Handler for value change.
* Updated the model from the value.
*
* @param {String} value New value
*/
_valueChanged: function(value) {
if (this._internalValueChange) {
this.fire('headers-value-changed', {
value: value
});
return;
}
var headers = this.headersToJSON(value);
if (!headers || !headers.length) {
return;
}
var def = this.model;
var i;
var len;
var _updatePosition;
this._internalValueChange = true;
if (def && def.length) {
var defLength = def.length;
for (i = 0, len = headers.length; i < len; i++) {
var found = false;
for (var j = 0; j < defLength; j++) {
if (headers[i].name === def[j].name) {
found = true;
this._setModelValue(j, headers[i].value);
break;
}
}
if (!found) {
var item = this._headersModelFromObject(headers[i]);
_updatePosition = 0;
if (this.model) {
_updatePosition = this.push('model', item);
_updatePosition--;
} else {
this.set('model', [item]);
}
this._headerNameChanged('model.' + _updatePosition + '.name', item.name);
}
}
// remove headers that are currently in the form but they are not set in
// the parsed values array.
var parsedNames = headers.map(function(item) {
return item.name;
});
for (i = def.length - 1; i >= 0; i--) {
if (parsedNames.indexOf(def[i].name) === -1) {
this.splice('model', i, 1);
}
}
} else {
for (i = 0, len = headers.length; i < len; i++) {
var _model = this._headersModelFromObject(headers[i]);
_updatePosition = 0;
if (this.model) {
_updatePosition = this.push('model', _model);
_updatePosition--;
} else {
this.set('model', [_model]);
}
this._headerNameChanged('model.' + _updatePosition + '.name', _model.name);
}
}
this._internalValueChange = false;
},
/**
* Sets a proper value type to a model.
* It test is given model is an array and creates array value if needed.
*
* @param {Number} index Model index number
* @param {String} value A value to set.
*/
_setModelValue: function(index, value) {
var type = this.get(['model', index, 'type']);
if (type === 'array') {
value = value.split(',').map(function(item) {
return item.trim();
});
}
this.set(['model', index, 'value'], value);
},
/**
* Transforms generated from headers source string header object to
* form's internal data object.
*
* @param {Object<string, string>} header Map of header properties (name
* and value)
* @return {Object} Internal datamodel created from the header.
*/
_headersModelFromObject: function(header) {
var obj = {
key: header.name,
name: header.name,
value: header.value,
isCustom: true
};
return this._createModelObject(obj);
},
/**
* A handler for the `url-value-changed` event.
* If this element is not the source of the event then it will update the `value` property.
* It's to be used besides the Polymer's data binding system.
*/
_extValueChangedHandler: function(e) {
if (e.target === this) {
return;
}
this.set('value', e.detail.value);
},
// Overidden from Polymer.IronValidatableBehavior. Will set the `invalid`
// attribute automatically, which should be used for styling.
_getValidity: function() {
return this.$.form.validate();
}
});
</script>
</dom-module>
Copyright