diff --git a/horizon/static/framework/widgets/contenteditable/contenteditable.directive.js b/horizon/static/framework/widgets/contenteditable/contenteditable.directive.js
new file mode 100644
index 0000000000..401a3fbcc0
--- /dev/null
+++ b/horizon/static/framework/widgets/contenteditable/contenteditable.directive.js
@@ -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());
+ }
+ }
+ }
+})();
diff --git a/horizon/static/framework/widgets/contenteditable/contenteditable.directive.spec.js b/horizon/static/framework/widgets/contenteditable/contenteditable.directive.spec.js
new file mode 100644
index 0000000000..275af4db41
--- /dev/null
+++ b/horizon/static/framework/widgets/contenteditable/contenteditable.directive.spec.js
@@ -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('
')($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('')($scope);
+ $scope.$digest();
+
+ element.triggerHandler('focus');
+ element.html('bar');
+ $scope.$digest();
+ element.triggerHandler('blur');
+
+ expect($scope.testData).toBe('');
+ });
+ });
+})();
diff --git a/horizon/static/framework/widgets/contenteditable/contenteditable.module.js b/horizon/static/framework/widgets/contenteditable/contenteditable.module.js
new file mode 100644
index 0000000000..ea92536b84
--- /dev/null
+++ b/horizon/static/framework/widgets/contenteditable/contenteditable.module.js
@@ -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', []);
+})();
diff --git a/horizon/static/framework/widgets/form/fields/array.html b/horizon/static/framework/widgets/form/fields/array.html
index 0af7b93da3..9d0667ecf8 100644
--- a/horizon/static/framework/widgets/form/fields/array.html
+++ b/horizon/static/framework/widgets/form/fields/array.html
@@ -2,8 +2,8 @@
sf-field-model="sf-new-array"
sf-new-array>
-
-
\ No newline at end of file
diff --git a/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/example-forms/radios-checkboxes-select.json b/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/example-forms/radios-checkboxes-select.json
new file mode 100644
index 0000000000..e43a376c80
--- /dev/null
+++ b/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/example-forms/radios-checkboxes-select.json
@@ -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)"
+ }
+ ]
+ }
+ ]
+}
diff --git a/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/example-forms/sections-fieldsets.json b/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/example-forms/sections-fieldsets.json
new file mode 100644
index 0000000000..60869b900c
--- /dev/null
+++ b/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/example-forms/sections-fieldsets.json
@@ -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"
+ ]
+ }
+ ]
+}
diff --git a/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/example-forms/tabarray.json b/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/example-forms/tabarray.json
new file mode 100644
index 0000000000..de1cbccca1
--- /dev/null
+++ b/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/example-forms/tabarray.json
@@ -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"
+ }
+ ]
+}
diff --git a/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/example-forms/tabs.json b/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/example-forms/tabs.json
new file mode 100644
index 0000000000..a271ae4b06
--- /dev/null
+++ b/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/example-forms/tabs.json
@@ -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"
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/example-forms/text-inputs.json b/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/example-forms/text-inputs.json
new file mode 100644
index 0000000000..4bfeb7dee5
--- /dev/null
+++ b/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/example-forms/text-inputs.json
@@ -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"
+ }
+ ]
+}
diff --git a/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/form-builder.controller.js b/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/form-builder.controller.js
new file mode 100644
index 0000000000..61a61af031
--- /dev/null
+++ b/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/form-builder.controller.js
@@ -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);
+ }
+ }
+})();
diff --git a/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/form-builder.directive.js b/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/form-builder.directive.js
new file mode 100644
index 0000000000..580d774b9e
--- /dev/null
+++ b/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/form-builder.directive.js
@@ -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;
+ }
+})();
+
diff --git a/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/form-builder.html b/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/form-builder.html
new file mode 100644
index 0000000000..809e272843
--- /dev/null
+++ b/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/form-builder.html
@@ -0,0 +1,58 @@
+
Live Edit Schema Form Examples
+
This page contains examples of the different fields provided by Schema Form in Horizon. The Schema and Form sections below can be edited inline to customise the form output. For further documentation on Schema Form, see http://schemaform.io/.
+
+
+
+
+ Form Output
+
+
+
+
+
+
+
+
+
{$ ctrl.schema.title $}
+
+
+
+
+
+
+
Model
+
{$ ctrl.prettyModelData() $}
+
+
+
+
Select Example
+
+
+
Schema
+
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.
+
+
{{ ctrl.schemaError }}
+
Form
+
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 gettext for translation.
+
+
{{ ctrl.formError }}
+
+
diff --git a/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/form-builder.module.js b/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/form-builder.module.js
new file mode 100644
index 0000000000..a72107799a
--- /dev/null
+++ b/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/form-builder.module.js
@@ -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);
+ }
+
+})();
diff --git a/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/form-builder.scss b/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/form-builder.scss
new file mode 100644
index 0000000000..8f93827b5f
--- /dev/null
+++ b/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/form-builder.scss
@@ -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;
+ }
+}
diff --git a/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/form-config-modal.html b/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/form-config-modal.html
new file mode 100644
index 0000000000..046adae1c9
--- /dev/null
+++ b/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/form-config-modal.html
@@ -0,0 +1,3 @@
+
This JavaScript object contains your new form and can be used with horizon.framework.widgets.form.ModalFormService
+
+
{$::model.formJS$}
diff --git a/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/index.html b/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/index.html
new file mode 100644
index 0000000000..6110424a30
--- /dev/null
+++ b/openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/index.html
@@ -0,0 +1 @@
+
diff --git a/openstack_dashboard/static/dashboard/scss/components/_code.scss b/openstack_dashboard/static/dashboard/scss/components/_code.scss
new file mode 100644
index 0000000000..cda7ac0380
--- /dev/null
+++ b/openstack_dashboard/static/dashboard/scss/components/_code.scss
@@ -0,0 +1,7 @@
+// Workarounds for some browsers inserting