From 6d7d3e226881c500f133f5e1b29e087e3aa00471 Mon Sep 17 00:00:00 2001 From: Karel Hala Date: Thu, 31 Aug 2017 10:48:00 +0200 Subject: [PATCH 01/17] Add extensible components API to create components which can expose API and render endpoints --- app/javascript/custom-typings.ts | 2 + app/javascript/extensible-components/index.ts | 50 +++++++++++++++++++ app/javascript/extensible-components/lib.ts | 28 +++++++++++ .../packs/extensible-componenets-common.js | 1 + 4 files changed, 81 insertions(+) create mode 100644 app/javascript/custom-typings.ts create mode 100644 app/javascript/extensible-components/index.ts create mode 100644 app/javascript/extensible-components/lib.ts create mode 100644 app/javascript/packs/extensible-componenets-common.js diff --git a/app/javascript/custom-typings.ts b/app/javascript/custom-typings.ts new file mode 100644 index 00000000000..1ba5fa88554 --- /dev/null +++ b/app/javascript/custom-typings.ts @@ -0,0 +1,2 @@ +declare var ManageIQ: any; +declare var Rx: any; \ No newline at end of file diff --git a/app/javascript/extensible-components/index.ts b/app/javascript/extensible-components/index.ts new file mode 100644 index 00000000000..37afb5fdf29 --- /dev/null +++ b/app/javascript/extensible-components/index.ts @@ -0,0 +1,50 @@ +import { IExtensionComponent, IMiQApiCallback } from './lib'; + +/** + * Class for easy creation of extensible component. + */ +export class ExtensibleComponent { + public unsubscribe: Function; + constructor(public name: string, public api: IMiQApiCallback, public render: IMiQApiCallback){} +} + +/** + * Create new object which will hold extension components on MiQ main object. + */ +ManageIQ.extensionComponents = ManageIQ.extensionComponents || {}; + +/** + * Subject from from Rxjs to send message that we want to register new component. + */ +ManageIQ.extensionComponents.source = new Rx.Subject(); + +/** + * Components will be saved in items which will be set. To easy remove of components which no longer exists. + */ +ManageIQ.extensionComponents.items = new Set(); + +/** + * Helper function to create new component. + * @param name string name of new component. + * @param api callback functions to change inner logic of component. + * @param render callback function to apply render functions. + */ +ManageIQ.extensionComponents.newComponent = function(name: string, api?: IMiQApiCallback, render?: IMiQApiCallback) { + let newCmp = new ExtensibleComponent(name, api, render); + ManageIQ.extensionComponents.source.onNext({action: 'add', payload: newCmp}); + return newCmp; +} + +/** + * Subscribe to extensionComponents source to add new components to items object. + */ +ManageIQ.extensionComponents.source.subscribe((event: IExtensionComponent) => { + if (event.action === 'add' && event.hasOwnProperty('payload')) { + event.payload.unsubscribe = () => { + ManageIQ.extensionComponents.items.delete(event.payload); + } + ManageIQ.extensionComponents.items.add(event.payload); + } else { + throw new Error('Unsupported action with extension components.'); + } +}); \ No newline at end of file diff --git a/app/javascript/extensible-components/lib.ts b/app/javascript/extensible-components/lib.ts new file mode 100644 index 00000000000..d98cd336d67 --- /dev/null +++ b/app/javascript/extensible-components/lib.ts @@ -0,0 +1,28 @@ +export interface IExtensionComponent { + action: string; + payload: any; +} + +export interface IMiQApiCallback { + [propName: string]: Function; +} + +export interface IExtensibleComponent { + extensibleComponent: any; + apiCallbacks: () => IMiQApiCallback; + renderCallbacks: () => IMiQApiCallback; +} + +export const extensionSource = ManageIQ.extensionComponents.source; + +export const extensionItems = ManageIQ.extensionComponents.items; + +/** + * Helper function to create new component. + * @param name string name of new component. + * @param api callback functions to change inner logic of component. + * @param render callback function to apply render functions. + */ +export function newComponent(name: string, api?: IMiQApiCallback, render?: IMiQApiCallback) { + return ManageIQ.extensionComponents.newComponent(name, api, render); +} \ No newline at end of file diff --git a/app/javascript/packs/extensible-componenets-common.js b/app/javascript/packs/extensible-componenets-common.js new file mode 100644 index 00000000000..52db0e70974 --- /dev/null +++ b/app/javascript/packs/extensible-componenets-common.js @@ -0,0 +1 @@ +require('../extensible-components'); \ No newline at end of file From 3ddc11158f3dc23419b6827cd8aa609b5d0c83cb Mon Sep 17 00:00:00 2001 From: Karel Hala Date: Thu, 31 Aug 2017 15:18:33 +0200 Subject: [PATCH 02/17] Add registrator function for extensible components --- app/javascript/custom-typings.ts | 2 +- app/javascript/extensible-components/index.ts | 19 ++++++++++++++++++- app/javascript/extensible-components/lib.ts | 2 +- .../packs/extensible-componenets-common.js | 2 +- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/app/javascript/custom-typings.ts b/app/javascript/custom-typings.ts index 1ba5fa88554..f0b394b20aa 100644 --- a/app/javascript/custom-typings.ts +++ b/app/javascript/custom-typings.ts @@ -1,2 +1,2 @@ declare var ManageIQ: any; -declare var Rx: any; \ No newline at end of file +declare var Rx: any; diff --git a/app/javascript/extensible-components/index.ts b/app/javascript/extensible-components/index.ts index 37afb5fdf29..697de49fb42 100644 --- a/app/javascript/extensible-components/index.ts +++ b/app/javascript/extensible-components/index.ts @@ -47,4 +47,21 @@ ManageIQ.extensionComponents.source.subscribe((event: IExtensionComponent) => { } else { throw new Error('Unsupported action with extension components.'); } -}); \ No newline at end of file +}); + +ManageIQ.extensionComponents.subscribe = function(cmpName: string) { + let unsubscribe; + return { + with: (callback) => { + unsubscribe = ManageIQ.extensionComponents.source + .map(sourceAction => sourceAction.action === 'add' ? sourceAction.payload : {}) + .filter(component => component && component.name === cmpName) + .subscribe(cmp => cmp && callback(cmp)); + + ManageIQ.extensionComponents.items.forEach((component) => { + component.name === cmpName && callback(component); + }); + }, + delete: () => unsubscribe && unsubscribe() + } +} diff --git a/app/javascript/extensible-components/lib.ts b/app/javascript/extensible-components/lib.ts index d98cd336d67..356d95fb90b 100644 --- a/app/javascript/extensible-components/lib.ts +++ b/app/javascript/extensible-components/lib.ts @@ -25,4 +25,4 @@ export const extensionItems = ManageIQ.extensionComponents.items; */ export function newComponent(name: string, api?: IMiQApiCallback, render?: IMiQApiCallback) { return ManageIQ.extensionComponents.newComponent(name, api, render); -} \ No newline at end of file +} diff --git a/app/javascript/packs/extensible-componenets-common.js b/app/javascript/packs/extensible-componenets-common.js index 52db0e70974..6cabd5ac4c4 100644 --- a/app/javascript/packs/extensible-componenets-common.js +++ b/app/javascript/packs/extensible-componenets-common.js @@ -1 +1 @@ -require('../extensible-components'); \ No newline at end of file +require('../extensible-components'); From 400e0a79f5c7987f6ec5b298d6a655b1717e561e Mon Sep 17 00:00:00 2001 From: Karel Hala Date: Fri, 1 Sep 2017 17:01:19 +0200 Subject: [PATCH 03/17] Use better styling and rename some variables in extensible component --- app/javascript/extensible-components/index.ts | 66 +++++++++---------- app/javascript/extensible-components/lib.ts | 12 ++-- 2 files changed, 41 insertions(+), 37 deletions(-) diff --git a/app/javascript/extensible-components/index.ts b/app/javascript/extensible-components/index.ts index 697de49fb42..d3ac3a3f63c 100644 --- a/app/javascript/extensible-components/index.ts +++ b/app/javascript/extensible-components/index.ts @@ -1,67 +1,67 @@ import { IExtensionComponent, IMiQApiCallback } from './lib'; +const source = new Rx.Subject(); +const items = new Set(); + /** * Class for easy creation of extensible component. */ export class ExtensibleComponent { - public unsubscribe: Function; + public delete: () => void; constructor(public name: string, public api: IMiQApiCallback, public render: IMiQApiCallback){} } -/** - * Create new object which will hold extension components on MiQ main object. - */ -ManageIQ.extensionComponents = ManageIQ.extensionComponents || {}; - -/** - * Subject from from Rxjs to send message that we want to register new component. - */ -ManageIQ.extensionComponents.source = new Rx.Subject(); - -/** - * Components will be saved in items which will be set. To easy remove of components which no longer exists. - */ -ManageIQ.extensionComponents.items = new Set(); - /** * Helper function to create new component. * @param name string name of new component. * @param api callback functions to change inner logic of component. * @param render callback function to apply render functions. */ -ManageIQ.extensionComponents.newComponent = function(name: string, api?: IMiQApiCallback, render?: IMiQApiCallback) { +function addComponent(name: string, api?: IMiQApiCallback, render?: IMiQApiCallback) { let newCmp = new ExtensibleComponent(name, api, render); - ManageIQ.extensionComponents.source.onNext({action: 'add', payload: newCmp}); + source.onNext({action: 'add', payload: newCmp}); return newCmp; } /** - * Subscribe to extensionComponents source to add new components to items object. + * Helper function to subscribe to extension items based on component's name. + * @param cmpName name of component we want to subscribe to. + * @return object which has with and unsubscribe property. With is for callback to use found component and delete to + * unsubscribe from rxjs subject. */ -ManageIQ.extensionComponents.source.subscribe((event: IExtensionComponent) => { - if (event.action === 'add' && event.hasOwnProperty('payload')) { - event.payload.unsubscribe = () => { - ManageIQ.extensionComponents.items.delete(event.payload); - } - ManageIQ.extensionComponents.items.add(event.payload); - } else { - throw new Error('Unsupported action with extension components.'); - } -}); - -ManageIQ.extensionComponents.subscribe = function(cmpName: string) { +function subscribe(cmpName: string) { let unsubscribe; return { with: (callback) => { - unsubscribe = ManageIQ.extensionComponents.source + unsubscribe = source .map(sourceAction => sourceAction.action === 'add' ? sourceAction.payload : {}) .filter(component => component && component.name === cmpName) .subscribe(cmp => cmp && callback(cmp)); - ManageIQ.extensionComponents.items.forEach((component) => { + ManageIQ.extensions.items.forEach((component) => { component.name === cmpName && callback(component); }); }, delete: () => unsubscribe && unsubscribe() } } + +const extensions = { + addComponent, + subscribe, + get items() { return items; } +} + +ManageIQ.extensions = ManageIQ.extensions || extensions; + +/** + * Subscribe to extensions source to add new components to items object. + */ +source.subscribe((component: IExtensionComponent) => { + if (component.action === 'add' && component.hasOwnProperty('payload')) { + component.payload.delete = () => ManageIQ.extensions.items.delete(component.payload); + ManageIQ.extensions.items.add(component.payload); + } else { + console.error('Unsupported action with extension components.'); + } +}); diff --git a/app/javascript/extensible-components/lib.ts b/app/javascript/extensible-components/lib.ts index 356d95fb90b..99f0fb4f239 100644 --- a/app/javascript/extensible-components/lib.ts +++ b/app/javascript/extensible-components/lib.ts @@ -13,9 +13,13 @@ export interface IExtensibleComponent { renderCallbacks: () => IMiQApiCallback; } -export const extensionSource = ManageIQ.extensionComponents.source; +export function getItems() { + return ManageIQ.extensions.getItems(); +} -export const extensionItems = ManageIQ.extensionComponents.items; +export function subscribe(cmpName: string) { + return ManageIQ.extensions.subscribe(cmpName); +} /** * Helper function to create new component. @@ -23,6 +27,6 @@ export const extensionItems = ManageIQ.extensionComponents.items; * @param api callback functions to change inner logic of component. * @param render callback function to apply render functions. */ -export function newComponent(name: string, api?: IMiQApiCallback, render?: IMiQApiCallback) { - return ManageIQ.extensionComponents.newComponent(name, api, render); +export function addComponent(name: string, api?: IMiQApiCallback, render?: IMiQApiCallback) { + return ManageIQ.extensions.addComponent(name, api, render); } From 0bc16759bd1bf7ab10630cdc1ae8dda663cee462 Mon Sep 17 00:00:00 2001 From: Karel Hala Date: Wed, 6 Sep 2017 15:10:56 +0200 Subject: [PATCH 04/17] Add tests to extensible component --- app/javascript/extensible-components/index.ts | 2 +- app/javascript/extensible-components/lib.ts | 2 +- .../packs/extensible-componenets.spec.js | 75 +++++++++++++++++++ spec/javascripts/support/jasmine.yml | 2 +- 4 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 spec/javascripts/packs/extensible-componenets.spec.js diff --git a/app/javascript/extensible-components/index.ts b/app/javascript/extensible-components/index.ts index d3ac3a3f63c..e10bc4a1360 100644 --- a/app/javascript/extensible-components/index.ts +++ b/app/javascript/extensible-components/index.ts @@ -42,7 +42,7 @@ function subscribe(cmpName: string) { component.name === cmpName && callback(component); }); }, - delete: () => unsubscribe && unsubscribe() + delete: () => unsubscribe && unsubscribe.dispose() } } diff --git a/app/javascript/extensible-components/lib.ts b/app/javascript/extensible-components/lib.ts index 99f0fb4f239..8bfd071f7f7 100644 --- a/app/javascript/extensible-components/lib.ts +++ b/app/javascript/extensible-components/lib.ts @@ -14,7 +14,7 @@ export interface IExtensibleComponent { } export function getItems() { - return ManageIQ.extensions.getItems(); + return ManageIQ.extensions.items; } export function subscribe(cmpName: string) { diff --git a/spec/javascripts/packs/extensible-componenets.spec.js b/spec/javascripts/packs/extensible-componenets.spec.js new file mode 100644 index 00000000000..3cb7124ec75 --- /dev/null +++ b/spec/javascripts/packs/extensible-componenets.spec.js @@ -0,0 +1,75 @@ +describe('Extensible components', function() { + var cmp; + var mockApi = { + onSomeAction: jasmine.createSpy('onSomeAction', function() {}), + onSomeActionWithParams: jasmine.createSpy('onSomeActionWithParams', function(param) {}), + }; + + var mockRender = { + addButtonHtml: jasmine.createSpy('addButtonHtml', function(htmlElem) {}) + }; + + it('should be defined with empty items', function() { + expect(ManageIQ.extensions).toBeDefined(); + expect(ManageIQ.extensions.items.size).toBe(0); + }); + + it('should create one item', function() { + cmp = ManageIQ.extensions.addComponent('testCmp', mockApi, mockRender); + expect(ManageIQ.extensions.items.size).toBe(1); + }); + + it('should remove newly created item', function() { + cmp.delete(); + expect(ManageIQ.extensions.items.size).toBe(0); + }); + + describe('default actions', function() { + var subscription; + var someParam = 'something'; + + beforeEach(function() { + cmp = ManageIQ.extensions.addComponent('testCmp', mockApi, mockRender); + }); + + describe('subscription', function() { + it('should subscribe based on name', function() { + subscription = ManageIQ.extensions.subscribe('testCmp'); + expect(subscription.delete).toBeDefined(); + expect(subscription.with).toBeDefined(); + }); + + it('should react to API', function() { + subscription.with(function(component) { + component.api.onSomeAction(); + component.api.onSomeActionWithParams(someParam); + }); + expect(mockApi.onSomeAction).toHaveBeenCalled(); + expect(mockApi.onSomeActionWithParams).toHaveBeenCalledWith(someParam); + }); + + it('should react to render', function() { + var someHTML = '
something
'; + subscription.with(function(component) { + component.render.addButtonHtml(someHTML); + }); + expect(mockRender.addButtonHtml).toHaveBeenCalledWith(someHTML); + }); + }); + + it('should not subscribe', function() { + subscription = ManageIQ.extensions.subscribe('somethingBad'); + subscription.with(function(component) { + //callback should not be called! + expect(false).toBe(true); + }); + expect(subscription.with).toBeDefined(); + expect(subscription.delete).toBeDefined(); + }); + + afterEach(function() { + cmp.delete(); + subscription && subscription.delete(); + }); + }); +}); diff --git a/spec/javascripts/support/jasmine.yml b/spec/javascripts/support/jasmine.yml index 97242487bf7..b7bba30c038 100644 --- a/spec/javascripts/support/jasmine.yml +++ b/spec/javascripts/support/jasmine.yml @@ -18,6 +18,7 @@ src_files: - assets/angular-mocks - __spec__/helpers/fixtures-fix.js - packs/manageiq-ui-classic/application-common.js + - packs/manageiq-ui-classic/extensible-componenets-common.js # stylesheets # @@ -126,4 +127,3 @@ boot_files: # # rack_options: # server: 'thin' - From 1aaa1e34c08fb3e7d0cc732372ddb24b72d5a1c6 Mon Sep 17 00:00:00 2001 From: Karel Hala Date: Wed, 6 Sep 2017 17:23:24 +0200 Subject: [PATCH 05/17] Update render description to accept function callback with element in extensible component --- app/javascript/extensible-components/index.ts | 6 +++--- app/javascript/extensible-components/lib.ts | 8 +++++++- .../packs/extensible-componenets.spec.js | 14 +++++++++----- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/app/javascript/extensible-components/index.ts b/app/javascript/extensible-components/index.ts index e10bc4a1360..412e9d96e97 100644 --- a/app/javascript/extensible-components/index.ts +++ b/app/javascript/extensible-components/index.ts @@ -1,4 +1,4 @@ -import { IExtensionComponent, IMiQApiCallback } from './lib'; +import { IExtensionComponent, IMiQApiCallback, IMiQRenderCallback } from './lib'; const source = new Rx.Subject(); const items = new Set(); @@ -8,7 +8,7 @@ const items = new Set(); */ export class ExtensibleComponent { public delete: () => void; - constructor(public name: string, public api: IMiQApiCallback, public render: IMiQApiCallback){} + constructor(public name: string, public api: IMiQApiCallback, public render: IMiQRenderCallback){} } /** @@ -17,7 +17,7 @@ export class ExtensibleComponent { * @param api callback functions to change inner logic of component. * @param render callback function to apply render functions. */ -function addComponent(name: string, api?: IMiQApiCallback, render?: IMiQApiCallback) { +function addComponent(name: string, api?: IMiQApiCallback, render?: IMiQRenderCallback) { let newCmp = new ExtensibleComponent(name, api, render); source.onNext({action: 'add', payload: newCmp}); return newCmp; diff --git a/app/javascript/extensible-components/lib.ts b/app/javascript/extensible-components/lib.ts index 8bfd071f7f7..2393749911f 100644 --- a/app/javascript/extensible-components/lib.ts +++ b/app/javascript/extensible-components/lib.ts @@ -1,3 +1,5 @@ +export type RenderCallback = (element: HTMLElement) => void; + export interface IExtensionComponent { action: string; payload: any; @@ -7,10 +9,14 @@ export interface IMiQApiCallback { [propName: string]: Function; } +export interface IMiQRenderCallback { + [propName: string]: (renderCallback: RenderCallback) => void; +} + export interface IExtensibleComponent { extensibleComponent: any; apiCallbacks: () => IMiQApiCallback; - renderCallbacks: () => IMiQApiCallback; + renderCallbacks: () => IMiQRenderCallback; } export function getItems() { diff --git a/spec/javascripts/packs/extensible-componenets.spec.js b/spec/javascripts/packs/extensible-componenets.spec.js index 3cb7124ec75..6bfc2b2944f 100644 --- a/spec/javascripts/packs/extensible-componenets.spec.js +++ b/spec/javascripts/packs/extensible-componenets.spec.js @@ -1,12 +1,14 @@ describe('Extensible components', function() { + var htmlPartial = '
something
'; var cmp; var mockApi = { onSomeAction: jasmine.createSpy('onSomeAction', function() {}), - onSomeActionWithParams: jasmine.createSpy('onSomeActionWithParams', function(param) {}), + onSomeActionWithParams: jasmine.createSpy('onSomeActionWithParams', function(someParam){}), }; var mockRender = { - addButtonHtml: jasmine.createSpy('addButtonHtml', function(htmlElem) {}) + addButton: jasmine.createSpy('addButton', function(callback) {}), + addButton2: function(callback) { callback(htmlPartial); } }; it('should be defined with empty items', function() { @@ -49,11 +51,13 @@ describe('Extensible components', function() { }); it('should react to render', function() { - var someHTML = '
something
'; + var someCallback = jasmine.createSpy('someCallback', function(element) {}); subscription.with(function(component) { - component.render.addButtonHtml(someHTML); + component.render.addButton(someCallback); + component.render.addButton2(someCallback); }); - expect(mockRender.addButtonHtml).toHaveBeenCalledWith(someHTML); + expect(mockRender.addButton).toHaveBeenCalledWith(someCallback); + expect(someCallback).toHaveBeenCalledWith(htmlPartial); }); }); From 41116ef3446b7749817151e7f9da671764a1f51f Mon Sep 17 00:00:00 2001 From: Karel Hala Date: Wed, 6 Sep 2017 17:23:24 +0200 Subject: [PATCH 06/17] Update render description to accept function callback with element in extensible component --- app/javascript/extensible-components/lib.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/javascript/extensible-components/lib.ts b/app/javascript/extensible-components/lib.ts index 2393749911f..a5248b58ae3 100644 --- a/app/javascript/extensible-components/lib.ts +++ b/app/javascript/extensible-components/lib.ts @@ -1,5 +1,4 @@ export type RenderCallback = (element: HTMLElement) => void; - export interface IExtensionComponent { action: string; payload: any; From b6d6aaee89315b2e2cfd6f22786dbdd90471432b Mon Sep 17 00:00:00 2001 From: Karel Hala Date: Wed, 16 Aug 2017 15:41:55 +0200 Subject: [PATCH 07/17] New component for form with controller --- app/javascript/middleware/forms/index.ts | 3 +++ app/javascript/middleware/forms/new.ts | 15 +++++++++++++++ app/javascript/packs/custom-typings.ts | 1 + app/javascript/packs/middleware_forms.js | 1 + app/views/ems_block_storage/new.html.haml | 1 - app/views/ems_middleware/new.html.haml | 3 ++- package.json | 1 + 7 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 app/javascript/middleware/forms/index.ts create mode 100644 app/javascript/middleware/forms/new.ts create mode 100644 app/javascript/packs/custom-typings.ts create mode 100644 app/javascript/packs/middleware_forms.js diff --git a/app/javascript/middleware/forms/index.ts b/app/javascript/middleware/forms/index.ts new file mode 100644 index 00000000000..b5754bfb568 --- /dev/null +++ b/app/javascript/middleware/forms/index.ts @@ -0,0 +1,3 @@ +import NewProviderForm from './new'; + +ManageIQ.angular.app.component('newProviderForm', new NewProviderForm); \ No newline at end of file diff --git a/app/javascript/middleware/forms/new.ts b/app/javascript/middleware/forms/new.ts new file mode 100644 index 00000000000..b7a4f6dd566 --- /dev/null +++ b/app/javascript/middleware/forms/new.ts @@ -0,0 +1,15 @@ +import * as ng from 'angular'; + +export default class NewProviderForm implements ng.IComponentOptions { + public template: string = `
blablabla
`; + public controller: any = NewProviderController; + public controllerAs: string = 'newProv'; + public replace = true; + public bindings: any = {}; +} + +class NewProviderController { + constructor() { + console.log('blaaaa'); + } +} \ No newline at end of file diff --git a/app/javascript/packs/custom-typings.ts b/app/javascript/packs/custom-typings.ts new file mode 100644 index 00000000000..105662e3bce --- /dev/null +++ b/app/javascript/packs/custom-typings.ts @@ -0,0 +1 @@ +declare var ManageIQ: any; \ No newline at end of file diff --git a/app/javascript/packs/middleware_forms.js b/app/javascript/packs/middleware_forms.js new file mode 100644 index 00000000000..75877ed416b --- /dev/null +++ b/app/javascript/packs/middleware_forms.js @@ -0,0 +1 @@ +require ('../middleware/forms'); \ No newline at end of file diff --git a/app/views/ems_block_storage/new.html.haml b/app/views/ems_block_storage/new.html.haml index 4e1d22e1d80..f30b6d5284b 100644 --- a/app/views/ems_block_storage/new.html.haml +++ b/app/views/ems_block_storage/new.html.haml @@ -13,7 +13,6 @@ %input{:type => 'hidden', :id => "cred_type", :name => "cred_type", :value => "default"} = render :partial => "shared/views/ems_common/angular/form" - :javascript ManageIQ.angular.app.value('emsCommonFormId', '#{@ems.id || "new"}'); miq_bootstrap($('#form_id').val()); diff --git a/app/views/ems_middleware/new.html.haml b/app/views/ems_middleware/new.html.haml index e6f5dbc5fcf..390a5b44952 100644 --- a/app/views/ems_middleware/new.html.haml +++ b/app/views/ems_middleware/new.html.haml @@ -13,7 +13,8 @@ %input{:type => 'hidden', :id => "cred_type", :name => "cred_type", :value => "default"} = render :partial => "form" - + %new-provider-form += javascript_pack_tag 'manageiq-ui-classic/middleware_forms' :javascript ManageIQ.angular.app.value('emsCommonFormId', '#{@ems.id || "new"}'); miq_bootstrap($('#form_id').val()); diff --git a/package.json b/package.json index f8d0c76dfd8..32a43cd5b0f 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "zone.js": "~0.8.5" }, "devDependencies": { + "@types/angular": "^1.6.29", "autoprefixer": "~6.7.7", "babel-core": "~6.24.1", "babel-eslint": "~6.0.4", From e977f1bddedae757230d57b06937e877619f247b Mon Sep 17 00:00:00 2001 From: Karel Hala Date: Thu, 17 Aug 2017 09:23:10 +0200 Subject: [PATCH 08/17] Enable redux with rootReducer as array of reducers, By Karel Hala and Vojtech Szocs. --- app/javascript/app-redux/app-reducer.ts | 22 +++++++++++++++++++ app/javascript/app-redux/app-store.ts | 19 ++++++++++++++++ app/javascript/app-redux/index.ts | 11 ++++++++++ app/javascript/middleware/forms/new.ts | 10 ++++----- app/javascript/packs/redux-common.js | 1 + app/views/static/middleware/new-provider.html | 2 ++ config/webpack/loaders/html.js | 13 +++++++++++ package.json | 4 ++++ 8 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 app/javascript/app-redux/app-reducer.ts create mode 100644 app/javascript/app-redux/app-store.ts create mode 100644 app/javascript/app-redux/index.ts create mode 100644 app/javascript/packs/redux-common.js create mode 100644 app/views/static/middleware/new-provider.html create mode 100644 config/webpack/loaders/html.js diff --git a/app/javascript/app-redux/app-reducer.ts b/app/javascript/app-redux/app-reducer.ts new file mode 100644 index 00000000000..ebbfd058096 --- /dev/null +++ b/app/javascript/app-redux/app-reducer.ts @@ -0,0 +1,22 @@ +import {createStore, Store, Reducer} from 'redux'; +import {AppState} from './app-store'; + +export type AppReducer = Reducer; + +const reducers: Set = new Set(); + +export const rootReducer: AppReducer = (state = {}, action) => { + let newState = state; + + reducers.forEach((reducer) => { + newState = reducer(newState, action) + }); + return newState; +}; + +export function addReducer(reducer: AppReducer) { + reducers.add(reducer); + return () => { + reducers.delete(reducer); + } +} diff --git a/app/javascript/app-redux/app-store.ts b/app/javascript/app-redux/app-store.ts new file mode 100644 index 00000000000..1db1be91869 --- /dev/null +++ b/app/javascript/app-redux/app-store.ts @@ -0,0 +1,19 @@ +import { createStore, applyMiddleware, compose, Store} from 'redux'; +import {rootReducer} from './app-reducer'; + +export type AppState = Object; + +const composeEnhancers = window['.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__'] || compose; + +export function configureStore(initialState?: AppState): Store { + const middlewares = []; + const enhancer = composeEnhancers( + applyMiddleware(...middlewares), + ); + + return createStore( + rootReducer, + initialState!, + enhancer, + ); +} \ No newline at end of file diff --git a/app/javascript/app-redux/index.ts b/app/javascript/app-redux/index.ts new file mode 100644 index 00000000000..d853a9305f2 --- /dev/null +++ b/app/javascript/app-redux/index.ts @@ -0,0 +1,11 @@ +import {createStore, Store, Reducer} from 'redux'; +import {addReducer} from './app-reducer'; +import {configureStore, AppState} from './app-store'; + +ManageIQ.redux = {}; + +const store: Store = configureStore({}); + +ManageIQ.redux.addReducer = addReducer; + +ManageIQ.redux.store = store; \ No newline at end of file diff --git a/app/javascript/middleware/forms/new.ts b/app/javascript/middleware/forms/new.ts index b7a4f6dd566..ccd8cc6aad6 100644 --- a/app/javascript/middleware/forms/new.ts +++ b/app/javascript/middleware/forms/new.ts @@ -1,15 +1,15 @@ import * as ng from 'angular'; export default class NewProviderForm implements ng.IComponentOptions { - public template: string = `
blablabla
`; + public templateUrl: string = '/static/middleware/new-provider.html'; public controller: any = NewProviderController; public controllerAs: string = 'newProv'; public replace = true; - public bindings: any = {}; + public bindings: any = { + formFieldsUrl: '<', + novalidate: '<' + }; } class NewProviderController { - constructor() { - console.log('blaaaa'); - } } \ No newline at end of file diff --git a/app/javascript/packs/redux-common.js b/app/javascript/packs/redux-common.js new file mode 100644 index 00000000000..e8f17771476 --- /dev/null +++ b/app/javascript/packs/redux-common.js @@ -0,0 +1 @@ +require('../app-redux') \ No newline at end of file diff --git a/app/views/static/middleware/new-provider.html b/app/views/static/middleware/new-provider.html new file mode 100644 index 00000000000..b7aa4ad0691 --- /dev/null +++ b/app/views/static/middleware/new-provider.html @@ -0,0 +1,2 @@ +
+ something something
\ No newline at end of file diff --git a/config/webpack/loaders/html.js b/config/webpack/loaders/html.js new file mode 100644 index 00000000000..3113938ef9d --- /dev/null +++ b/config/webpack/loaders/html.js @@ -0,0 +1,13 @@ +module.exports = { + test:/\.html$/, + use: [ { + loader: 'html-loader', + options: { + minimize: true, + removeAttributeQuotes: false, + caseSensitive: true, + customAttrSurround: [ [/#/, /(?:)/], [/\*/, /(?:)/], [/\[?\(?/, /(?:)/] ], + customAttrAssign: [ /\)?\]?=/ ] + } + }] +} \ No newline at end of file diff --git a/package.json b/package.json index 32a43cd5b0f..a38326af1de 100644 --- a/package.json +++ b/package.json @@ -23,12 +23,14 @@ "@angular/platform-browser": "~4.0.3", "@angular/platform-browser-dynamic": "~4.0.3", "core-js": "~2.4.1", + "redux": "^3.7.2", "rxjs": "~5.3.0", "ui-select": "0.19.8", "zone.js": "~0.8.5" }, "devDependencies": { "@types/angular": "^1.6.29", + "@types/redux": "^3.6.0", "autoprefixer": "~6.7.7", "babel-core": "~6.24.1", "babel-eslint": "~6.0.4", @@ -53,6 +55,7 @@ "extract-text-webpack-plugin": "~2.1.0", "file-loader": "~0.11.1", "glob": "~7.1.1", + "html-loader": "^0.5.1", "js-yaml": "~3.8.3", "node-sass": "~4.5.2", "path-complete-extname": "~0.1.0", @@ -60,6 +63,7 @@ "postcss-smart-import": "~0.6.11", "precss": "~1.4.0", "rails-erb-loader": "~5.0.0", + "raw-loader": "^0.5.1", "resolve-url-loader": "^2.0.2", "sass-loader": "~6.0.3", "style-loader": "~0.16.1", From 7a9d4dd82c158b255abba7c7591ce76d38cf5f01 Mon Sep 17 00:00:00 2001 From: Karel Hala Date: Thu, 17 Aug 2017 14:28:41 +0200 Subject: [PATCH 09/17] Add type definitions to MiQ redux --- app/javascript/app-redux/app-reducer.ts | 22 ---------------- app/javascript/app-redux/index.ts | 11 -------- app/javascript/middleware/forms/index.ts | 8 ++++++ app/javascript/middleware/forms/new.ts | 3 +++ .../middleware/new-provider-reducer.ts | 21 ++++++++++++++++ app/javascript/miq-redux/index.ts | 15 +++++++++++ app/javascript/miq-redux/reducer.ts | 25 +++++++++++++++++++ .../app-store.ts => miq-redux/store.ts} | 4 +-- app/javascript/packs/miq-redux-common.js | 1 + app/javascript/packs/miq-redux.ts | 16 ++++++++++++ app/javascript/packs/redux-common.js | 1 - 11 files changed, 91 insertions(+), 36 deletions(-) delete mode 100644 app/javascript/app-redux/app-reducer.ts delete mode 100644 app/javascript/app-redux/index.ts create mode 100644 app/javascript/middleware/new-provider-reducer.ts create mode 100644 app/javascript/miq-redux/index.ts create mode 100644 app/javascript/miq-redux/reducer.ts rename app/javascript/{app-redux/app-store.ts => miq-redux/store.ts} (83%) create mode 100644 app/javascript/packs/miq-redux-common.js create mode 100644 app/javascript/packs/miq-redux.ts delete mode 100644 app/javascript/packs/redux-common.js diff --git a/app/javascript/app-redux/app-reducer.ts b/app/javascript/app-redux/app-reducer.ts deleted file mode 100644 index ebbfd058096..00000000000 --- a/app/javascript/app-redux/app-reducer.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {createStore, Store, Reducer} from 'redux'; -import {AppState} from './app-store'; - -export type AppReducer = Reducer; - -const reducers: Set = new Set(); - -export const rootReducer: AppReducer = (state = {}, action) => { - let newState = state; - - reducers.forEach((reducer) => { - newState = reducer(newState, action) - }); - return newState; -}; - -export function addReducer(reducer: AppReducer) { - reducers.add(reducer); - return () => { - reducers.delete(reducer); - } -} diff --git a/app/javascript/app-redux/index.ts b/app/javascript/app-redux/index.ts deleted file mode 100644 index d853a9305f2..00000000000 --- a/app/javascript/app-redux/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {createStore, Store, Reducer} from 'redux'; -import {addReducer} from './app-reducer'; -import {configureStore, AppState} from './app-store'; - -ManageIQ.redux = {}; - -const store: Store = configureStore({}); - -ManageIQ.redux.addReducer = addReducer; - -ManageIQ.redux.store = store; \ No newline at end of file diff --git a/app/javascript/middleware/forms/index.ts b/app/javascript/middleware/forms/index.ts index b5754bfb568..fb889ee00ca 100644 --- a/app/javascript/middleware/forms/index.ts +++ b/app/javascript/middleware/forms/index.ts @@ -1,3 +1,11 @@ +import {applyReducerHash} from '../../miq-redux/reducer'; import NewProviderForm from './new'; +import {providerReducers} from '../new-provider-reducer'; +import { AppState, Action } from '../../packs/miq-redux'; +function newProviderReducer(state: AppState, action: Action): AppState { + return ManageIQ.redux.applyReducerHash(providerReducers, state, action); +}; + +ManageIQ.redux.addReducer(newProviderReducer); ManageIQ.angular.app.component('newProviderForm', new NewProviderForm); \ No newline at end of file diff --git a/app/javascript/middleware/forms/new.ts b/app/javascript/middleware/forms/new.ts index ccd8cc6aad6..0c5d66414f6 100644 --- a/app/javascript/middleware/forms/new.ts +++ b/app/javascript/middleware/forms/new.ts @@ -12,4 +12,7 @@ export default class NewProviderForm implements ng.IComponentOptions { } class NewProviderController { + public $onInit() { + console.log(this); + } } \ No newline at end of file diff --git a/app/javascript/middleware/new-provider-reducer.ts b/app/javascript/middleware/new-provider-reducer.ts new file mode 100644 index 00000000000..ff254c40de7 --- /dev/null +++ b/app/javascript/middleware/new-provider-reducer.ts @@ -0,0 +1,21 @@ +import { AppState } from '../packs/miq-redux'; + +const asdf = 'INIT_NEW_PROVIDER_HAWKULAR' +function initNewProvider(state, action): AppState { + return {...state, data: action.payload.data}; +} + + +export const providerReducers = { + [asdf]: initNewProvider +} + + +function asd(state, action) { + switch (action.type) { + case asdf: + return initNewProvider(state, action) + default: + return state + } +} \ No newline at end of file diff --git a/app/javascript/miq-redux/index.ts b/app/javascript/miq-redux/index.ts new file mode 100644 index 00000000000..e38e45e7bc8 --- /dev/null +++ b/app/javascript/miq-redux/index.ts @@ -0,0 +1,15 @@ +import {createStore} from 'redux'; +import {addReducer, applyReducerHash} from './reducer'; +import {configureStore} from './store'; + +import { AppState, MiqStore } from '../packs/miq-redux'; + +ManageIQ.redux = {}; + +const store: MiqStore = configureStore({}); + +ManageIQ.redux.addReducer = addReducer; + +ManageIQ.redux.store = store; + +ManageIQ.redux.applyReducerHash = applyReducerHash; diff --git a/app/javascript/miq-redux/reducer.ts b/app/javascript/miq-redux/reducer.ts new file mode 100644 index 00000000000..9cdb31574a0 --- /dev/null +++ b/app/javascript/miq-redux/reducer.ts @@ -0,0 +1,25 @@ +import {createStore, Store, Reducer, Action} from 'redux'; + +import { IMiqAction, AppReducer, IMiqReducerHash, AppState} from '../packs/miq-redux'; + +const reducers: Set = new Set(); + +export const rootReducer: AppReducer = (state = {}, action: IMiqAction) => { + let newState = state; + + reducers.forEach((reducer) => { + newState = reducer(newState, action) + }); + return newState; +}; + +export function addReducer(reducer: AppReducer) { + reducers.add(reducer); + return () => { + reducers.delete(reducer); + } +} + +export function applyReducerHash(reducers: IMiqReducerHash, state: AppState, action: IMiqAction): AppState { + return (reducers.hasOwnProperty(action.type) && reducers[action.type](state, action)) || state; +} diff --git a/app/javascript/app-redux/app-store.ts b/app/javascript/miq-redux/store.ts similarity index 83% rename from app/javascript/app-redux/app-store.ts rename to app/javascript/miq-redux/store.ts index 1db1be91869..36930480aa2 100644 --- a/app/javascript/app-redux/app-store.ts +++ b/app/javascript/miq-redux/store.ts @@ -1,7 +1,7 @@ import { createStore, applyMiddleware, compose, Store} from 'redux'; -import {rootReducer} from './app-reducer'; +import {rootReducer} from './reducer'; -export type AppState = Object; +import {AppState} from '../packs/miq-redux'; const composeEnhancers = window['.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__'] || compose; diff --git a/app/javascript/packs/miq-redux-common.js b/app/javascript/packs/miq-redux-common.js new file mode 100644 index 00000000000..3dd0437abab --- /dev/null +++ b/app/javascript/packs/miq-redux-common.js @@ -0,0 +1 @@ +require('../miq-redux') \ No newline at end of file diff --git a/app/javascript/packs/miq-redux.ts b/app/javascript/packs/miq-redux.ts new file mode 100644 index 00000000000..e0f859499fd --- /dev/null +++ b/app/javascript/packs/miq-redux.ts @@ -0,0 +1,16 @@ +import {Reducer, Action, Store} from 'redux'; + +export type AppReducer = Reducer; +export interface IMiqAction extends Action { + type: string; + payload?: any; +} +export interface IMiqReducerHash { + [propName: string]: AppReducer; +} + +export type AppState = Object; + +export type MiqStore = Store; + +export type Action = Action; \ No newline at end of file diff --git a/app/javascript/packs/redux-common.js b/app/javascript/packs/redux-common.js deleted file mode 100644 index e8f17771476..00000000000 --- a/app/javascript/packs/redux-common.js +++ /dev/null @@ -1 +0,0 @@ -require('../app-redux') \ No newline at end of file From cfd862693f88e810d964947e46ea69fbcfd45ecf Mon Sep 17 00:00:00 2001 From: Karel Hala Date: Thu, 17 Aug 2017 14:56:34 +0200 Subject: [PATCH 10/17] Create lib file with exposed public functions for redux --- app/javascript/middleware/forms/index.ts | 7 +++---- app/javascript/middleware/new-provider-reducer.ts | 11 ----------- app/javascript/miq-redux/index.ts | 1 - app/javascript/miq-redux/lib.ts | 11 +++++++++++ app/views/ems_middleware/new.html.haml | 1 + 5 files changed, 15 insertions(+), 16 deletions(-) create mode 100644 app/javascript/miq-redux/lib.ts diff --git a/app/javascript/middleware/forms/index.ts b/app/javascript/middleware/forms/index.ts index fb889ee00ca..6b037ab7449 100644 --- a/app/javascript/middleware/forms/index.ts +++ b/app/javascript/middleware/forms/index.ts @@ -1,11 +1,10 @@ -import {applyReducerHash} from '../../miq-redux/reducer'; import NewProviderForm from './new'; import {providerReducers} from '../new-provider-reducer'; import { AppState, Action } from '../../packs/miq-redux'; - +import {addReducer, applyReducerHash} from '../../miq-redux/lib'; function newProviderReducer(state: AppState, action: Action): AppState { - return ManageIQ.redux.applyReducerHash(providerReducers, state, action); + return applyReducerHash(providerReducers, state, action); }; -ManageIQ.redux.addReducer(newProviderReducer); +addReducer(newProviderReducer); ManageIQ.angular.app.component('newProviderForm', new NewProviderForm); \ No newline at end of file diff --git a/app/javascript/middleware/new-provider-reducer.ts b/app/javascript/middleware/new-provider-reducer.ts index ff254c40de7..6956f4864eb 100644 --- a/app/javascript/middleware/new-provider-reducer.ts +++ b/app/javascript/middleware/new-provider-reducer.ts @@ -5,17 +5,6 @@ function initNewProvider(state, action): AppState { return {...state, data: action.payload.data}; } - export const providerReducers = { [asdf]: initNewProvider -} - - -function asd(state, action) { - switch (action.type) { - case asdf: - return initNewProvider(state, action) - default: - return state - } } \ No newline at end of file diff --git a/app/javascript/miq-redux/index.ts b/app/javascript/miq-redux/index.ts index e38e45e7bc8..8f398f487ad 100644 --- a/app/javascript/miq-redux/index.ts +++ b/app/javascript/miq-redux/index.ts @@ -1,7 +1,6 @@ import {createStore} from 'redux'; import {addReducer, applyReducerHash} from './reducer'; import {configureStore} from './store'; - import { AppState, MiqStore } from '../packs/miq-redux'; ManageIQ.redux = {}; diff --git a/app/javascript/miq-redux/lib.ts b/app/javascript/miq-redux/lib.ts new file mode 100644 index 00000000000..2a8a59aa38f --- /dev/null +++ b/app/javascript/miq-redux/lib.ts @@ -0,0 +1,11 @@ +export function addReducer(reducer) { + return ManageIQ.redux.addReducer(reducer); +} + +export function getStore() { + return ManageIQ.redux.store; +} + +export function applyReducerHash(reducers, state, action) { + return ManageIQ.redux.applyReducerHash(reducers, state, action); +} \ No newline at end of file diff --git a/app/views/ems_middleware/new.html.haml b/app/views/ems_middleware/new.html.haml index 390a5b44952..83d73e343d3 100644 --- a/app/views/ems_middleware/new.html.haml +++ b/app/views/ems_middleware/new.html.haml @@ -11,6 +11,7 @@ %input{:type => 'hidden', :id => "form_id", :value => "##{f.options[:html][:id]}"} %input{:type => 'hidden', :id => "button_name", :name => "button", :value => "add"} %input{:type => 'hidden', :id => "cred_type", :name => "cred_type", :value => "default"} + = render :partial => "form" %new-provider-form From 84dcbb4c5696fbe9bc20769be1af04684c8fab7a Mon Sep 17 00:00:00 2001 From: Karel Hala Date: Thu, 17 Aug 2017 17:14:30 +0200 Subject: [PATCH 11/17] Add reducer for updating new hawkular provider --- app/javascript/middleware/forms/index.ts | 8 +--- .../middleware/forms/new-provider-reducer.ts | 37 ++++++++++++++ app/javascript/middleware/forms/new.ts | 48 ++++++++++++++++--- .../middleware/new-provider-reducer.ts | 10 ---- app/javascript/miq-redux/lib.ts | 2 +- app/javascript/miq-redux/store.ts | 2 +- app/views/ems_middleware/new.html.haml | 6 ++- app/views/static/middleware/new-provider.html | 2 - .../static/middleware/new-provider.html.haml | 12 +++++ package.json | 2 + 10 files changed, 101 insertions(+), 28 deletions(-) create mode 100644 app/javascript/middleware/forms/new-provider-reducer.ts delete mode 100644 app/javascript/middleware/new-provider-reducer.ts delete mode 100644 app/views/static/middleware/new-provider.html create mode 100644 app/views/static/middleware/new-provider.html.haml diff --git a/app/javascript/middleware/forms/index.ts b/app/javascript/middleware/forms/index.ts index 6b037ab7449..eb90bd754a4 100644 --- a/app/javascript/middleware/forms/index.ts +++ b/app/javascript/middleware/forms/index.ts @@ -1,10 +1,6 @@ import NewProviderForm from './new'; -import {providerReducers} from '../new-provider-reducer'; +import {reducers} from './new-provider-reducer'; import { AppState, Action } from '../../packs/miq-redux'; import {addReducer, applyReducerHash} from '../../miq-redux/lib'; -function newProviderReducer(state: AppState, action: Action): AppState { - return applyReducerHash(providerReducers, state, action); -}; -addReducer(newProviderReducer); -ManageIQ.angular.app.component('newProviderForm', new NewProviderForm); \ No newline at end of file +ManageIQ.angular.app.component('newProviderForm', new NewProviderForm()); diff --git a/app/javascript/middleware/forms/new-provider-reducer.ts b/app/javascript/middleware/forms/new-provider-reducer.ts new file mode 100644 index 00000000000..db932e38b5e --- /dev/null +++ b/app/javascript/middleware/forms/new-provider-reducer.ts @@ -0,0 +1,37 @@ +import { AppState } from '../../packs/miq-redux'; +import {merge, defaultsDeep} from 'lodash'; + +export const INIT_NEW_PROVIDER = 'INIT_NEW_PROVIDER_HAWKULAR' +export const UPDATE_NEW_PROVIDER = 'UPDATE_NEW_PROVIDER_HAWKULAR'; +function initNewProvider(state, action): AppState { + const newProvider = { + providers: { + middleware: { + hawkular: { + newProvider: { + name: '' + } + } + } + } + }; + return {...defaultsDeep(state, newProvider)} +} + +function updateNewProvider(state, action): AppState { + const newProvider = { + providers: { + middleware: { + hawkular: { + newProvider: action.payload + } + } + } + }; + return {...merge(state, newProvider)} +} + +export const reducers = { + [INIT_NEW_PROVIDER]: initNewProvider, + [UPDATE_NEW_PROVIDER]: updateNewProvider +} diff --git a/app/javascript/middleware/forms/new.ts b/app/javascript/middleware/forms/new.ts index 0c5d66414f6..cbac9f77a14 100644 --- a/app/javascript/middleware/forms/new.ts +++ b/app/javascript/middleware/forms/new.ts @@ -1,18 +1,54 @@ import * as ng from 'angular'; +import {getStore, addReducer, applyReducerHash} from '../../miq-redux/lib'; +import {AppState, Action} from '../../packs/miq-redux'; +import {MiqStore} from '../../packs/miq-redux'; +import {INIT_NEW_PROVIDER, reducers, UPDATE_NEW_PROVIDER} from './new-provider-reducer'; export default class NewProviderForm implements ng.IComponentOptions { - public templateUrl: string = '/static/middleware/new-provider.html'; + public templateUrl: string = '/static/middleware/new-provider.html.haml'; public controller: any = NewProviderController; public controllerAs: string = 'newProv'; - public replace = true; public bindings: any = { - formFieldsUrl: '<', - novalidate: '<' + formFieldsUrl: '@', + novalidate: '@', + createUrl: '@' }; } class NewProviderController { + public componentState: Object; + public newProviderName: string; + private formFieldsUrl: string; + private novalidate: boolean; + private createUrl: string; + private reduxStore: MiqStore; + private unbind: any = {}; + + constructor() { + this.unbind.reducer = addReducer((state: AppState, action: Action) => { + return applyReducerHash(reducers, state, action) + }); + this.reduxStore = getStore(); + this.unbind.redux = this.reduxStore.subscribe(() => { + const currState: any = this.reduxStore.getState(); + this.newProviderName = currState.providers.middleware.hawkular.newProvider.name; + }); + } + public $onInit() { - console.log(this); + this.reduxStore.dispatch({type: INIT_NEW_PROVIDER}); + } + + public onNameChanged(providerName) { + this.reduxStore.dispatch({type: UPDATE_NEW_PROVIDER, payload: + { + name: providerName + } + }); + } + + public $onDestroy() { + this.unbind.redux(); + this.unbind.reducer(); } -} \ No newline at end of file +} diff --git a/app/javascript/middleware/new-provider-reducer.ts b/app/javascript/middleware/new-provider-reducer.ts deleted file mode 100644 index 6956f4864eb..00000000000 --- a/app/javascript/middleware/new-provider-reducer.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { AppState } from '../packs/miq-redux'; - -const asdf = 'INIT_NEW_PROVIDER_HAWKULAR' -function initNewProvider(state, action): AppState { - return {...state, data: action.payload.data}; -} - -export const providerReducers = { - [asdf]: initNewProvider -} \ No newline at end of file diff --git a/app/javascript/miq-redux/lib.ts b/app/javascript/miq-redux/lib.ts index 2a8a59aa38f..8dd8d4c2798 100644 --- a/app/javascript/miq-redux/lib.ts +++ b/app/javascript/miq-redux/lib.ts @@ -8,4 +8,4 @@ export function getStore() { export function applyReducerHash(reducers, state, action) { return ManageIQ.redux.applyReducerHash(reducers, state, action); -} \ No newline at end of file +} diff --git a/app/javascript/miq-redux/store.ts b/app/javascript/miq-redux/store.ts index 36930480aa2..7c38676e426 100644 --- a/app/javascript/miq-redux/store.ts +++ b/app/javascript/miq-redux/store.ts @@ -16,4 +16,4 @@ export function configureStore(initialState?: AppState): Store { initialState!, enhancer, ); -} \ No newline at end of file +} diff --git a/app/views/ems_middleware/new.html.haml b/app/views/ems_middleware/new.html.haml index 83d73e343d3..032ac4306ec 100644 --- a/app/views/ems_middleware/new.html.haml +++ b/app/views/ems_middleware/new.html.haml @@ -11,10 +11,12 @@ %input{:type => 'hidden', :id => "form_id", :value => "##{f.options[:html][:id]}"} %input{:type => 'hidden', :id => "button_name", :name => "button", :value => "add"} %input{:type => 'hidden', :id => "cred_type", :name => "cred_type", :value => "default"} - + = render :partial => "form" - %new-provider-form + %new-provider-form{"create-url" => "asd", + "form-fields-url" => "rrr", + :novalidate => "true"} = javascript_pack_tag 'manageiq-ui-classic/middleware_forms' :javascript ManageIQ.angular.app.value('emsCommonFormId', '#{@ems.id || "new"}'); diff --git a/app/views/static/middleware/new-provider.html b/app/views/static/middleware/new-provider.html deleted file mode 100644 index b7aa4ad0691..00000000000 --- a/app/views/static/middleware/new-provider.html +++ /dev/null @@ -1,2 +0,0 @@ -
- something something
\ No newline at end of file diff --git a/app/views/static/middleware/new-provider.html.haml b/app/views/static/middleware/new-provider.html.haml new file mode 100644 index 00000000000..0884394037a --- /dev/null +++ b/app/views/static/middleware/new-provider.html.haml @@ -0,0 +1,12 @@ +%div + .form-group + %label.col-md-2.control-label{"for" => "ems_name"} + = _('Name') + .col-md-8 + %input.form-control{"type" => "text", + "id" => "ems_name", + "name" => "name", + "ng-model" => "providerName", + "ng-change" => "newProv.onNameChanged(providerName)", + "ng-value" => "newProv.newProviderName", + "auto-focus" => ""} \ No newline at end of file diff --git a/package.json b/package.json index a38326af1de..2fa2141e43e 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,14 @@ "@angular/platform-browser-dynamic": "~4.0.3", "core-js": "~2.4.1", "redux": "^3.7.2", + "reselect": "^3.0.1", "rxjs": "~5.3.0", "ui-select": "0.19.8", "zone.js": "~0.8.5" }, "devDependencies": { "@types/angular": "^1.6.29", + "@types/lodash": "^4.14.73", "@types/redux": "^3.6.0", "autoprefixer": "~6.7.7", "babel-core": "~6.24.1", From 65a5765c598775f1c0eb5ce0e8b4d4811f95667a Mon Sep 17 00:00:00 2001 From: Karel Hala Date: Mon, 21 Aug 2017 14:33:19 +0200 Subject: [PATCH 12/17] Use correct number of spaces in redux common file --- app/javascript/middleware/forms/index.ts | 3 - .../middleware/forms/new-provider-reducer.ts | 52 +++++------ app/javascript/middleware/forms/new.ts | 86 +++++++++---------- app/javascript/miq-redux/index.ts | 7 +- app/javascript/miq-redux/lib.ts | 6 +- app/javascript/miq-redux/reducer.ts | 18 ++-- .../miq-redux.ts => miq-redux/redux-types.ts} | 10 +-- app/javascript/miq-redux/store.ts | 8 +- 8 files changed, 93 insertions(+), 97 deletions(-) rename app/javascript/{packs/miq-redux.ts => miq-redux/redux-types.ts} (58%) diff --git a/app/javascript/middleware/forms/index.ts b/app/javascript/middleware/forms/index.ts index eb90bd754a4..1b4d4aa71da 100644 --- a/app/javascript/middleware/forms/index.ts +++ b/app/javascript/middleware/forms/index.ts @@ -1,6 +1,3 @@ import NewProviderForm from './new'; -import {reducers} from './new-provider-reducer'; -import { AppState, Action } from '../../packs/miq-redux'; -import {addReducer, applyReducerHash} from '../../miq-redux/lib'; ManageIQ.angular.app.component('newProviderForm', new NewProviderForm()); diff --git a/app/javascript/middleware/forms/new-provider-reducer.ts b/app/javascript/middleware/forms/new-provider-reducer.ts index db932e38b5e..7593b7035b9 100644 --- a/app/javascript/middleware/forms/new-provider-reducer.ts +++ b/app/javascript/middleware/forms/new-provider-reducer.ts @@ -1,37 +1,37 @@ -import { AppState } from '../../packs/miq-redux'; -import {merge, defaultsDeep} from 'lodash'; +import { AppState } from '../../miq-redux/redux-types'; +import { merge, defaultsDeep } from 'lodash'; -export const INIT_NEW_PROVIDER = 'INIT_NEW_PROVIDER_HAWKULAR' +export const INIT_NEW_PROVIDER: string = 'INIT_NEW_PROVIDER_HAWKULAR' export const UPDATE_NEW_PROVIDER = 'UPDATE_NEW_PROVIDER_HAWKULAR'; function initNewProvider(state, action): AppState { - const newProvider = { - providers: { - middleware: { - hawkular: { - newProvider: { - name: '' - } - } - } + const newProvider = { + providers: { + middleware: { + hawkular: { + newProvider: { + name: '' + } } - }; - return {...defaultsDeep(state, newProvider)} + } + } + }; + return { ...defaultsDeep(state, newProvider) } } function updateNewProvider(state, action): AppState { - const newProvider = { - providers: { - middleware: { - hawkular: { - newProvider: action.payload - } - } + const newProvider = { + providers: { + middleware: { + hawkular: { + newProvider: action.payload } - }; - return {...merge(state, newProvider)} + } + } + }; + return { ...merge(state, newProvider) } } export const reducers = { - [INIT_NEW_PROVIDER]: initNewProvider, - [UPDATE_NEW_PROVIDER]: updateNewProvider -} + [INIT_NEW_PROVIDER]: initNewProvider, + [UPDATE_NEW_PROVIDER]: updateNewProvider +}; diff --git a/app/javascript/middleware/forms/new.ts b/app/javascript/middleware/forms/new.ts index cbac9f77a14..5d6b177be43 100644 --- a/app/javascript/middleware/forms/new.ts +++ b/app/javascript/middleware/forms/new.ts @@ -1,54 +1,54 @@ import * as ng from 'angular'; -import {getStore, addReducer, applyReducerHash} from '../../miq-redux/lib'; -import {AppState, Action} from '../../packs/miq-redux'; -import {MiqStore} from '../../packs/miq-redux'; -import {INIT_NEW_PROVIDER, reducers, UPDATE_NEW_PROVIDER} from './new-provider-reducer'; +import { getStore, addReducer, applyReducerHash } from '../../miq-redux/lib'; +import { AppState, Action } from '../../miq-redux/redux-types'; +import { MiqStore } from '../../miq-redux/redux-types'; +import { INIT_NEW_PROVIDER, reducers, UPDATE_NEW_PROVIDER } from './new-provider-reducer'; export default class NewProviderForm implements ng.IComponentOptions { - public templateUrl: string = '/static/middleware/new-provider.html.haml'; - public controller: any = NewProviderController; - public controllerAs: string = 'newProv'; - public bindings: any = { - formFieldsUrl: '@', - novalidate: '@', - createUrl: '@' - }; + public templateUrl: string = '/static/middleware/new-provider.html.haml'; + public controller: any = NewProviderController; + public controllerAs: string = 'newProv'; + public bindings: any = { + formFieldsUrl: '@', + novalidate: '@', + createUrl: '@' + }; } class NewProviderController { - public componentState: Object; - public newProviderName: string; - private formFieldsUrl: string; - private novalidate: boolean; - private createUrl: string; - private reduxStore: MiqStore; - private unbind: any = {}; + public componentState: Object; + public newProviderName: string; + private formFieldsUrl: string; + private novalidate: boolean; + private createUrl: string; + private reduxStore: MiqStore; + private unbind: any = {}; - constructor() { - this.unbind.reducer = addReducer((state: AppState, action: Action) => { - return applyReducerHash(reducers, state, action) - }); - this.reduxStore = getStore(); - this.unbind.redux = this.reduxStore.subscribe(() => { - const currState: any = this.reduxStore.getState(); - this.newProviderName = currState.providers.middleware.hawkular.newProvider.name; - }); - } + constructor() { + this.unbind.reducer = addReducer((state: AppState, action: Action) => { + return applyReducerHash(reducers, state, action) + }); + this.reduxStore = getStore(); + this.unbind.redux = this.reduxStore.subscribe(() => { + const currState: any = this.reduxStore.getState(); + this.newProviderName = currState.providers.middleware.hawkular.newProvider.name; + }); + } - public $onInit() { - this.reduxStore.dispatch({type: INIT_NEW_PROVIDER}); - } + public $onInit() { + this.reduxStore.dispatch({ type: INIT_NEW_PROVIDER }); + } - public onNameChanged(providerName) { - this.reduxStore.dispatch({type: UPDATE_NEW_PROVIDER, payload: - { - name: providerName - } - }); - } + public onNameChanged(providerName) { + this.reduxStore.dispatch({ + type: UPDATE_NEW_PROVIDER, payload: { + name: providerName + } + }); + } - public $onDestroy() { - this.unbind.redux(); - this.unbind.reducer(); - } + public $onDestroy() { + this.unbind.redux(); + this.unbind.reducer(); + } } diff --git a/app/javascript/miq-redux/index.ts b/app/javascript/miq-redux/index.ts index 8f398f487ad..5725f2734e1 100644 --- a/app/javascript/miq-redux/index.ts +++ b/app/javascript/miq-redux/index.ts @@ -1,7 +1,6 @@ -import {createStore} from 'redux'; -import {addReducer, applyReducerHash} from './reducer'; -import {configureStore} from './store'; -import { AppState, MiqStore } from '../packs/miq-redux'; +import { addReducer, applyReducerHash } from './reducer'; +import { configureStore } from './store'; +import { MiqStore } from './redux-types'; ManageIQ.redux = {}; diff --git a/app/javascript/miq-redux/lib.ts b/app/javascript/miq-redux/lib.ts index 8dd8d4c2798..47c15c1b1ba 100644 --- a/app/javascript/miq-redux/lib.ts +++ b/app/javascript/miq-redux/lib.ts @@ -1,11 +1,11 @@ export function addReducer(reducer) { - return ManageIQ.redux.addReducer(reducer); + return ManageIQ.redux.addReducer(reducer); } export function getStore() { - return ManageIQ.redux.store; + return ManageIQ.redux.store; } export function applyReducerHash(reducers, state, action) { - return ManageIQ.redux.applyReducerHash(reducers, state, action); + return ManageIQ.redux.applyReducerHash(reducers, state, action); } diff --git a/app/javascript/miq-redux/reducer.ts b/app/javascript/miq-redux/reducer.ts index 9cdb31574a0..44c47a7b21f 100644 --- a/app/javascript/miq-redux/reducer.ts +++ b/app/javascript/miq-redux/reducer.ts @@ -1,25 +1,25 @@ -import {createStore, Store, Reducer, Action} from 'redux'; +import { createStore, Store, Reducer, Action } from 'redux'; -import { IMiqAction, AppReducer, IMiqReducerHash, AppState} from '../packs/miq-redux'; +import { IMiqAction, AppReducer, IMiqReducerHash, AppState } from './redux-types'; const reducers: Set = new Set(); export const rootReducer: AppReducer = (state = {}, action: IMiqAction) => { - let newState = state; + let newState = state; - reducers.forEach((reducer) => { - newState = reducer(newState, action) - }); - return newState; + reducers.forEach((reducer) => { + newState = reducer(newState, action) + }); + return newState; }; export function addReducer(reducer: AppReducer) { reducers.add(reducer); return () => { - reducers.delete(reducer); + reducers.delete(reducer); } } export function applyReducerHash(reducers: IMiqReducerHash, state: AppState, action: IMiqAction): AppState { - return (reducers.hasOwnProperty(action.type) && reducers[action.type](state, action)) || state; + return (reducers.hasOwnProperty(action.type) && reducers[action.type](state, action)) || state; } diff --git a/app/javascript/packs/miq-redux.ts b/app/javascript/miq-redux/redux-types.ts similarity index 58% rename from app/javascript/packs/miq-redux.ts rename to app/javascript/miq-redux/redux-types.ts index e0f859499fd..12548d72817 100644 --- a/app/javascript/packs/miq-redux.ts +++ b/app/javascript/miq-redux/redux-types.ts @@ -1,16 +1,16 @@ -import {Reducer, Action, Store} from 'redux'; +import { Reducer, Action, Store } from 'redux'; export type AppReducer = Reducer; export interface IMiqAction extends Action { - type: string; - payload?: any; + type: string; + payload?: any; } export interface IMiqReducerHash { - [propName: string]: AppReducer; + [propName: string]: AppReducer; } export type AppState = Object; export type MiqStore = Store; -export type Action = Action; \ No newline at end of file +export type Action = Action; diff --git a/app/javascript/miq-redux/store.ts b/app/javascript/miq-redux/store.ts index 7c38676e426..6ea693a6545 100644 --- a/app/javascript/miq-redux/store.ts +++ b/app/javascript/miq-redux/store.ts @@ -1,9 +1,9 @@ -import { createStore, applyMiddleware, compose, Store} from 'redux'; -import {rootReducer} from './reducer'; +import { createStore, applyMiddleware, compose, Store } from 'redux'; +import { rootReducer } from './reducer'; -import {AppState} from '../packs/miq-redux'; +import { AppState } from './redux-types'; -const composeEnhancers = window['.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__'] || compose; +const composeEnhancers = window['__REDUX_DEVTOOLS_EXTENSION_COMPOSE__'] || compose; export function configureStore(initialState?: AppState): Store { const middlewares = []; From befd287070fdefc2734a6517ce743d1dba8e91a2 Mon Sep 17 00:00:00 2001 From: Karel Hala Date: Wed, 23 Aug 2017 10:20:15 +0200 Subject: [PATCH 13/17] Change data to be saved in redux for new form when any part of newProvider object has changed --- .../middleware/forms/new-provider-reducer.ts | 3 +- app/javascript/middleware/forms/new.ts | 36 +++++++++++++------ app/javascript/packs/custom-typings.ts | 3 +- app/views/ems_middleware/new.html.haml | 1 + .../static/middleware/new-provider.html.haml | 34 ++++++++++++------ 5 files changed, 53 insertions(+), 24 deletions(-) diff --git a/app/javascript/middleware/forms/new-provider-reducer.ts b/app/javascript/middleware/forms/new-provider-reducer.ts index 7593b7035b9..a51b47d9409 100644 --- a/app/javascript/middleware/forms/new-provider-reducer.ts +++ b/app/javascript/middleware/forms/new-provider-reducer.ts @@ -9,7 +9,8 @@ function initNewProvider(state, action): AppState { middleware: { hawkular: { newProvider: { - name: '' + name: '', + type: undefined } } } diff --git a/app/javascript/middleware/forms/new.ts b/app/javascript/middleware/forms/new.ts index 5d6b177be43..31be9c4e4bc 100644 --- a/app/javascript/middleware/forms/new.ts +++ b/app/javascript/middleware/forms/new.ts @@ -9,6 +9,7 @@ export default class NewProviderForm implements ng.IComponentOptions { public controller: any = NewProviderController; public controllerAs: string = 'newProv'; public bindings: any = { + types: '<', formFieldsUrl: '@', novalidate: '@', createUrl: '@' @@ -16,33 +17,46 @@ export default class NewProviderForm implements ng.IComponentOptions { } class NewProviderController { + public types: any[]; public componentState: Object; - public newProviderName: string; + public newProvider: any; private formFieldsUrl: string; private novalidate: boolean; private createUrl: string; private reduxStore: MiqStore; private unbind: any = {}; + public $name: string = 'newProvider'; + private selects: NodeListOf; - constructor() { - this.unbind.reducer = addReducer((state: AppState, action: Action) => { - return applyReducerHash(reducers, state, action) - }); + public static $inject = ['$element']; + + constructor(private $element: Element) { + this.unbind.reducer = addReducer(NewProviderController.applyReducers); this.reduxStore = getStore(); - this.unbind.redux = this.reduxStore.subscribe(() => { - const currState: any = this.reduxStore.getState(); - this.newProviderName = currState.providers.middleware.hawkular.newProvider.name; - }); + this.unbind.redux = this.reduxStore.subscribe(() => this.updateStore()); + } + + private static applyReducers(state: AppState, action: Action) { + return applyReducerHash(reducers, state, action); + } + + public updateStore() { + const currState: any = this.reduxStore.getState(); + this.newProvider = {...currState.providers.middleware.hawkular.newProvider}; } public $onInit() { + this.selects = this.$element.querySelectorAll('select'); this.reduxStore.dispatch({ type: INIT_NEW_PROVIDER }); + setTimeout(() => (angular.element(this.selects)).selectpicker('refresh')); } - public onNameChanged(providerName) { + public onChangedProvider() { + console.log(this.newProvider); this.reduxStore.dispatch({ type: UPDATE_NEW_PROVIDER, payload: { - name: providerName + name: this.newProvider.name, + type: this.newProvider.type } }); } diff --git a/app/javascript/packs/custom-typings.ts b/app/javascript/packs/custom-typings.ts index 105662e3bce..3d26d00c8d4 100644 --- a/app/javascript/packs/custom-typings.ts +++ b/app/javascript/packs/custom-typings.ts @@ -1 +1,2 @@ -declare var ManageIQ: any; \ No newline at end of file +declare var ManageIQ: any; +declare var angular: any; \ No newline at end of file diff --git a/app/views/ems_middleware/new.html.haml b/app/views/ems_middleware/new.html.haml index 032ac4306ec..1b689f0d1bb 100644 --- a/app/views/ems_middleware/new.html.haml +++ b/app/views/ems_middleware/new.html.haml @@ -16,6 +16,7 @@ = render :partial => "form" %new-provider-form{"create-url" => "asd", "form-fields-url" => "rrr", + "types" => "#{[[_('Choose'), nil]] + @ems_types}", :novalidate => "true"} = javascript_pack_tag 'manageiq-ui-classic/middleware_forms' :javascript diff --git a/app/views/static/middleware/new-provider.html.haml b/app/views/static/middleware/new-provider.html.haml index 0884394037a..76119dfc9ec 100644 --- a/app/views/static/middleware/new-provider.html.haml +++ b/app/views/static/middleware/new-provider.html.haml @@ -1,12 +1,24 @@ %div - .form-group - %label.col-md-2.control-label{"for" => "ems_name"} - = _('Name') - .col-md-8 - %input.form-control{"type" => "text", - "id" => "ems_name", - "name" => "name", - "ng-model" => "providerName", - "ng-change" => "newProv.onNameChanged(providerName)", - "ng-value" => "newProv.newProviderName", - "auto-focus" => ""} \ No newline at end of file + .form-group + %label.col-md-2.control-label{"for" => "ems_name"} + = _('Name') + .col-md-8 + %input.form-control{"type" => "text", + "id" => "ems_name", + "name" => "name", + "ng-model" => "newProv.newProvider.name", + "ng-change" => "newProv.onChangedProvider()", + "ng-value" => "newProv.newProvider.name", + "auto-focus" => ""} + .form-group + %label.col-md-2.control-label{"for" => "ems_type"} + = _('Type') + .col-md-8 + %select{"ng-options"=>"type[0] for type in newProv.types track by type[1]", + "ng-model" => "newProv.newProvider.type", + "selectpicker-for-select-tag" => ""} + + -# %pf-select{"ng-model" => "newProv.newProvider.type", + -# "ng-change" => "newProv.onChangedProvider()", + -# "id" => "ems_type", + -# "options"=>"newProv.types"} From 5366f80689ccfb46cbaa4001a7ea2a85a50ec2c0 Mon Sep 17 00:00:00 2001 From: Karel Hala Date: Thu, 24 Aug 2017 15:53:36 +0200 Subject: [PATCH 14/17] Introduce new common form controller which hooks itself and it's children to redux store and unbind them when neccessary Add changeForm method to default form Controller Init form from default form controller Add JS doc comments to defaultForm --- .../forms-common/defaultFormController.ts | 65 ++++++++++++++++ .../middleware/forms/new-provider-reducer.ts | 19 ++--- app/javascript/middleware/forms/new.ts | 67 +++++++---------- app/javascript/miq-redux/lib.ts | 4 + app/views/ems_middleware/new.html.haml | 1 + .../static/middleware/new-provider.html.haml | 74 ++++++++++++++++--- 6 files changed, 171 insertions(+), 59 deletions(-) create mode 100644 app/javascript/forms-common/defaultFormController.ts diff --git a/app/javascript/forms-common/defaultFormController.ts b/app/javascript/forms-common/defaultFormController.ts new file mode 100644 index 00000000000..fb19d2aa069 --- /dev/null +++ b/app/javascript/forms-common/defaultFormController.ts @@ -0,0 +1,65 @@ +import { getStore, addReducer, applyReducerHash, UPDATE_FORM, INIT_FORM } from '../miq-redux/lib'; +import { MiqStore, IMiqReducerHash, AppState, Action } from '../miq-redux/redux-types'; + +export interface IUnbindReduxReducers { + redux?: () => void; + reducer?: () => void; +} + +export interface IFormController { + updateFormObject: () => void; +} + +export abstract class DefaultFormController { + protected unbind: IUnbindReduxReducers = {}; + protected reduxStore: MiqStore; + public formObject: any; + + /** + * Constructor which will get reduxStore and subscribes to it. + * @param reducersHash optional, hash of reducers which will be added to rootReducer. + */ + constructor(reducersHash?: IMiqReducerHash) { + if (reducersHash) { + this.unbind.reducer = addReducer( + (state: AppState, action: Action) => applyReducerHash(reducersHash, state, action) + ); + } + this.reduxStore = getStore(); + this.unbind.redux = this.reduxStore.subscribe(() => this.updateFormObject()); + } + + /** + * Method for updating form object with current store. + */ + protected updateFormObject(): void { + throw new Error('Controller should implement updateStore method'); + } + + /** + * Method which is fired when component is destroyed. + * It will unbind redux from current scope and if some reducers were passed in, it will unbind them from reducer + * array as well. + */ + public $onDestroy(): void { + this.unbind.redux(); + if (this.unbind.reducer) { + this.unbind.reducer(); + } + } + + /** + * Method which takes care of firing action `UPDATE_FORM`. + * It will pass formObject as payload to it. + */ + public onChangeForm(): void { + this.reduxStore.dispatch({ type: UPDATE_FORM, payload: this.formObject }); + } + + /** + * Method which is fired on form init, it will fire action `INIT_FORM` for defining default values in form. + */ + protected $onInit(): void { + this.reduxStore.dispatch({ type: INIT_FORM }); + } +} \ No newline at end of file diff --git a/app/javascript/middleware/forms/new-provider-reducer.ts b/app/javascript/middleware/forms/new-provider-reducer.ts index a51b47d9409..0923bf44c71 100644 --- a/app/javascript/middleware/forms/new-provider-reducer.ts +++ b/app/javascript/middleware/forms/new-provider-reducer.ts @@ -1,16 +1,16 @@ -import { AppState } from '../../miq-redux/redux-types'; +import { UPDATE_FORM, INIT_FORM } from '../../miq-redux/lib'; +import { AppState, IMiqReducerHash } from '../../miq-redux/redux-types'; import { merge, defaultsDeep } from 'lodash'; -export const INIT_NEW_PROVIDER: string = 'INIT_NEW_PROVIDER_HAWKULAR' -export const UPDATE_NEW_PROVIDER = 'UPDATE_NEW_PROVIDER_HAWKULAR'; function initNewProvider(state, action): AppState { const newProvider = { providers: { middleware: { hawkular: { newProvider: { - name: '', - type: undefined + type: undefined, + zone: 'default', + protocol: undefined } } } @@ -24,15 +24,16 @@ function updateNewProvider(state, action): AppState { providers: { middleware: { hawkular: { - newProvider: action.payload + newProvider: {...action.payload} } } } }; + console.log({ ...merge(state, newProvider) }); return { ...merge(state, newProvider) } } -export const reducers = { - [INIT_NEW_PROVIDER]: initNewProvider, - [UPDATE_NEW_PROVIDER]: updateNewProvider +export const reducers: IMiqReducerHash = { + [INIT_FORM]: initNewProvider, + [UPDATE_FORM]: updateNewProvider }; diff --git a/app/javascript/middleware/forms/new.ts b/app/javascript/middleware/forms/new.ts index 31be9c4e4bc..a49263408f5 100644 --- a/app/javascript/middleware/forms/new.ts +++ b/app/javascript/middleware/forms/new.ts @@ -1,8 +1,6 @@ import * as ng from 'angular'; -import { getStore, addReducer, applyReducerHash } from '../../miq-redux/lib'; -import { AppState, Action } from '../../miq-redux/redux-types'; -import { MiqStore } from '../../miq-redux/redux-types'; -import { INIT_NEW_PROVIDER, reducers, UPDATE_NEW_PROVIDER } from './new-provider-reducer'; +import { reducers } from './new-provider-reducer'; +import { DefaultFormController, IFormController } from '../../forms-common/defaultFormController'; export default class NewProviderForm implements ng.IComponentOptions { public templateUrl: string = '/static/middleware/new-provider.html.haml'; @@ -10,59 +8,50 @@ export default class NewProviderForm implements ng.IComponentOptions { public controllerAs: string = 'newProv'; public bindings: any = { types: '<', + zones: '<', formFieldsUrl: '@', novalidate: '@', createUrl: '@' }; } -class NewProviderController { +class NewProviderController extends DefaultFormController implements IFormController { + public zones: any; public types: any[]; - public componentState: Object; - public newProvider: any; + public formObject: any; private formFieldsUrl: string; private novalidate: boolean; private createUrl: string; - private reduxStore: MiqStore; - private unbind: any = {}; - public $name: string = 'newProvider'; private selects: NodeListOf; - - public static $inject = ['$element']; - - constructor(private $element: Element) { - this.unbind.reducer = addReducer(NewProviderController.applyReducers); - this.reduxStore = getStore(); - this.unbind.redux = this.reduxStore.subscribe(() => this.updateStore()); + public protocols = [ + ['', undefined], + ['SSL', 'ssl-with-validation'], + ['SSL trusting custom CA', 'ssl-with-validation-custom-ca'], + ['SSL without validation', 'ssl-with-validation'], + ['Non-SSL', 'non-ssl'], + ]; + + public static $inject = ['$element', '$scope', '$timeout']; + + constructor(private $element: Element, private $scope: ng.IScope, private $timeout: ng.ITimeoutService) { + super(reducers); } - private static applyReducers(state: AppState, action: Action) { - return applyReducerHash(reducers, state, action); - } - - public updateStore() { + public updateFormObject() { const currState: any = this.reduxStore.getState(); - this.newProvider = {...currState.providers.middleware.hawkular.newProvider}; - } - - public $onInit() { - this.selects = this.$element.querySelectorAll('select'); - this.reduxStore.dispatch({ type: INIT_NEW_PROVIDER }); - setTimeout(() => (angular.element(this.selects)).selectpicker('refresh')); + this.formObject = { ...currState.providers.middleware.hawkular.newProvider }; + this.refreshItems(); } - public onChangedProvider() { - console.log(this.newProvider); - this.reduxStore.dispatch({ - type: UPDATE_NEW_PROVIDER, payload: { - name: this.newProvider.name, - type: this.newProvider.type - } + public refreshItems() { + this.$timeout(() => { + this.$scope.$apply(); + (angular.element(this.selects)).selectpicker('refresh'); }); } - public $onDestroy() { - this.unbind.redux(); - this.unbind.reducer(); + public $onInit() { + super.$onInit(); + this.selects = this.$element.querySelectorAll('select'); } } diff --git a/app/javascript/miq-redux/lib.ts b/app/javascript/miq-redux/lib.ts index 47c15c1b1ba..81336a94740 100644 --- a/app/javascript/miq-redux/lib.ts +++ b/app/javascript/miq-redux/lib.ts @@ -9,3 +9,7 @@ export function getStore() { export function applyReducerHash(reducers, state, action) { return ManageIQ.redux.applyReducerHash(reducers, state, action); } + +export const UPDATE_FORM = 'UPDATE_FORM'; + +export const INIT_FORM = 'INIT_FORM'; diff --git a/app/views/ems_middleware/new.html.haml b/app/views/ems_middleware/new.html.haml index 1b689f0d1bb..e7c03ff0c63 100644 --- a/app/views/ems_middleware/new.html.haml +++ b/app/views/ems_middleware/new.html.haml @@ -17,6 +17,7 @@ %new-provider-form{"create-url" => "asd", "form-fields-url" => "rrr", "types" => "#{[[_('Choose'), nil]] + @ems_types}", + "zones" => "#{@server_zones}", :novalidate => "true"} = javascript_pack_tag 'manageiq-ui-classic/middleware_forms' :javascript diff --git a/app/views/static/middleware/new-provider.html.haml b/app/views/static/middleware/new-provider.html.haml index 76119dfc9ec..b9d6db82972 100644 --- a/app/views/static/middleware/new-provider.html.haml +++ b/app/views/static/middleware/new-provider.html.haml @@ -1,4 +1,4 @@ -%div +.form-horizontal .form-group %label.col-md-2.control-label{"for" => "ems_name"} = _('Name') @@ -6,19 +6,71 @@ %input.form-control{"type" => "text", "id" => "ems_name", "name" => "name", - "ng-model" => "newProv.newProvider.name", - "ng-change" => "newProv.onChangedProvider()", - "ng-value" => "newProv.newProvider.name", + "ng-model" => "newProv.formObject.name", + "ng-change" => "newProv.onChangeForm()", + "ng-value" => "newProv.formObject.name", "auto-focus" => ""} .form-group %label.col-md-2.control-label{"for" => "ems_type"} = _('Type') .col-md-8 - %select{"ng-options"=>"type[0] for type in newProv.types track by type[1]", - "ng-model" => "newProv.newProvider.type", + %select{"ng-options"=>"type[1] as type[0] for type in newProv.types track by type[1]", + "ng-model" => "newProv.formObject.type", + "ng-value" => "newProv.formObject.type", + "ng-change" => "newProv.onChangeForm()", "selectpicker-for-select-tag" => ""} - - -# %pf-select{"ng-model" => "newProv.newProvider.type", - -# "ng-change" => "newProv.onChangedProvider()", - -# "id" => "ems_type", - -# "options"=>"newProv.types"} + %option{"ng-value" => "undefined", :label => _('')} + = _('') + .form-group + %label.col-md-2.control-label{"for" => "ems_zone"} + = _("Zone") + .col-md-8 + %select{"ng-options"=>"zone[1] as zone[0] for zone in newProv.zones track by zone[1]", + "ng-model" => "newProv.formObject.zone", + "ng-value" => "newProv.formObject.zone", + "ng-change" => "newProv.onChangeForm()", + "selectpicker-for-select-tag" => ""} + %hr + %div{"ng-if" => "newProv.formObject.type"} + .form-group + %label.col-md-2.control-label{"for" => "ems_protocol"} + = _("Security Protocol") + .col-md-8 + %select{"ng-options"=>"protocol[1] as protocol[0] for protocol in newProv.protocols track by protocol[1]", + "ng-model" => "newProv.formObject.protocol", + "ng-value" => "newProv.formObject.protocol", + "ng-change" => "newProv.onChangeForm()", + "selectpicker-for-select-tag" => ""} + .form-group + %label.col-md-2.control-label{"for" => "ems_hostname"} + = _("Hostname (or IPv4 or IPv6 address)") + .col-md-4 + %input.form-control{:type => "text", + "ng-model" => "newProv.formObject.hostname", + "ng-value" => "newProv.formObject.hostname", + "ng-change" => "newProv.onChangeForm()"} + .form-group + %label.col-md-2.control-label{"for" => "ems_port"} + = _("API Port") + .col-md-2 + %input.form-control{:type => "text", + :maxlength => "15", + "ng-model" => "newProv.formObject.port", + "ng-value" => "newProv.formObject.port", + "ng-change" => "newProv.onChangeForm()"} + .form-group + %label.col-md-2.control-label{"for" => "ems_username"} + = _("Username") + .col-md-4 + %input.form-control{:type => "text", + "ng-model" => "newProv.formObject.username", + "ng-value" => "newProv.formObject.username", + "ng-change" => "newProv.onChangeForm()"} + .form-group + %label.col-md-2.control-label{"for" => "ems_pwd"} + = _("Password") + .col-md-4 + %input.form-control{:type => "password", + "ng-model" => "newProv.formObject.pwd", + "ng-value" => "newProv.formObject.pwd", + "ng-change" => "newProv.onChangeForm()"} From 91956229663255db349890c8c5fcc289d76e3fbe Mon Sep 17 00:00:00 2001 From: Karel Hala Date: Wed, 30 Aug 2017 16:10:25 +0200 Subject: [PATCH 15/17] Add Extensible component to hawkular new provider --- app/javascript/extensible-components/index.ts | 1 + .../middleware/forms/new-provider-reducer.ts | 2 +- app/javascript/middleware/forms/new.ts | 31 ++++++++++++++++++- app/javascript/miq-redux/redux-types.ts | 1 + .../static/middleware/new-provider.html.haml | 1 + config/webpack/loaders/react.js | 8 +++++ config/webpacker.yml | 1 + package.json | 7 +++-- tsconfig.json | 3 +- 9 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 config/webpack/loaders/react.js diff --git a/app/javascript/extensible-components/index.ts b/app/javascript/extensible-components/index.ts index 412e9d96e97..49ca3e03ad1 100644 --- a/app/javascript/extensible-components/index.ts +++ b/app/javascript/extensible-components/index.ts @@ -65,3 +65,4 @@ source.subscribe((component: IExtensionComponent) => { console.error('Unsupported action with extension components.'); } }); + diff --git a/app/javascript/middleware/forms/new-provider-reducer.ts b/app/javascript/middleware/forms/new-provider-reducer.ts index 0923bf44c71..f6fef76c99d 100644 --- a/app/javascript/middleware/forms/new-provider-reducer.ts +++ b/app/javascript/middleware/forms/new-provider-reducer.ts @@ -29,7 +29,7 @@ function updateNewProvider(state, action): AppState { } } }; - console.log({ ...merge(state, newProvider) }); + return { ...merge(state, newProvider) } } diff --git a/app/javascript/middleware/forms/new.ts b/app/javascript/middleware/forms/new.ts index a49263408f5..9a4a5cc6f4a 100644 --- a/app/javascript/middleware/forms/new.ts +++ b/app/javascript/middleware/forms/new.ts @@ -1,6 +1,14 @@ import * as ng from 'angular'; import { reducers } from './new-provider-reducer'; import { DefaultFormController, IFormController } from '../../forms-common/defaultFormController'; +import { + IExtensibleComponent, + IMiQApiCallback, + RenderCallback, + IMiQRenderCallback, + addComponent +} from '../../extensible-components/lib'; +import { ExtensibleComponent } from '../../extensible-components'; export default class NewProviderForm implements ng.IComponentOptions { public templateUrl: string = '/static/middleware/new-provider.html.haml'; @@ -15,7 +23,8 @@ export default class NewProviderForm implements ng.IComponentOptions { }; } -class NewProviderController extends DefaultFormController implements IFormController { +class NewProviderController extends DefaultFormController implements IFormController, IExtensibleComponent { + public extensibleComponent: ExtensibleComponent; public zones: any; public types: any[]; public formObject: any; @@ -35,6 +44,7 @@ class NewProviderController extends DefaultFormController implements IFormContro constructor(private $element: Element, private $scope: ng.IScope, private $timeout: ng.ITimeoutService) { super(reducers); + this.extensibleComponent = addComponent('new-provider-hawkular', this.apiCallbacks(), this.renderCallbacks()); } public updateFormObject() { @@ -54,4 +64,23 @@ class NewProviderController extends DefaultFormController implements IFormContro super.$onInit(); this.selects = this.$element.querySelectorAll('select'); } + + public $onDestroy() { + super.$onDestroy(); + this.extensibleComponent.delete(); + } + + public apiCallbacks(): IMiQApiCallback { + return {} + } + + public renderCallbacks(): IMiQRenderCallback { + return { + newFieldsElement: (renderCallback) => this.newField(renderCallback) + } + } + + private newField(renderCallback: RenderCallback) { + this.$timeout(() => renderCallback(angular.element(this.$element).find('.form-group.additional-fields')[0])); + } } diff --git a/app/javascript/miq-redux/redux-types.ts b/app/javascript/miq-redux/redux-types.ts index 12548d72817..1a307d38073 100644 --- a/app/javascript/miq-redux/redux-types.ts +++ b/app/javascript/miq-redux/redux-types.ts @@ -5,6 +5,7 @@ export interface IMiqAction extends Action { type: string; payload?: any; } + export interface IMiqReducerHash { [propName: string]: AppReducer; } diff --git a/app/views/static/middleware/new-provider.html.haml b/app/views/static/middleware/new-provider.html.haml index b9d6db82972..ba9ab5d8903 100644 --- a/app/views/static/middleware/new-provider.html.haml +++ b/app/views/static/middleware/new-provider.html.haml @@ -30,6 +30,7 @@ "ng-value" => "newProv.formObject.zone", "ng-change" => "newProv.onChangeForm()", "selectpicker-for-select-tag" => ""} + .form-group.additional-fields %hr %div{"ng-if" => "newProv.formObject.type"} .form-group diff --git a/config/webpack/loaders/react.js b/config/webpack/loaders/react.js new file mode 100644 index 00000000000..326d69017f2 --- /dev/null +++ b/config/webpack/loaders/react.js @@ -0,0 +1,8 @@ +module.exports = { + test: /.jsx?$/, + loader: 'babel-loader', + exclude: /node_modules/, + query: { + presets: ['es2015', 'react'] + } +} diff --git a/config/webpacker.yml b/config/webpacker.yml index 823f4cdd28e..0fddea2afe9 100644 --- a/config/webpacker.yml +++ b/config/webpacker.yml @@ -10,6 +10,7 @@ default: &default - .erb - .js - .jsx + - .tsx - .ts - .vue - .sass diff --git a/package.json b/package.json index 2fa2141e43e..a7a68a5133f 100644 --- a/package.json +++ b/package.json @@ -34,13 +34,16 @@ "@types/lodash": "^4.14.73", "@types/redux": "^3.6.0", "autoprefixer": "~6.7.7", - "babel-core": "~6.24.1", + "awesome-typescript-loader": "^3.2.3", + "babel-core": "^6.24.1", "babel-eslint": "~6.0.4", - "babel-loader": "~7.0", + "babel-loader": "^7.0.0", "babel-plugin-syntax-dynamic-import": "^6.18.0", "babel-plugin-transform-class-properties": "^6.24.1", "babel-polyfill": "^6.23.0", "babel-preset-env": "~1.4.0", + "babel-preset-es2015": "^6.24.1", + "babel-preset-react": "^6.24.1", "coffee-loader": "~0.7.3", "coffee-script": "~1.12.5", "compression-webpack-plugin": "~0.4.0", diff --git a/tsconfig.json b/tsconfig.json index 92d92aff8b9..8e8d141338e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,8 @@ "module": "es6", "moduleResolution": "node", "sourceMap": true, - "target": "es5" + "target": "es5", + "jsx": "react" }, "exclude": [ "**/*.spec.ts", From 5e50054c414c2da81b7192772d862623a8a39709 Mon Sep 17 00:00:00 2001 From: Karel Hala Date: Wed, 13 Sep 2017 11:05:54 -0400 Subject: [PATCH 16/17] Use ng-redux to work with state --- app/assets/javascripts/application.js | 1 + .../javascripts/miq_angular_application.js | 1 + app/javascript/miq-redux/store.ts | 18 +++++++++++++----- package.json | 1 + 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 4d15da32ff8..59f9219b17f 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -84,3 +84,4 @@ //= require rx-angular/dist/rx.angular //= require patternfly-timeline/dist/timeline //= require ui-select/dist/select +//= require ng-redux/dist/ng-redux diff --git a/app/assets/javascripts/miq_angular_application.js b/app/assets/javascripts/miq_angular_application.js index 5c0f3e8fe3a..f5ab93ce8f8 100644 --- a/app/assets/javascripts/miq_angular_application.js +++ b/app/assets/javascripts/miq_angular_application.js @@ -10,6 +10,7 @@ ManageIQ.angular.app = angular.module('ManageIQ', [ 'miq.card', 'miq.util', 'kubernetesUI', + 'ngRedux', 'miqStaticAssets.dialogEditor', ]); miqHttpInject(ManageIQ.angular.app); diff --git a/app/javascript/miq-redux/store.ts b/app/javascript/miq-redux/store.ts index 6ea693a6545..289c871031f 100644 --- a/app/javascript/miq-redux/store.ts +++ b/app/javascript/miq-redux/store.ts @@ -11,9 +11,17 @@ export function configureStore(initialState?: AppState): Store { applyMiddleware(...middlewares), ); - return createStore( - rootReducer, - initialState!, - enhancer, - ); + let reduxStore; + if (ManageIQ.angular.app) { + ManageIQ.angular.app.config(['$ngReduxProvider', ($ngReduxProvider) => { + $ngReduxProvider.createStoreWith(rootReducer, middlewares, [enhancer], initialState); + }]) + .run(['$ngRedux', ($ngRedux) => { + ManageIQ.redux.store = ManageIQ.redux.store || $ngRedux; + }]); + } else { + return reduxStore = createStore(rootReducer, + initialState!, + enhancer); + } } diff --git a/package.json b/package.json index a7a68a5133f..b4991ae29b0 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@angular/platform-browser": "~4.0.3", "@angular/platform-browser-dynamic": "~4.0.3", "core-js": "~2.4.1", + "ng-redux": "^3.5.2", "redux": "^3.7.2", "reselect": "^3.0.1", "rxjs": "~5.3.0", From c62816ca3333277573a05c60da18fe84f25a92db Mon Sep 17 00:00:00 2001 From: Karel Hala Date: Wed, 13 Sep 2017 12:46:09 -0400 Subject: [PATCH 17/17] Refresh form after changes in angular, connect to store --- .../forms-common/defaultFormController.ts | 38 ++++++++++--------- .../middleware/forms/formActions.ts | 5 +++ app/javascript/middleware/forms/new.ts | 19 ++++++---- app/javascript/miq-redux/store.ts | 13 ++++--- 4 files changed, 43 insertions(+), 32 deletions(-) create mode 100644 app/javascript/middleware/forms/formActions.ts diff --git a/app/javascript/forms-common/defaultFormController.ts b/app/javascript/forms-common/defaultFormController.ts index fb19d2aa069..a0f1432db8f 100644 --- a/app/javascript/forms-common/defaultFormController.ts +++ b/app/javascript/forms-common/defaultFormController.ts @@ -7,33 +7,42 @@ export interface IUnbindReduxReducers { } export interface IFormController { - updateFormObject: () => void; + mapStateToThis: (state: AppState) => any; } export abstract class DefaultFormController { protected unbind: IUnbindReduxReducers = {}; - protected reduxStore: MiqStore; + protected reduxStore: any; public formObject: any; /** * Constructor which will get reduxStore and subscribes to it. * @param reducersHash optional, hash of reducers which will be added to rootReducer. */ - constructor(reducersHash?: IMiqReducerHash) { + constructor(reducersHash?: IMiqReducerHash, protected Actions?) { if (reducersHash) { this.unbind.reducer = addReducer( (state: AppState, action: Action) => applyReducerHash(reducersHash, state, action) ); } this.reduxStore = getStore(); - this.unbind.redux = this.reduxStore.subscribe(() => this.updateFormObject()); + this.initForm(); + this.unbind.redux = this.reduxStore.connect(this.mapStateToThis, Actions)(this); + this.reduxStore.subscribe(() => this.refreshForm()); } - /** - * Method for updating form object with current store. - */ - protected updateFormObject(): void { - throw new Error('Controller should implement updateStore method'); + protected refreshForm() {} + + protected mapStateToThis(state: AppState): any { + throw new Error('Controller should implement mapStateToThis method'); + } + + public updateForm(payload) { + throw new Error('Controller should implement updateForm method, did you forget to import it?'); + } + + public initForm() { + this.reduxStore.dispatch({type: INIT_FORM}); } /** @@ -53,13 +62,6 @@ export abstract class DefaultFormController { * It will pass formObject as payload to it. */ public onChangeForm(): void { - this.reduxStore.dispatch({ type: UPDATE_FORM, payload: this.formObject }); + this.updateForm(this.formObject); } - - /** - * Method which is fired on form init, it will fire action `INIT_FORM` for defining default values in form. - */ - protected $onInit(): void { - this.reduxStore.dispatch({ type: INIT_FORM }); - } -} \ No newline at end of file +} diff --git a/app/javascript/middleware/forms/formActions.ts b/app/javascript/middleware/forms/formActions.ts new file mode 100644 index 00000000000..73370756e31 --- /dev/null +++ b/app/javascript/middleware/forms/formActions.ts @@ -0,0 +1,5 @@ +import { UPDATE_FORM, INIT_FORM } from '../../miq-redux/lib' + +export function updateForm(payload) { + return dispatch => dispatch({type: UPDATE_FORM, payload: payload}); +} diff --git a/app/javascript/middleware/forms/new.ts b/app/javascript/middleware/forms/new.ts index 9a4a5cc6f4a..a1d1730d4d7 100644 --- a/app/javascript/middleware/forms/new.ts +++ b/app/javascript/middleware/forms/new.ts @@ -9,6 +9,7 @@ import { addComponent } from '../../extensible-components/lib'; import { ExtensibleComponent } from '../../extensible-components'; +import * as FormActions from './formActions'; export default class NewProviderForm implements ng.IComponentOptions { public templateUrl: string = '/static/middleware/new-provider.html.haml'; @@ -42,18 +43,20 @@ class NewProviderController extends DefaultFormController implements IFormContro public static $inject = ['$element', '$scope', '$timeout']; - constructor(private $element: Element, private $scope: ng.IScope, private $timeout: ng.ITimeoutService) { - super(reducers); + constructor(private $element: Element, + private $scope: ng.IScope, + private $timeout: ng.ITimeoutService) { + super(reducers, FormActions); this.extensibleComponent = addComponent('new-provider-hawkular', this.apiCallbacks(), this.renderCallbacks()); } - public updateFormObject() { - const currState: any = this.reduxStore.getState(); - this.formObject = { ...currState.providers.middleware.hawkular.newProvider }; - this.refreshItems(); + public mapStateToThis(state) { + return { + formObject: state.providers.middleware.hawkular.newProvider + } } - public refreshItems() { + public refreshForm() { this.$timeout(() => { this.$scope.$apply(); (angular.element(this.selects)).selectpicker('refresh'); @@ -61,8 +64,8 @@ class NewProviderController extends DefaultFormController implements IFormContro } public $onInit() { - super.$onInit(); this.selects = this.$element.querySelectorAll('select'); + this.refreshForm(); } public $onDestroy() { diff --git a/app/javascript/miq-redux/store.ts b/app/javascript/miq-redux/store.ts index 289c871031f..12e912e18f7 100644 --- a/app/javascript/miq-redux/store.ts +++ b/app/javascript/miq-redux/store.ts @@ -13,12 +13,13 @@ export function configureStore(initialState?: AppState): Store { let reduxStore; if (ManageIQ.angular.app) { - ManageIQ.angular.app.config(['$ngReduxProvider', ($ngReduxProvider) => { - $ngReduxProvider.createStoreWith(rootReducer, middlewares, [enhancer], initialState); - }]) - .run(['$ngRedux', ($ngRedux) => { - ManageIQ.redux.store = ManageIQ.redux.store || $ngRedux; - }]); + ManageIQ.angular.app + .config(['$ngReduxProvider', ($ngReduxProvider) => { + $ngReduxProvider.createStoreWith(rootReducer, middlewares, [enhancer], initialState); + }]) + .run(['$ngRedux', ($ngRedux) => { + ManageIQ.redux.store = ManageIQ.redux.store || $ngRedux; + }]); } else { return reduxStore = createStore(rootReducer, initialState!,