UNPKG

api-console-assets

Version:

This repo only exists to publish api console components to npm

557 lines (511 loc) 17.7 kB
<!-- @license Copyright 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="../arc-icons/arc-icons.html"> <link rel="import" href="../arc-polyfills/arc-polyfills.html"> <link rel="import" href="../iron-form/iron-form.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="../paper-tooltip/paper-tooltip.html"> <link rel="import" href="../request-payload-editor-behavior/request-payload-editor-behavior.html"> <link rel="import" href="../iron-validatable-behavior/iron-validatable-behavior.html"> <link rel="import" href="../raml-type-form-behavior/raml-type-form-behavior.html"> <link rel="import" href="../paper-checkbox/paper-checkbox.html"> <link rel="import" href="form-data-editor-item.html"> <!-- An element to edit form data (x-www-form-urlencoded). The element renders a form of body properties. Each row contains name and value input fields to describe parameters in body. Empty values for both name and value inputs are not included in final body value. ### Example ``` <form-data-editor></form-data-editor> ``` The element implements `request-payload-editor-behavior` that offers methods to encode and decode values. The UI contains controls to encode and decode values. ## Models The element implements `ArcBehaviors.RamlTypeFormBehavior` to compute a model from a RAML type. If `ramlType` property is set then it computes the `dataModel` property with RAML data model for form view. Internally the element generates a view `model`. If `dataModel` contains an item with `name` that equals `model` item name then it adds data model item to the view model. This model is then used to build a form controls for given RAML type. ## Disabling model and value computation To disable any computations in the element set `attrForOpened` attribute. When an attribute is set with the name that equals `attrForOpened` value then computations will be resumed. It is helpful when UI allows switching between different editors: ```html <form-data-editor attr-for-opened="enabled"></form-data-editor> <script> function enableEditor() { document.querySelector('form-data-editor').setAttribute('enabled', true); } </script> ``` Or you can use `iron-pages` element: ```html <iron-pages selected="{{openedEditor}}" selected-attribute="opened"> <paper-textarea value="{{value}}"></paper-textarea> <form-data-editor attr-for-opened="opened" value="{{value}}"></form-data-editor> </iron-pages> ``` Note, this will only work if you set an attribute. It will not handle property change. ### Styling `<form-data-editor>` provides the following custom properties and mixins for styling: Custom property | Description | Default ----------------|-------------|---------- `--form-data-editor` | Mixin applied to the element | `{}` `--form-data-editor-row` | Mixin applied to each form row | `{}` `--form-data-editor-encode-buttons` | Mixin applied to encode / decode buttons container | `{}` `--action-button` | Theme mixin, applied to the "add parameter" button | `{}` `--form-data-editor-add-button` | Mixin applied to the "add parameter" button | `{}` `--form-data-editor-add-button-background-color` | Background color of the "add parameter" button | `--primary-color` `--form-data-editor-add-button-color` | Font color of the "add parameter" button | `--primary-background-color` `--from-row-action-icon-color` | Delete parameter button color | `--icon-button-color` or `rgba(0, 0, 0, 0.74)` `--from-row-action-icon-color-hover` | Delete parameter button color when hovering with the pointer | `--accent-color` or `rgba(0, 0, 0, 0.74)` `--form-data-editor-row-narrow` | Mixin applied to each form row when narrow layout | `{}` `--form-data-editor-row-optional` | Mixin applied to the optional rows | `{}` `--form-data-editor-row-optional-visible` | Mixin applied to the optional rows when becomes visible | `{}` @group UI Elements @element form-data-editor @demo demo/index.html Regular element usage @demo demo/raml.html Usage with RAML type --> <dom-module id="form-data-editor"> <template> <style> :host { display: block; @apply --form-data-editor; } .form-item { @apply --layout-horizontal; @apply --layout-start; @apply --form-data-editor-row; } form-data-editor-item { @apply(--layout-flex); } .option-pane { margin: 8px 0; @apply --layout-horizontal; @apply --layout-center; @apply --form-data-editor-encode-buttons; } .option-pane paper-checkbox { margin-left: 12px; } .add-param-button { margin-top: 12px; background-color: var(--form-data-editor-add-button-background-color, --primary-color); color: var(--form-data-editor-add-button-color, --primary-background-color); @apply --action-button; @apply --form-data-editor-add-button; } paper-icon-button { color: var(--from-row-action-icon-color, var(--icon-button-color, rgba(0, 0, 0, 0.74))); transition: color 0.2s linear; } paper-icon-button:hover { color: var(--from-row-action-icon-color-hover, var(--accent-color, rgba(0, 0, 0, 0.74))); } .narrow .form-item { display: block; @apply --form-data-editor-row-narrow; } .form-item[data-optional] { display: none; @apply --form-data-editor-row-optional; } :host([optional-opened]) [data-optional] { @apply --layout-horizontal; @apply --form-data-editor-row-optional-visible; } </style> <div class="option-pane"> <paper-button title="Encodes payload to x-www-form-urlencoded data" on-tap="_encodePaylod">encode payload</paper-button> <paper-button title="Decodes payload to human readable form" on-tap="_decodePaylod">decode payload</paper-button> <paper-checkbox hidden$="[[!hasOptional]]" checked="{{optionalOpened}}">Show optional properties</paper-checkbox> </div> <form is="iron-form" id="form"> <template is="dom-repeat" items="{{model}}"> <div class="form-item" data-optional$="[[_computeIsOptional(hasOptional, item.model.required)]]"> <form-data-editor-item narrow="[[narrow]]" name="{{item.name}}" value="{{item.value}}" model="[[item.model]]" required="[[item.model.required]]"></form-data-editor-item> <span> <paper-icon-button icon="arc:close" on-tap="_removeParam"></paper-icon-button> <paper-tooltip animation-delay="200">Remove parameter</paper-tooltip> </span> </div> </template> </form> <paper-button on-tap="add" class="add-param-button">Add form parameter</paper-button> </template> <script> Polymer({ is: 'form-data-editor', behaviors: [ ArcBehaviors.RequestPayloadEditorBehavior, ArcBehaviors.RamlTypeFormBehavior, Polymer.IronValidatableBehavior ], /** * Event fire when the value of the editor change. * This event is not fired if `attrForOpened` is set and corresponding value is not set. * * @event payload-value-changed * @param {String} value Current payload value. */ properties: { /** * Computed view model. */ model: Array, /** * If set it renders a narrow layout */ narrow: { type: Boolean, value: false }, /** * RAML type definition for a body. * It adds a documentation and type check from the RAML type. */ ramlType: Object, /** * The view model generated by the RAML type. */ dataModel: { type: Array, readOnly: true, observer: '_modelFromDataModel' }, /** * Computed value. True if the data model has at least one optional * value. */ hasOptional: { type: Boolean, value: false, computed: '_computeHasOptional(dataModel)', }, /** * When set and `ramlType` is set then it renders all possiblem * parameters. By default optional parameters are hidden. * * This has no effect if there's no not required items in the model. */ optionalOpened: { type: Boolean, reflectToAttribute: true } }, /** * Default options for data model generation. */ get _modelOpts() { return { decodeValues: true }; }, observers: [ '_valueChanged(_isOpened, value)', '_modelChanged(_isOpened, model.*)', '_ramlTypeChanged(_isOpened, ramlType)', '__isOpenedChanged(_isOpened)' ], __isOpenedChanged: function(isOpened) { if (isOpened) { this.updateModel(this.value); } }, _getValidity: function() { return this.$.form.validate(); }, _ramlTypeChanged: function(opened, ramlType) { if (!opened) { return; } if (!ramlType) { this._setDataModel(undefined); return; } if (ramlType.formParameters) { this._modelFromRaml08(ramlType); } else { this._modelFromRaml10(ramlType); } }, _modelFromRaml10: function(type) { if (!type || !type.properties || !type.properties.length) { this._setDataModel(undefined); return; } var modelOpts = this._modelOpts; var model = type.properties.map(function(value) { return this._createModelObject(value, modelOpts); }, this); this._setDataModel(model); }, _modelFromRaml08: function(type) { var params = this._clone(type.formParameters); var modelOpts = this._modelOpts; var model = Object.keys(params).map(function(key) { var value = type.formParameters[key]; value.key = key; return this._createModelObject(value, modelOpts); }, this); this._setDataModel(model); }, /** * Handler for value change. * If the element is opened then it will update model. */ _valueChanged: function(opened, value) { if (!opened) { return; } if (this.__internalChange) { this.fire('payload-value-changed', { value: value }); return; } this.updateModel(value); }, /** * Updates the `model` from current `value`. */ updateModel: function(value) { if (value === undefined && !this.model) { return this.set('model', [this._createModelObject({}, {})]); } if (typeof value !== 'string' && value !== undefined) { return; } if (!value) { if (this.model && this.dataModel) { this.updateValue(); return; } return this.set('model', [this._createModelObject({}, {})]); } var arr = this.stringToArray(value); this.set('model', arr); this._linkModels(arr, this.dataModel); }, /** * Adds a data model property to a view model item if data model exists. * It checks names to compare models. * * @param {Array} model A view model (form data model) * @param {Array} dataModel A data model (RAML type model) */ _linkModels: function(viewModel, dataModel) { if (!dataModel || !viewModel || !viewModel.length) { return; } this.__stopValueChanged = true; var vl = dataModel.length; for (var i = 0, len = viewModel.length; i < len; i++) { var oldModel = viewModel[i].model; var changed = false; for (var j = 0; j < vl; j++) { if (viewModel[i].name === dataModel[j].name) { changed = true; if (oldModel !== dataModel[j]) { this.set(['model', i, 'model'], dataModel[j]); } break; } } if (oldModel && !changed) { this.set(['model', i, 'model'], {}); } } this.__stopValueChanged = false; }, /** Encode payload button press handler */ _encodePaylod: function() { if (!this._isOpened) { // value is out of sync with model return; } this.encodeUrlEncoded(this.model); this.__postEncodeDecode(); }, /** Decode payload button press handler */ _decodePaylod: function() { if (!this._isOpened) { // value is out of sync with model return; } this.decodeUrlEncoded(this.model); this.__postEncodeDecode(); }, /** * Notifies paths about a change in the model's name/value properties. */ __postEncodeDecode: function() { var model = this.model; if (!model) { return; } this.__internalChange = true; for (var i = 0, len = model.length; i < len; i++) { this.notifyPath(['model', i, 'name']); this.notifyPath(['model', i, 'value']); } this.__internalChange = false; }, /** * Append empty parameter row. * * @param {?Object} item A predefined values for the model. It adds empty row id no values has * been passed. */ add: function(item) { if (item instanceof Event) { item = undefined; } item = this._createModelItem(item); if (!this.model) { this.set('model', [item]); } else { this.push('model', item); } }, // Creates a model item from base object. _createModelItem: function(item) { item = item || {}; item.name = item.name || ''; item.value = item.value || ''; item = this._createModelObject(item, this._modelOpts); item = this._addDataModel(item); return item; }, /** Remove element from the params form */ _removeParam: function(e) { var index = this.$$('template[is="dom-repeat"]').indexForElement(e.target); this.splice('model', index, 1); }, // Handler for model change. _modelChanged: function(opened, record) { if (!opened || !record || !record.path || this.__stopValueChanged) { return; } var paths = record.path.split('.'); var propertyChanged = paths.pop(); // if path == 'model' it means the object was initialized. if (record.path !== 'model') { this.updateValue(record.base); } if (propertyChanged === 'name') { var path = paths.join('.'); this._updateModelWithDataModel(path, record.value); } }, _updateModelWithDataModel: function(path, modelName) { if (!this.dataModel) { return; } var model = this.dataModel.find(function(item) { return item.name === modelName; }); this.set(path + '.model', model); }, /** * Appends view model data to the data model. * @param {Object} dataModel Data model item. * @return {Object} The same object with `model` property. It can be * `undefiend`. */ _addDataModel: function(dataModel) { var model = this._getDataModelForName(dataModel.name); dataModel.model = model || {}; return dataModel; }, /** * Finds a model for given property name. * @param {String} name Data model item name. * @return {Object} View model or undefined if not found. */ _getDataModelForName: function(name) { var model = this.dataModel; if (!model) { return; } return model.find(function(item) { return item.name === name; }); }, /** * Sets a view model (`model` property) from data model. * * @param {Array} dataModel The data model */ _modelFromDataModel: function(dataModel) { if (!dataModel) { return; } var model = dataModel.map(function(item) { return { name: item.name || item.key, value: item.value || '', model: item }; }); this.set(['model'], model); }, /** * Updates current `value` property from model. * If the model attribute is not set it will take current model. * * @param {?Object<string, string>} model */ updateValue: function(model) { if (!this._isOpened) { return; } model = model || this.model; var hasModel = model && model.length; var value = hasModel ? this.formArrayToString(model) : ''; this.__internalChange = true; this.set('value', value); this.__internalChange = false; }, // Computes css class name for narrow layout _computeNarrowClass: function(narrow) { return narrow ? 'narrow' : undefined; }, /** * Computes if any of the model items is required. */ _computeHasOptional: function(model) { for (var i = 0, len = model.length; i < len; i++) { if (!model[i].required) { return true; } } return false; }, _computeIsOptional: function(hasOptional, required) { return hasOptional && !required; } }); </script> </dom-module>