blob: b4434c8cd6c3a8a1a6c1b2300f520aa5225fa7c4 [file] [log] [blame]
Matteo Scandolofb46ae62017-08-08 09:10:50 -07001
2/*
3 * Copyright 2017-present Open Networking Foundation
4
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8
9 * http://www.apache.org/licenses/LICENSE-2.0
10
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18
Matteo Scandolo9f87f302016-12-13 18:11:10 -080019import {IXosTableCfg} from '../../core/table/table';
Matteo Scandolo47860fe2017-02-02 12:05:55 -080020import {IXosModelStoreService} from '../../datasources/stores/model.store';
Matteo Scandolod58d5042016-12-16 16:59:21 -080021import {IXosConfigHelpersService} from '../../core/services/helpers/config.helpers';
Matteo Scandoloee655a12016-12-19 15:38:43 -080022import * as _ from 'lodash';
Matteo Scandolo80c3a652017-01-06 10:48:31 -080023import {IXosResourceService} from '../../datasources/rest/model.rest';
Matteo Scandolo04964232017-01-07 12:53:46 -080024import {IStoreHelpersService} from '../../datasources/helpers/store.helpers';
Matteo Scandolo580033a2017-08-17 11:16:39 -070025import {IXosModelDiscovererService, IXosModel} from '../../datasources/helpers/model-discoverer.service';
Matteo Scandolo5d962a32017-08-01 18:16:14 -070026import './crud.scss';
27import {IXosCrudRelationService} from './crud.relations.service';
Matteo Scandoloc8a58c82017-08-17 17:14:38 -070028import {IXosDebugService, IXosDebugStatus} from '../../core/debug/debug.service';
29import {IXosKeyboardShortcutService} from '../../core/services/keyboard-shortcut';
Matteo Scandolo04a34a12017-09-12 10:37:48 -070030import {Subscription} from 'rxjs';
Matteo Scandolo1aee1982017-02-17 08:33:23 -080031
32export interface IXosModelRelation {
33 model: string;
34 type: string;
Matteo Scandolo5d962a32017-08-01 18:16:14 -070035 on_field: string;
Matteo Scandolo9f87f302016-12-13 18:11:10 -080036}
37
38class CrudController {
Matteo Scandolo1aee1982017-02-17 08:33:23 -080039 static $inject = [
40 '$scope',
Matteo Scandolo5d962a32017-08-01 18:16:14 -070041 '$log',
Matteo Scandolo1aee1982017-02-17 08:33:23 -080042 '$state',
43 '$stateParams',
44 'XosModelStore',
45 'ConfigHelpers',
46 'ModelRest',
47 'StoreHelpers',
Matteo Scandolo5d962a32017-08-01 18:16:14 -070048 'XosModelDiscoverer',
Matteo Scandoloc8a58c82017-08-17 17:14:38 -070049 'XosCrudRelation',
50 'XosDebug',
51 'XosKeyboardShortcut'
Matteo Scandolo1aee1982017-02-17 08:33:23 -080052 ];
Matteo Scandolo9f87f302016-12-13 18:11:10 -080053
Matteo Scandolo5d962a32017-08-01 18:16:14 -070054 // bindings
55
Matteo Scandolo1aee1982017-02-17 08:33:23 -080056 public data: {model: string};
Matteo Scandolo9f87f302016-12-13 18:11:10 -080057 public tableCfg: IXosTableCfg;
Matteo Scandoloee655a12016-12-19 15:38:43 -080058 public formCfg: any;
Matteo Scandoloee655a12016-12-19 15:38:43 -080059 public baseUrl: string;
60 public list: boolean;
Matteo Scandolo580033a2017-08-17 11:16:39 -070061 public modelName: string;
62 public pluralTitle: string;
63 public singularTitle: string;
Matteo Scandolo9f87f302016-12-13 18:11:10 -080064 public tableData: any[];
Matteo Scandolo580033a2017-08-17 11:16:39 -070065 public model: any; // holds the real model
66 public modelDef: IXosModel;
Matteo Scandolo5d962a32017-08-01 18:16:14 -070067 public related: {manytoone: IXosModelRelation[], onetomany: IXosModelRelation[]} = {
68 manytoone: [],
69 onetomany: []
70 };
71 public relatedModels: {manytoone: any, onetomany: any} = {
72 manytoone: {},
73 onetomany: {}
74 };
Matteo Scandoloc8a58c82017-08-17 17:14:38 -070075 public debugTab: boolean;
Matteo Scandolo9f87f302016-12-13 18:11:10 -080076
Matteo Scandolo04a34a12017-09-12 10:37:48 -070077 private subscription: Subscription;
78
Matteo Scandolo9f87f302016-12-13 18:11:10 -080079 constructor(
Matteo Scandolof2c3ed62016-12-15 14:32:50 -080080 private $scope: angular.IScope,
Matteo Scandolo5d962a32017-08-01 18:16:14 -070081 private $log: angular.ILogService,
Matteo Scandoloee655a12016-12-19 15:38:43 -080082 private $state: angular.ui.IStateService,
83 private $stateParams: ng.ui.IStateParamsService,
Matteo Scandolo47860fe2017-02-02 12:05:55 -080084 private store: IXosModelStoreService,
Matteo Scandolo80c3a652017-01-06 10:48:31 -080085 private ConfigHelpers: IXosConfigHelpersService,
Matteo Scandolo04964232017-01-07 12:53:46 -080086 private ModelRest: IXosResourceService,
Matteo Scandolo1aee1982017-02-17 08:33:23 -080087 private StoreHelpers: IStoreHelpersService,
Matteo Scandolo5d962a32017-08-01 18:16:14 -070088 private XosModelDiscovererService: IXosModelDiscovererService,
Matteo Scandoloc8a58c82017-08-17 17:14:38 -070089 private XosCrudRelation: IXosCrudRelationService,
90 private XosDebug: IXosDebugService,
91 private XosKeyboardShortcut: IXosKeyboardShortcutService
Matteo Scandolo9f87f302016-12-13 18:11:10 -080092 ) {
Matteo Scandoloa4718592017-08-10 14:54:51 -070093 this.$log.info('[XosCrud] Setup', $state.current.data);
Matteo Scandolo5d962a32017-08-01 18:16:14 -070094
Matteo Scandolo9f87f302016-12-13 18:11:10 -080095 this.data = this.$state.current.data;
Matteo Scandolo580033a2017-08-17 11:16:39 -070096 this.modelDef = this.XosModelDiscovererService.get(this.data.model);
97 this.modelName = this.modelDef.verbose_name ? this.modelDef.verbose_name : this.modelDef.name;
98 this.pluralTitle = this.ConfigHelpers.pluralize(this.modelName);
99 this.singularTitle = this.ConfigHelpers.pluralize(this.modelName, 1);
Matteo Scandolo9f87f302016-12-13 18:11:10 -0800100
Matteo Scandoloee655a12016-12-19 15:38:43 -0800101 this.list = true;
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800102
103 // TODO get the proper URL from model discoverer
Matteo Scandolo580033a2017-08-17 11:16:39 -0700104 this.baseUrl = '#/' + this.modelDef.clientUrl.replace(':id?', '');
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800105
Matteo Scandolo580033a2017-08-17 11:16:39 -0700106 this.tableCfg = this.modelDef.tableCfg;
107 this.formCfg = this.modelDef.formCfg;
Matteo Scandoloee655a12016-12-19 15:38:43 -0800108
Matteo Scandoloc8a58c82017-08-17 17:14:38 -0700109 this.debugTab = this.XosDebug.status.modelsTab;
110 this.$scope.$on('xos.debug.status', (e, status: IXosDebugStatus) => {
111 this.debugTab = status.modelsTab;
112 this.$scope.$apply();
113 });
114
Matteo Scandoloee655a12016-12-19 15:38:43 -0800115 // if it is a detail page
116 if ($stateParams['id']) {
117 this.list = false;
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800118
119 // if it is the create page
120 if ($stateParams['id'] === 'add') {
121 // generate a resource for an empty model
Matteo Scandolo47c53fc2017-03-23 14:11:32 -0700122 const endpoint = this.XosModelDiscovererService.getApiUrlFromModel(this.XosModelDiscovererService.get(this.data.model));
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800123 const resource = this.ModelRest.getResource(endpoint);
124 this.model = new resource({});
125 }
Matteo Scandolo04a34a12017-09-12 10:37:48 -0700126 else {
127 this.subscription = this.store.get(this.data.model, $stateParams['id'])
128 .first(val => {
129 // NOTE emit an event only if we have an object, and only the first time we have it
130 return Object.keys(val).length > 0;
131 })
132 .subscribe(res => {
133 $scope.$evalAsync(() => {
134 this.related.onetomany = _.filter($state.current.data.relations, {type: 'onetomany'});
135 this.related.manytoone = _.filter($state.current.data.relations, {type: 'manytoone'});
136 this.model = res;
137 this.getRelatedModels(this.related, this.model);
138 });
139 });
140 }
Matteo Scandoloc8a58c82017-08-17 17:14:38 -0700141
142 this.XosKeyboardShortcut.registerKeyBinding({
143 key: 'A',
144 cb: () => this.XosDebug.toggleDebug('modelsTab'),
145 description: 'Toggle Debug tab in model details view'
146 }, 'view');
147
148 this.XosKeyboardShortcut.registerKeyBinding({
149 key: 'delete',
150 cb: () => {
151 this.$state.go(this.$state.current.name, {id: null});
152 },
153 description: 'Go back to the list view'
154 }, 'view');
155 }
156 // list page
157 else {
158 this.tableCfg.selectedRow = -1;
159
160 this.XosKeyboardShortcut.registerKeyBinding({
161 key: 'Tab',
162 cb: () => this.iterateItems(),
163 description: 'Iterate trough items in the list'
164 }, 'view');
165
166 this.XosKeyboardShortcut.registerKeyBinding({
167 key: 'Enter',
168 cb: () => {
169 if (this.tableCfg.selectedRow < 0) {
170 return;
171 }
172 this.$state.go(this.$state.current.name, {id: this.tableCfg.filteredData[this.tableCfg.selectedRow].id});
173 },
174 description: 'View details of selected item'
175 }, 'view');
176
177 this.XosKeyboardShortcut.registerKeyBinding({
178 key: 'Delete',
179 cb: () => {
180 if (this.tableCfg.selectedRow < 0) {
181 return;
182 }
183 const deleteFn = _.find(this.tableCfg.actions, {label: 'delete'});
184 deleteFn.cb(this.tableCfg.filteredData[this.tableCfg.selectedRow]);
185 },
186 description: 'View details of selected item'
187 }, 'view');
188
189 // FIXME XosKeyboardShortcut modifiers does not look to work
190 // this.XosKeyboardShortcut.registerKeyBinding({
191 // key: 'Tab',
192 // modifiers: ['alt'],
193 // cb: () => {
194 // this.tableCfg.selectedRow = -1;
195 // },
196 // description: 'Clear selected item'
197 // }, 'view');
Matteo Scandolo04a34a12017-09-12 10:37:48 -0700198
199 this.subscription = this.store.query(this.data.model)
200 .subscribe(
201 (event) => {
202 // NOTE Observable mess with $digest cycles, we need to schedule the expression later
203 $scope.$evalAsync(() => {
204 this.tableData = event;
205 });
206 }
207 );
Matteo Scandoloee655a12016-12-19 15:38:43 -0800208 }
Matteo Scandolo9f87f302016-12-13 18:11:10 -0800209 }
Matteo Scandolo00d97892016-12-23 17:53:12 -0800210
Matteo Scandolo04a34a12017-09-12 10:37:48 -0700211 $onDestroy() {
212 this.subscription.unsubscribe();
213 this.$log.info(`[XosCrud] Destroying component`);
214 }
215
Matteo Scandoloc8a58c82017-08-17 17:14:38 -0700216 public iterateItems() {
217 const rowCount = this.tableCfg.filteredData.length > this.tableCfg.pagination.pageSize ? this.tableCfg.pagination.pageSize : this.tableCfg.filteredData.length;
218 if ((this.tableCfg.selectedRow + 1) < rowCount) {
219 this.tableCfg.selectedRow++;
220 }
221 else {
222 this.tableCfg.selectedRow = 0;
223 }
224 this.$scope.$apply();
225 }
Matteo Scandolo5d962a32017-08-01 18:16:14 -0700226
227 public getRelatedItemId(relation: IXosModelRelation, item: any): boolean {
228 return this.XosCrudRelation.existsRelatedItem(relation, item);
229 }
230
231 public getHumanReadableOnField(r: IXosModelRelation) {
232 return this.XosCrudRelation.getHumanReadableOnField(r, this.data.model);
233 }
234
235 public getRelatedModels(relations: {manytoone: IXosModelRelation[], onetomany: IXosModelRelation[]}, item: any) {
Matteo Scandoloa4718592017-08-10 14:54:51 -0700236 this.$log.debug(`[XosCrud] Managing relation for ${this.data.model}:`, relations);
Matteo Scandolo5d962a32017-08-01 18:16:14 -0700237
238 // loading many to one relations (you'll get a model)
239 _.forEach(relations.manytoone, (r: IXosModelRelation) => {
240 if (!item || !item[`${r.on_field.toLowerCase()}_id`]) {
241 return;
242 }
243
244 this.$log.debug(`[XosCrud] Loading manytoone relation with ${r.model} on ${r.on_field}`);
245
246 if (!angular.isDefined(this.relatedModels.manytoone[r.model])) {
247 this.relatedModels.manytoone[r.model] = {};
248 }
249
250 this.XosCrudRelation.getModel(r, item[`${r.on_field.toLowerCase()}_id`])
251 .then(res => {
252 this.relatedModels.manytoone[r.model][r.on_field] = res;
253 })
254 .catch(err => {
255 this.$log.error(`[XosCrud] Error loading manytoone relation with ${r.model} on ${r.on_field}`, err);
256 });
257 });
258
259 // loading onetomany relations (you'll get a list of models)
260 _.forEach(relations.onetomany, (r: IXosModelRelation) => {
261 if (!item) {
262 return;
263 }
264
265 this.$log.debug(`[XosCrud] Loading onetomany relation with ${r.model} on ${r.on_field}`);
266
267 if (!angular.isDefined(this.relatedModels.onetomany[r.model])) {
268 this.relatedModels.onetomany[r.model] = {};
269 }
270
271 this.XosCrudRelation.getModels(r, item.id)
272 .then(res => {
273 this.relatedModels.onetomany[r.model][r.on_field] = res;
274 })
275 .catch(err => {
276 this.$log.error(`[XosCrud] Error loading onetomany relation with ${r.model} on ${r.on_field}`, err);
277 });
278 });
Matteo Scandolo00d97892016-12-23 17:53:12 -0800279 }
Matteo Scandolo9f87f302016-12-13 18:11:10 -0800280}
281
282export const xosCrud: angular.IComponentOptions = {
283 template: require('./crud.html'),
284 controllerAs: 'vm',
285 controller: CrudController
286};