Schema Form Developer Panel
A developer panel that allows you to edit schema forms in real-time, with multiple examples. Also, you can view that same form in a standard Horizon modal to preview what the form will look like. Based on http://schemaform.io/examples/bootstrap-example.html Also fixes some bugs in the schema-form implementation: - Arrays cleaned up and fixed - Tab arrays cleaned up - Improved modal form to use schema.title if available Co-Authored-By: Rob Cresswell <robert.cresswell@outlook.com> Change-Id: Ia75a18d4c0c064ae618ee923cd6d602b1ef6e66e
This commit is contained in:
parent
22d907cb76
commit
9ed4acd4ff
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* (c) Copyright 2016 Cisco Systems
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use self 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.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name horizon.framework.widgets.contenteditable
|
||||
* @description
|
||||
* Allows the use of contenteditable with ng-model. Altered from
|
||||
* https://docs.angularjs.org/api/ng/type/ngModel.NgModelController
|
||||
*/
|
||||
|
||||
angular
|
||||
.module('horizon.framework.widgets.contenteditable')
|
||||
.directive('contenteditable', contenteditable);
|
||||
|
||||
function contenteditable() {
|
||||
var directive = {
|
||||
restrict: 'A',
|
||||
require: '?ngModel', // get a hold of NgModelController
|
||||
link: link
|
||||
};
|
||||
return directive;
|
||||
|
||||
function link(scope, element, attrs, ngModel) {
|
||||
if (!ngModel) { return; } // do nothing if no ng-model
|
||||
|
||||
// Specify how UI should be updated
|
||||
ngModel.$render = function() {
|
||||
element.html(ngModel.$viewValue || '');
|
||||
};
|
||||
|
||||
// Listen for change events to enable binding
|
||||
element.on('blur keyup change', function() {
|
||||
scope.$evalAsync(read);
|
||||
});
|
||||
read();
|
||||
|
||||
function read() {
|
||||
ngModel.$setViewValue(element.html());
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright 2016 Cisco Systems
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
describe('contenteditable directive', function() {
|
||||
var $compile, $scope;
|
||||
|
||||
beforeEach(module('horizon.framework.widgets'));
|
||||
beforeEach(module('horizon.framework.widgets.contenteditable'));
|
||||
beforeEach(inject(function ($injector) {
|
||||
$compile = $injector.get('$compile');
|
||||
$scope = $injector.get('$rootScope').$new();
|
||||
$scope.testData = '';
|
||||
}));
|
||||
|
||||
describe('using a model', function() {
|
||||
var element;
|
||||
beforeEach(function before() {
|
||||
element = $compile('<pre contenteditable ng-model="testData"></pre>')($scope);
|
||||
$scope.$digest();
|
||||
});
|
||||
|
||||
it('should update the model when content is edited', function () {
|
||||
element.triggerHandler('focus');
|
||||
element.html('foo');
|
||||
$scope.$digest();
|
||||
element.triggerHandler('blur');
|
||||
expect($scope.testData).toBe('foo');
|
||||
});
|
||||
|
||||
it('should update the view when model is changed', function () {
|
||||
element.triggerHandler('focus');
|
||||
$scope.testData = 'spam';
|
||||
$scope.$digest();
|
||||
|
||||
expect(element.html()).toBe('spam');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not do anything without an accompanying ng-model', function() {
|
||||
var element = $compile('<pre contenteditable></pre>')($scope);
|
||||
$scope.$digest();
|
||||
|
||||
element.triggerHandler('focus');
|
||||
element.html('bar');
|
||||
$scope.$digest();
|
||||
element.triggerHandler('blur');
|
||||
|
||||
expect($scope.testData).toBe('');
|
||||
});
|
||||
});
|
||||
})();
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2016 Cisco Systems, Inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc overview
|
||||
* @name horizon.framework.widgets.contenteditable
|
||||
*/
|
||||
angular
|
||||
.module('horizon.framework.widgets.contenteditable', []);
|
||||
})();
|
@ -2,8 +2,8 @@
|
||||
sf-field-model="sf-new-array"
|
||||
sf-new-array>
|
||||
<label class="control-label" ng-show="showTitle()">{$:: form.title $}</label>
|
||||
<ol class="list-group" sf-field-model ui-sortable="form.sortOptions">
|
||||
<li class="list-group-item {$::form.fieldHtmlClass$}"
|
||||
<ol class="list-unstyled" sf-field-model ui-sortable="form.sortOptions">
|
||||
<li class="{$::form.fieldHtmlClass$}"
|
||||
schema-form-array-items
|
||||
sf-field-model="ng-repeat"
|
||||
ng-repeat="item in $$value$$ track by $index">
|
||||
|
@ -1,4 +1,4 @@
|
||||
<fieldset ng-disabled="form.readonly" class="hz-fieldset {$::form.htmlClass$}">
|
||||
<legend ng-class="{'sr-only': !showTitle() }">{$:: form.title $}</legend>
|
||||
<legend ng-show="showTitle()" ng-class="{'sr-only': !showTitle() }">{$:: form.title $}</legend>
|
||||
<div class="help-block" ng-show="form.description" ng-bind-html="form.description"></div>
|
||||
</fieldset>
|
||||
|
@ -2,10 +2,10 @@
|
||||
ng-model="modelArray" schema-validate="form"
|
||||
sf-field-model="sf-new-array"
|
||||
sf-new-array
|
||||
class="clearfix hz-tabarray schema-form-tabarray-{$form.tabType || 'left'$} {$form.htmlClass$}">
|
||||
class="clearfix hz-tabarray schema-form-tabarray-{$form.tabType || 'left'$} {::$form.htmlClass$}">
|
||||
<div ng-if="!form.tabType || form.tabType !== 'right'"
|
||||
ng-class="{'col-xs-3': !form.tabType || form.tabType === 'left'}">
|
||||
<ul class="nav nav-tabs"
|
||||
<ul class="nav nav-pills nav-stacked"
|
||||
ng-class="{ 'tabs-left': !form.tabType || form.tabType === 'left'}">
|
||||
<li sf-field-model="ng-repeat"
|
||||
ng-repeat="item in $$value$$ track by $index"
|
||||
@ -18,14 +18,14 @@
|
||||
ng-click="$event.preventDefault() || (selected.tab = appendToArray().length - 1)">
|
||||
<a href="#">
|
||||
<span class="fa fa-plus"></span>
|
||||
<span class="hz-tabarray-add">{$ form.add || 'Add'$}</span>
|
||||
<span class="hz-tabarray-add">{$:: form.add || 'Add'$}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div ng-class="{'col-xs-9': !form.tabType || form.tabType === 'left' || form.tabType === 'right'}">
|
||||
<div class="tab-content {$form.fieldHtmlClass$}">
|
||||
<div class="tab-content {$::form.fieldHtmlClass$}">
|
||||
<div class="tab-pane clearfix tab{$selected.tab$} index{$$index$}"
|
||||
sf-field-model="ng-repeat"
|
||||
ng-repeat="item in $$value$$ track by $index"
|
||||
@ -37,7 +37,7 @@
|
||||
ng-click="selected.tab = deleteFromArray($index).length - 1"
|
||||
ng-disabled="form.schema.minItems >= modelArray.length"
|
||||
type="button"
|
||||
class="btn {$ form.style.remove || 'btn-default' $} pull-right">
|
||||
class="btn {$:: form.style.remove || 'btn-danger' $} pull-right">
|
||||
<span class="fa fa-trash"></span>
|
||||
<span class="hz-tabarray-remove">{$::form.remove || 'Remove'$}</span>
|
||||
</button>
|
||||
@ -50,7 +50,7 @@
|
||||
</div>
|
||||
|
||||
<div ng-if="form.tabType === 'right'" class="col-xs-3">
|
||||
<ul class="nav nav-tabs tabs-right">
|
||||
<ul class="nav nav-pills tabs-right">
|
||||
<li sf-field-model="ng-repeat"
|
||||
ng-repeat="item in $$value$$ track by $index"
|
||||
ng-click="$event.preventDefault() || (selected.tab = $index)"
|
||||
|
@ -42,7 +42,7 @@
|
||||
|
||||
function controller($uibModalInstance, context) {
|
||||
var ctrl = this;
|
||||
ctrl.formTitle = context.title;
|
||||
ctrl.formTitle = context.schema.title || context.title;
|
||||
ctrl.form = context.form;
|
||||
ctrl.schema = context.schema;
|
||||
ctrl.model = context.model;
|
||||
|
@ -50,6 +50,7 @@
|
||||
function open(config) {
|
||||
var modalConfig = {
|
||||
backdrop: 'static',
|
||||
size: 'lg',
|
||||
resolve: {
|
||||
context: function() {
|
||||
return {
|
||||
|
@ -20,6 +20,7 @@
|
||||
angular
|
||||
.module('horizon.framework.widgets', [
|
||||
'horizon.framework.widgets.headers',
|
||||
'horizon.framework.widgets.contenteditable',
|
||||
'horizon.framework.widgets.details',
|
||||
'horizon.framework.widgets.form',
|
||||
'horizon.framework.widgets.help-panel',
|
||||
|
@ -0,0 +1,19 @@
|
||||
# (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# 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.
|
||||
|
||||
PANEL = 'form_builder'
|
||||
PANEL_GROUP = 'default'
|
||||
PANEL_DASHBOARD = 'developer'
|
||||
ADD_PANEL = \
|
||||
'openstack_dashboard.contrib.developer.form_builder.panel.FormBuilder'
|
22
openstack_dashboard/contrib/developer/form_builder/panel.py
Normal file
22
openstack_dashboard/contrib/developer/form_builder/panel.py
Normal file
@ -0,0 +1,22 @@
|
||||
# (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# 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.
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import horizon
|
||||
|
||||
|
||||
class FormBuilder(horizon.Panel):
|
||||
name = _("Form Builder")
|
||||
slug = 'form_builder'
|
20
openstack_dashboard/contrib/developer/form_builder/urls.py
Normal file
20
openstack_dashboard/contrib/developer/form_builder/urls.py
Normal file
@ -0,0 +1,20 @@
|
||||
# (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# 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.
|
||||
|
||||
from django.conf.urls import url
|
||||
from openstack_dashboard.contrib.developer.form_builder import views
|
||||
|
||||
urlpatterns = [
|
||||
url('', views.IndexView.as_view(), name='index'),
|
||||
]
|
19
openstack_dashboard/contrib/developer/form_builder/views.py
Normal file
19
openstack_dashboard/contrib/developer/form_builder/views.py
Normal file
@ -0,0 +1,19 @@
|
||||
# (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# 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.
|
||||
|
||||
from django.views import generic
|
||||
|
||||
|
||||
class IndexView(generic.TemplateView):
|
||||
template_name = 'angular.html'
|
@ -25,9 +25,10 @@
|
||||
*/
|
||||
angular
|
||||
.module('horizon.dashboard.developer', [
|
||||
'horizon.dashboard.developer.theme-preview',
|
||||
'horizon.dashboard.developer.form-builder',
|
||||
'horizon.dashboard.developer.profiler',
|
||||
'horizon.dashboard.developer.resource-browser',
|
||||
'horizon.dashboard.developer.profiler'
|
||||
'horizon.dashboard.developer.theme-preview'
|
||||
])
|
||||
.config(config);
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
// Top level file for Developer dashboard SCSS
|
||||
@import "theme-preview/theme-preview";
|
||||
@import "form-builder/form-builder";
|
||||
@import "profiler/profiler";
|
||||
@import "theme-preview/theme-preview";
|
||||
|
@ -0,0 +1,60 @@
|
||||
{
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"title": "Add Ons, Required and Feedback",
|
||||
"properties": {
|
||||
"first": {
|
||||
"type": "string"
|
||||
},
|
||||
"second": {
|
||||
"type": "string"
|
||||
},
|
||||
"third": {
|
||||
"type": "string"
|
||||
},
|
||||
"fourth": {
|
||||
"type": "string",
|
||||
"pattern": "^YES$"
|
||||
},
|
||||
"fifth": {
|
||||
"type": "string",
|
||||
"pattern": "^NO$"
|
||||
}
|
||||
}
|
||||
},
|
||||
"form": [
|
||||
{
|
||||
"type": "help",
|
||||
"helpvalue": "This is helpful <b><em><u>HTML</u></em></b> help text. Don't you feel better now?"
|
||||
},
|
||||
{
|
||||
"key": "first",
|
||||
"type": "text",
|
||||
"title": "Shows 'fieldAddonLeft'",
|
||||
"fieldAddonLeft": "With <b><em><u>HTML</u></em></b> added on left"
|
||||
},
|
||||
{
|
||||
"key": "second",
|
||||
"type": "text",
|
||||
"title": "Shows 'fieldAddonRight",
|
||||
"fieldAddonRight": "With <b><em><u>HTML</u></em></b> added on right"
|
||||
},
|
||||
{
|
||||
"key": "third",
|
||||
"type": "text",
|
||||
"title": "Title of a 'required' field",
|
||||
"required": "true"
|
||||
},
|
||||
{
|
||||
"key": "fourth",
|
||||
"type": "text",
|
||||
"title": "This requires the pattern 'YES'"
|
||||
},
|
||||
{
|
||||
"key": "fifth",
|
||||
"type": "text",
|
||||
"title": "This requires the pattern 'NO', but does NOT provide the feedback icon",
|
||||
"feedback": false
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
{
|
||||
"schema": {
|
||||
"title": "Array",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"arrayKey": {
|
||||
"type": "array",
|
||||
"title": "Array Item",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"one": {
|
||||
"type": "string"
|
||||
},
|
||||
"two": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"form": [
|
||||
{
|
||||
"key": "arrayKey",
|
||||
"type": "array",
|
||||
"title": "The 'array' title",
|
||||
"description": "This is the 'array' description"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
{
|
||||
"schema": {
|
||||
"title": "Buttons",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"none": "null"
|
||||
}
|
||||
},
|
||||
"form": [
|
||||
{
|
||||
"key": "singleButton",
|
||||
"type": "button",
|
||||
"title": "Button"
|
||||
},
|
||||
{
|
||||
"key": "submitButton",
|
||||
"type": "submit",
|
||||
"title": "A Submit Button"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"title": "Delete 'Foo'?",
|
||||
"properties": {
|
||||
"confirm": {}
|
||||
}
|
||||
},
|
||||
"form": [
|
||||
{
|
||||
"key": "confirm",
|
||||
"type": "help",
|
||||
"helpvalue": "Are you sure you wish to delete 'Foo'? This action cannot be undone."
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
<H1>Helpy McHelp Text</H1>
|
||||
<p>This is some snazzy HTML help text.</p>
|
||||
<p>You are welcome!</p>
|
@ -0,0 +1,143 @@
|
||||
{
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"title": "Radios, Checkboxes, and Select",
|
||||
"properties": {
|
||||
"checkBox": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"checkBoxes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"one",
|
||||
"two",
|
||||
"three"
|
||||
]
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"type": "string"
|
||||
},
|
||||
"radios": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"one",
|
||||
"two",
|
||||
"three"
|
||||
]
|
||||
},
|
||||
"radioInline": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"one",
|
||||
"two",
|
||||
"three"
|
||||
]
|
||||
},
|
||||
"radioButtons": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"one",
|
||||
"two",
|
||||
"three"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"form": [
|
||||
{
|
||||
"key": "checkBox",
|
||||
"type": "checkbox",
|
||||
"title": "This is a 'checkbox' title",
|
||||
"description": "This is the 'checkbox' description"
|
||||
},
|
||||
{
|
||||
"key": "checkBoxes",
|
||||
"type": "checkboxes",
|
||||
"title": "This is a 'checkboxes' title",
|
||||
"description": "This is the 'checkboxes' description"
|
||||
},
|
||||
{
|
||||
"key": "select",
|
||||
"type": "select",
|
||||
"title": "This is a 'select' title",
|
||||
"description": "This is the 'select' description",
|
||||
"titleMap": [
|
||||
{
|
||||
"value": "one",
|
||||
"name": "1 (One)"
|
||||
},
|
||||
{
|
||||
"value": "two",
|
||||
"name": "2 (Two)"
|
||||
},
|
||||
{
|
||||
"value": "three",
|
||||
"name": "3 (Three)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "radios",
|
||||
"type": "radios",
|
||||
"title": "This is a 'radios' title",
|
||||
"description": "This is the 'radios' description",
|
||||
"titleMap": [
|
||||
{
|
||||
"value": "one",
|
||||
"name": "1 (One)"
|
||||
},
|
||||
{
|
||||
"value": "two",
|
||||
"name": "2 (Two)"
|
||||
},
|
||||
{
|
||||
"value": "three",
|
||||
"name": "3 (Three)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "radioInline",
|
||||
"type": "radios-inline",
|
||||
"title": "This is a 'radios-inline' title",
|
||||
"description": "This is the 'radios-inline' description",
|
||||
"titleMap": [
|
||||
{
|
||||
"value": "one",
|
||||
"name": "1 (Alpha)"
|
||||
},
|
||||
{
|
||||
"value": "two",
|
||||
"name": "2 (Beta)"
|
||||
},
|
||||
{
|
||||
"value": "three",
|
||||
"name": "3 (Gamma)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "radioButtons",
|
||||
"type": "radiobuttons",
|
||||
"title": "This is a 'radiobuttons' title",
|
||||
"description": "This is the 'radiobuttons' description",
|
||||
"titleMap": [
|
||||
{
|
||||
"value": "one",
|
||||
"name": "1 (I)"
|
||||
},
|
||||
{
|
||||
"value": "two",
|
||||
"name": "2 (II)"
|
||||
},
|
||||
{
|
||||
"value": "three",
|
||||
"name": "3 (III)"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
{
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"title": "Sections and Fieldsets",
|
||||
"properties": {
|
||||
"first": {
|
||||
"type": "string"
|
||||
},
|
||||
"second": {
|
||||
"type": "string"
|
||||
},
|
||||
"third": {
|
||||
"type": "string"
|
||||
},
|
||||
"fourth": {
|
||||
"type": "string"
|
||||
},
|
||||
"fifth": {
|
||||
"type": "string"
|
||||
},
|
||||
"sixth": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"form": [
|
||||
{
|
||||
"type": "section",
|
||||
"items": [
|
||||
"first",
|
||||
"second"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "fieldset",
|
||||
"title": "A 'fieldset' title",
|
||||
"description": "A 'fieldset' description",
|
||||
"items": [
|
||||
"third",
|
||||
"fourth"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "fieldset",
|
||||
"title": "A second 'fieldset' title",
|
||||
"description": "A second 'fieldset' description",
|
||||
"items": [
|
||||
"fifth",
|
||||
"sixth"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
{
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"title": "Tab Array",
|
||||
"properties": {
|
||||
"tabArrayItems": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"one": {
|
||||
"type": "string"
|
||||
},
|
||||
"two": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"form": [
|
||||
{
|
||||
"key": "tabArrayItems",
|
||||
"type": "tabarray",
|
||||
"title": "{$ 'Tab '+$index $}",
|
||||
"description": "The 'tabarray' description"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
{
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"title": "Tabs",
|
||||
"properties": {
|
||||
"first": {
|
||||
"type": "string"
|
||||
},
|
||||
"second": {
|
||||
"type": "string"
|
||||
},
|
||||
"third": {
|
||||
"type": "string"
|
||||
},
|
||||
"fourth": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"form": [
|
||||
{
|
||||
"type": "tabs",
|
||||
"tabs": [
|
||||
{
|
||||
"title": "Tab One",
|
||||
"help": "static/dashboard/developer/form-builder/example-forms/example-help.html",
|
||||
"items": [
|
||||
"first",
|
||||
"second"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Tab Two",
|
||||
"help": "static/dashboard/developer/form-builder/example-forms/example-help.html",
|
||||
"items": [
|
||||
"third",
|
||||
"fourth"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
{
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"title": "Text Inputs",
|
||||
"properties": {
|
||||
"aString": {
|
||||
"type": "string"
|
||||
},
|
||||
"aTextArea": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"form": [
|
||||
{
|
||||
"key": "aString",
|
||||
"title": "This is a 'string' input ",
|
||||
"description": "This is the 'string' input description",
|
||||
"placeholder": "Prompt for input"
|
||||
},
|
||||
{
|
||||
"key": "aTextArea",
|
||||
"type": "textarea",
|
||||
"title": "This is a 'textarea' input ",
|
||||
"description": "This is the 'textarea' input description",
|
||||
"placeholder": "Prompt for input"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,189 @@
|
||||
/*
|
||||
* (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('horizon.dashboard.developer.form-builder')
|
||||
.controller('horizon.dashboard.developer.form-builder.FormBuilderController', controller);
|
||||
|
||||
controller.$inject = [
|
||||
'$http',
|
||||
'$scope',
|
||||
'horizon.framework.widgets.form.ModalFormService',
|
||||
'horizon.dashboard.developer.form-builder.basePath',
|
||||
'horizon.framework.util.i18n.gettext',
|
||||
];
|
||||
|
||||
/**
|
||||
* @ngdoc controller
|
||||
* @name horizon.dashboard.developer.form-builder:FormBuilderController
|
||||
* @description
|
||||
* This controller allows the launching of any actions registered for resource types
|
||||
*/
|
||||
function controller($http, $scope, modalFormService, basePath, gettext) {
|
||||
var ctrl = this;
|
||||
ctrl.schemaParses = true;
|
||||
ctrl.formParses = true;
|
||||
ctrl.availableForms = [
|
||||
{
|
||||
name: gettext("Text Inputs"),
|
||||
data: basePath + 'example-forms/text-inputs.json'
|
||||
},
|
||||
{
|
||||
name: gettext("Buttons"),
|
||||
data: basePath + 'example-forms/buttons.json'
|
||||
},
|
||||
{
|
||||
name: gettext("Radios, Checkboxes and Select"),
|
||||
data: basePath + 'example-forms/radios-checkboxes-select.json'
|
||||
},
|
||||
{
|
||||
name: gettext("Sections and Fieldsets"),
|
||||
data: basePath + 'example-forms/sections-fieldsets.json'
|
||||
},
|
||||
{
|
||||
name: gettext("Tabs"),
|
||||
data: basePath + 'example-forms/tabs.json'
|
||||
},
|
||||
{
|
||||
name: gettext("Add Ons, Required and Feedback"),
|
||||
data: basePath + 'example-forms/addons-required-feedback.json'
|
||||
},
|
||||
{
|
||||
name: gettext("Array"),
|
||||
data: basePath + 'example-forms/array.json'
|
||||
},
|
||||
{
|
||||
name: gettext("Tab Array"),
|
||||
data: basePath + 'example-forms/tabarray.json'
|
||||
},
|
||||
{
|
||||
name: gettext("A Confirmation Dialog"),
|
||||
data: basePath + 'example-forms/confirmation-dialog.json'
|
||||
}
|
||||
];
|
||||
ctrl.selectedForm = ctrl.availableForms[0];
|
||||
ctrl.model = {};
|
||||
|
||||
ctrl.formJson = JSON.stringify(ctrl.form, undefined, 2);
|
||||
ctrl.schemaJson = JSON.stringify(ctrl.schema, undefined, 2);
|
||||
ctrl.launchCurrentFormAsModal = launchCurrentFormAsModal;
|
||||
ctrl.viewFormJavascript = viewFormJavascript;
|
||||
|
||||
// Update if user selects a new form example
|
||||
$scope.$watch('ctrl.selectedForm',function(item) {
|
||||
if (angular.isDefined(item.data)) {
|
||||
$http.get(item.data).then(function(result) {
|
||||
setNewForm(result.data);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Update if user edits schema JSON
|
||||
$scope.$watch('ctrl.schemaJson',function(val, old) {
|
||||
if (val && val !== old) {
|
||||
try {
|
||||
ctrl.schema = JSON.parse(ctrl.schemaJson);
|
||||
ctrl.schemaParses = true;
|
||||
} catch (e) {
|
||||
ctrl.schemaParses = false;
|
||||
ctrl.schemaError = e.message;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update if the user edits the form JSON
|
||||
$scope.$watch('ctrl.formJson', function(val, old){
|
||||
if (val && val !== old) {
|
||||
try {
|
||||
ctrl.form = JSON.parse(ctrl.formJson);
|
||||
ctrl.formParses = true;
|
||||
} catch (e) {
|
||||
ctrl.formParses = false;
|
||||
ctrl.formError = e.message;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Format the current form model for readability
|
||||
ctrl.prettyModelData = function(){
|
||||
return typeof ctrl.model === 'string' ? ctrl.model : JSON.stringify(ctrl.model, undefined, 2);
|
||||
};
|
||||
|
||||
// Format the current form, schema and model and a single javascript string
|
||||
function prettyFormJavascript(){
|
||||
// Put the user's form into a JavaScript object, including any current model values
|
||||
var currentFormAsObject = {
|
||||
schema: JSON.parse(ctrl.schemaJson),
|
||||
form: JSON.parse(ctrl.formJson),
|
||||
model: ctrl.model
|
||||
};
|
||||
// Convert that to a string so we can use it as input to a schema form model
|
||||
return "var formConfig = " + JSON.stringify(currentFormAsObject, undefined, 2) + ";";
|
||||
}
|
||||
|
||||
// Set the builder to loaded form data
|
||||
function setNewForm(data) {
|
||||
ctrl.schema = data.schema;
|
||||
ctrl.form = data.form;
|
||||
ctrl.schemaJson = JSON.stringify(ctrl.schema, undefined, 2);
|
||||
ctrl.formJson = JSON.stringify(ctrl.form, undefined, 2);
|
||||
ctrl.model = data.model || {};
|
||||
}
|
||||
|
||||
// Show the user what the current form looks like as a modal
|
||||
function launchCurrentFormAsModal() {
|
||||
var config = {
|
||||
schema: ctrl.schema,
|
||||
form: ctrl.form,
|
||||
model: ctrl.model
|
||||
};
|
||||
return modalFormService.open(config);
|
||||
}
|
||||
|
||||
// Show the user the current form as javascript for copy-paste
|
||||
function viewFormJavascript() {
|
||||
// Build a schema form to display the user's form
|
||||
var viewJsSchema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"formJs": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
};
|
||||
var viewJsForm = [
|
||||
{
|
||||
"key": "formJs",
|
||||
"type": "template",
|
||||
"templateUrl": basePath + "form-config-modal.html"
|
||||
}
|
||||
];
|
||||
var viewJsModel = {
|
||||
"formJS": prettyFormJavascript(),
|
||||
};
|
||||
var config = {
|
||||
schema: viewJsSchema,
|
||||
form: viewJsForm,
|
||||
model: viewJsModel,
|
||||
title: gettext("Your Form as JavaScript")
|
||||
};
|
||||
return modalFormService.open(config);
|
||||
}
|
||||
}
|
||||
})();
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('horizon.dashboard.developer.form-builder')
|
||||
.directive('formBuilder', directive);
|
||||
|
||||
directive.$inject = ['horizon.dashboard.developer.form-builder.basePath'];
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name form-builder
|
||||
* @description
|
||||
* Show a schema form builder
|
||||
*/
|
||||
|
||||
function directive(path) {
|
||||
var directive = {
|
||||
restrict: 'E',
|
||||
templateUrl: path + 'form-builder.html',
|
||||
scope: {},
|
||||
controller: 'horizon.dashboard.developer.form-builder.FormBuilderController as ctrl'
|
||||
};
|
||||
|
||||
return directive;
|
||||
}
|
||||
})();
|
||||
|
@ -0,0 +1,58 @@
|
||||
<h1 translate>Live Edit Schema Form Examples</h1>
|
||||
<p translate>This page contains examples of the different fields provided by Schema Form in Horizon. The <code>Schema</code> and <code>Form</code> sections below can be edited inline to customise the form output. For further documentation on Schema Form, see <a href="http://schemaform.io/">http://schemaform.io/</a>.</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<h2 class="h3">
|
||||
<span translate>Form Output</span>
|
||||
|
||||
<button class="btn btn-default"
|
||||
type="button"
|
||||
ng-disabled="!(ctrl.formParses && ctrl.schemaParses)"
|
||||
ng-click="ctrl.launchCurrentFormAsModal()">
|
||||
<span translate>View As Modal</span>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-default"
|
||||
type="button"
|
||||
ng-disabled="!(ctrl.formParses && ctrl.schemaParses)"
|
||||
ng-click="ctrl.viewFormJavascript()">
|
||||
<span translate>View Form JSON</span>
|
||||
</button>
|
||||
</h2>
|
||||
|
||||
<div class="modal-content form-builder-modal">
|
||||
<div class="modal-header">
|
||||
<div class="h4 modal-title">{$ ctrl.schema.title $}</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form name="schemaForm"
|
||||
sf-model="ctrl.model"
|
||||
sf-form="ctrl.form"
|
||||
sf-schema="ctrl.schema"
|
||||
ng-submit="ctrl.submitForm(schemaForm, ctrl.model)">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="h3" translate>Model</h2>
|
||||
<pre>{$ ctrl.prettyModelData() $}</pre>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6">
|
||||
<h2 class="h3" translate>Select Example</h2>
|
||||
<select class="form-control"
|
||||
ng-model="ctrl.selectedForm"
|
||||
ng-options="item.name for item in ctrl.availableForms">
|
||||
</select>
|
||||
|
||||
<h2 class="h3" translate>Schema</h2>
|
||||
<p translate>Use the schema to describe the data model and valid input patterns. A schema is always JSON. NOTE: A schema element is not needed for items that have no data model, like sections and fieldsets.</p>
|
||||
<pre class="code-input" contenteditable ng-keydown="ctrl.onKeydown($event)" ng-model="ctrl.schemaJson"></pre>
|
||||
<div class="alert alert-danger" role="alert" ng-hide="ctrl.schemaParses">{{ ctrl.schemaError }}</div>
|
||||
<h2 class="h3" translate>Form</h2>
|
||||
<p translate>Use the form to set titles, descriptions and choose specific UI element types for each schema item. In the form builder, the form must be JSON, but you can use JavaScript in production code, such as <code>gettext</code> for translation.</p>
|
||||
<pre class="code-input" contenteditable ng-keydown="ctrl.onKeydown($event)" ng-model="ctrl.formJson"></pre>
|
||||
<div class="alert alert-danger" role="alert" ng-hide="ctrl.formParses">{{ ctrl.formError }}</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc module
|
||||
* @ngname horizon.dashboard.developer.form-builder
|
||||
* @description
|
||||
* Dashboard module for the form-builder panel.
|
||||
*/
|
||||
angular
|
||||
.module('horizon.dashboard.developer.form-builder', [], config)
|
||||
.constant('horizon.dashboard.developer.form-builder.BASE_ROUTE', '/developer/form_builder/');
|
||||
|
||||
config.$inject = [
|
||||
'$provide',
|
||||
'$routeProvider',
|
||||
'$windowProvider',
|
||||
'horizon.dashboard.developer.form-builder.BASE_ROUTE'];
|
||||
|
||||
function config($provide, $routeProvider, $windowProvider, baseRoute) {
|
||||
$routeProvider
|
||||
.when(baseRoute, {
|
||||
templateUrl: $windowProvider.$get().STATIC_URL +
|
||||
'dashboard/developer/form-builder/index.html'
|
||||
});
|
||||
var path = $windowProvider.$get().STATIC_URL + 'dashboard/developer/form-builder/';
|
||||
$provide.constant('horizon.dashboard.developer.form-builder.basePath', path);
|
||||
}
|
||||
|
||||
})();
|
@ -0,0 +1,9 @@
|
||||
// Since this is inline on the form-builder demo page,
|
||||
// override the max-height / scrolling hack, and the shadow
|
||||
.form-builder-modal {
|
||||
box-shadow: none;
|
||||
|
||||
.modal-body {
|
||||
max-height: none;
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
<p translate>This JavaScript object contains your new form and can be used with <code>horizon.framework.widgets.form.ModalFormService</code></p>
|
||||
|
||||
<pre>{$::model.formJS$}</pre>
|
@ -0,0 +1 @@
|
||||
<form-builder></form-builder>
|
@ -0,0 +1,7 @@
|
||||
// Workarounds for some browsers inserting <div> elements when editing
|
||||
.code-input {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
color: $kbd-color;
|
||||
background-color: $kbd-bg;
|
||||
}
|
@ -19,6 +19,7 @@
|
||||
@import "components/breadcrumbs";
|
||||
@import "components/charts";
|
||||
@import "components/checkboxes";
|
||||
@import "components/code";
|
||||
@import "components/datepicker";
|
||||
@import "components/dl_lists";
|
||||
@import "components/dropdowns";
|
||||
|
Loading…
Reference in New Issue
Block a user