blob: 2aafe41bf145ee3a9996a441f07b85ea5799e80d [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 Scandolo04f487c2017-09-12 10:37:48 -070030import {Subscription} from 'rxjs';
Matteo Scandolo63498472017-09-26 17:21:41 -070031import {IXosModeldefsCache} from '../../datasources/helpers/modeldefs.service';
Matteo Scandolo1aee1982017-02-17 08:33:23 -080032
33export interface IXosModelRelation {
34 model: string;
35 type: string;
Matteo Scandolo5d962a32017-08-01 18:16:14 -070036 on_field: string;
Matteo Scandolo9f87f302016-12-13 18:11:10 -080037}
38
39class CrudController {
Matteo Scandolo1aee1982017-02-17 08:33:23 -080040 static $inject = [
41 '$scope',
Matteo Scandolo5d962a32017-08-01 18:16:14 -070042 '$log',
Matteo Scandolo1aee1982017-02-17 08:33:23 -080043 '$state',
44 '$stateParams',
45 'XosModelStore',
46 'ConfigHelpers',
47 'ModelRest',
48 'StoreHelpers',
Matteo Scandolo5d962a32017-08-01 18:16:14 -070049 'XosModelDiscoverer',
Matteo Scandoloc8a58c82017-08-17 17:14:38 -070050 'XosCrudRelation',
51 'XosDebug',
Matteo Scandolo63498472017-09-26 17:21:41 -070052 'XosKeyboardShortcut',
53 'XosModeldefsCache'
Matteo Scandolo1aee1982017-02-17 08:33:23 -080054 ];
Matteo Scandolo9f87f302016-12-13 18:11:10 -080055
Matteo Scandolo5d962a32017-08-01 18:16:14 -070056 // bindings
57
Matteo Scandolo1aee1982017-02-17 08:33:23 -080058 public data: {model: string};
Matteo Scandolo9f87f302016-12-13 18:11:10 -080059 public tableCfg: IXosTableCfg;
Matteo Scandoloee655a12016-12-19 15:38:43 -080060 public formCfg: any;
Matteo Scandoloee655a12016-12-19 15:38:43 -080061 public baseUrl: string;
62 public list: boolean;
Matteo Scandolo580033a2017-08-17 11:16:39 -070063 public modelName: string;
64 public pluralTitle: string;
65 public singularTitle: string;
Matteo Scandolo9f87f302016-12-13 18:11:10 -080066 public tableData: any[];
Matteo Scandolo580033a2017-08-17 11:16:39 -070067 public model: any; // holds the real model
68 public modelDef: IXosModel;
Matteo Scandolo5d962a32017-08-01 18:16:14 -070069 public related: {manytoone: IXosModelRelation[], onetomany: IXosModelRelation[]} = {
70 manytoone: [],
71 onetomany: []
72 };
73 public relatedModels: {manytoone: any, onetomany: any} = {
74 manytoone: {},
75 onetomany: {}
76 };
Matteo Scandoloc8a58c82017-08-17 17:14:38 -070077 public debugTab: boolean;
Matteo Scandolo9f87f302016-12-13 18:11:10 -080078
Matteo Scandolo39e04152017-11-29 14:24:45 -080079 public getRelatedModels = _.memoize(this._getRelatedModels);
80
Matteo Scandolo04f487c2017-09-12 10:37:48 -070081 private subscription: Subscription;
82
Matteo Scandolo9f87f302016-12-13 18:11:10 -080083 constructor(
Matteo Scandolof2c3ed62016-12-15 14:32:50 -080084 private $scope: angular.IScope,
Matteo Scandolo5d962a32017-08-01 18:16:14 -070085 private $log: angular.ILogService,
Matteo Scandoloee655a12016-12-19 15:38:43 -080086 private $state: angular.ui.IStateService,
87 private $stateParams: ng.ui.IStateParamsService,
Matteo Scandolo47860fe2017-02-02 12:05:55 -080088 private store: IXosModelStoreService,
Matteo Scandolo80c3a652017-01-06 10:48:31 -080089 private ConfigHelpers: IXosConfigHelpersService,
Matteo Scandolo04964232017-01-07 12:53:46 -080090 private ModelRest: IXosResourceService,
Matteo Scandolo1aee1982017-02-17 08:33:23 -080091 private StoreHelpers: IStoreHelpersService,
Matteo Scandolo5d962a32017-08-01 18:16:14 -070092 private XosModelDiscovererService: IXosModelDiscovererService,
Matteo Scandoloc8a58c82017-08-17 17:14:38 -070093 private XosCrudRelation: IXosCrudRelationService,
94 private XosDebug: IXosDebugService,
Matteo Scandolo63498472017-09-26 17:21:41 -070095 private XosKeyboardShortcut: IXosKeyboardShortcutService,
96 private XosModeldefsCache: IXosModeldefsCache
Matteo Scandolo9f87f302016-12-13 18:11:10 -080097 ) {
Matteo Scandoloa4718592017-08-10 14:54:51 -070098 this.$log.info('[XosCrud] Setup', $state.current.data);
Matteo Scandolo5d962a32017-08-01 18:16:14 -070099
Matteo Scandolo9f87f302016-12-13 18:11:10 -0800100 this.data = this.$state.current.data;
Matteo Scandolo63498472017-09-26 17:21:41 -0700101 this.modelDef = this.XosModeldefsCache.get(this.data.model);
Matteo Scandolo580033a2017-08-17 11:16:39 -0700102 this.modelName = this.modelDef.verbose_name ? this.modelDef.verbose_name : this.modelDef.name;
103 this.pluralTitle = this.ConfigHelpers.pluralize(this.modelName);
104 this.singularTitle = this.ConfigHelpers.pluralize(this.modelName, 1);
Matteo Scandolo9f87f302016-12-13 18:11:10 -0800105
Matteo Scandoloee655a12016-12-19 15:38:43 -0800106 this.list = true;
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800107
108 // TODO get the proper URL from model discoverer
Matteo Scandolo580033a2017-08-17 11:16:39 -0700109 this.baseUrl = '#/' + this.modelDef.clientUrl.replace(':id?', '');
Matteo Scandolo1aee1982017-02-17 08:33:23 -0800110
Matteo Scandolo580033a2017-08-17 11:16:39 -0700111 this.tableCfg = this.modelDef.tableCfg;
112 this.formCfg = this.modelDef.formCfg;
Matteo Scandoloee655a12016-12-19 15:38:43 -0800113
Matteo Scandoloc8a58c82017-08-17 17:14:38 -0700114 this.debugTab = this.XosDebug.status.modelsTab;
115 this.$scope.$on('xos.debug.status', (e, status: IXosDebugStatus) => {
116 this.debugTab = status.modelsTab;
117 this.$scope.$apply();
118 });
119
Matteo Scandoloee655a12016-12-19 15:38:43 -0800120 // if it is a detail page
121 if ($stateParams['id']) {
122 this.list = false;
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800123
124 // if it is the create page
125 if ($stateParams['id'] === 'add') {
126 // generate a resource for an empty model
Matteo Scandolo63498472017-09-26 17:21:41 -0700127 const endpoint = this.XosModelDiscovererService.getApiUrlFromModel(this.XosModeldefsCache.get(this.data.model));
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800128 const resource = this.ModelRest.getResource(endpoint);
129 this.model = new resource({});
Matteo Scandolo39e04152017-11-29 14:24:45 -0800130
131 // attach a redirect to the $save method
132 const originalSave = angular.copy(this.formCfg.actions[0].cb);
133 this.formCfg.actions[0].cb = (item, form: angular.IFormController) => {
134 originalSave(item, form)
135 .then(res => {
136 this.$state.go(this.$state.current, {id: res.id});
137 })
138 .catch(err => {
139 this.$log.error(`[XosCrud] Error while saving:`, item, err);
140 });
141 };
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800142 }
Matteo Scandolo04f487c2017-09-12 10:37:48 -0700143 else {
144 this.subscription = this.store.get(this.data.model, $stateParams['id'])
Matteo Scandolo04f487c2017-09-12 10:37:48 -0700145 .subscribe(res => {
146 $scope.$evalAsync(() => {
147 this.related.onetomany = _.filter($state.current.data.relations, {type: 'onetomany'});
148 this.related.manytoone = _.filter($state.current.data.relations, {type: 'manytoone'});
149 this.model = res;
150 this.getRelatedModels(this.related, this.model);
151 });
152 });
153 }
Matteo Scandoloc8a58c82017-08-17 17:14:38 -0700154
155 this.XosKeyboardShortcut.registerKeyBinding({
156 key: 'A',
157 cb: () => this.XosDebug.toggleDebug('modelsTab'),
158 description: 'Toggle Debug tab in model details view'
159 }, 'view');
160
161 this.XosKeyboardShortcut.registerKeyBinding({
162 key: 'delete',
163 cb: () => {
164 this.$state.go(this.$state.current.name, {id: null});
165 },
166 description: 'Go back to the list view'
167 }, 'view');
168 }
169 // list page
170 else {
171 this.tableCfg.selectedRow = -1;
172
173 this.XosKeyboardShortcut.registerKeyBinding({
174 key: 'Tab',
175 cb: () => this.iterateItems(),
176 description: 'Iterate trough items in the list'
177 }, 'view');
178
179 this.XosKeyboardShortcut.registerKeyBinding({
180 key: 'Enter',
181 cb: () => {
182 if (this.tableCfg.selectedRow < 0) {
183 return;
184 }
185 this.$state.go(this.$state.current.name, {id: this.tableCfg.filteredData[this.tableCfg.selectedRow].id});
186 },
187 description: 'View details of selected item'
188 }, 'view');
189
190 this.XosKeyboardShortcut.registerKeyBinding({
191 key: 'Delete',
192 cb: () => {
193 if (this.tableCfg.selectedRow < 0) {
194 return;
195 }
196 const deleteFn = _.find(this.tableCfg.actions, {label: 'delete'});
197 deleteFn.cb(this.tableCfg.filteredData[this.tableCfg.selectedRow]);
198 },
199 description: 'View details of selected item'
200 }, 'view');
201
Matteo Scandolo04f487c2017-09-12 10:37:48 -0700202 this.subscription = this.store.query(this.data.model)
203 .subscribe(
204 (event) => {
205 // NOTE Observable mess with $digest cycles, we need to schedule the expression later
206 $scope.$evalAsync(() => {
207 this.tableData = event;
208 });
209 }
210 );
Matteo Scandoloee655a12016-12-19 15:38:43 -0800211 }
Matteo Scandolo9f87f302016-12-13 18:11:10 -0800212 }
Matteo Scandolo00d97892016-12-23 17:53:12 -0800213
Matteo Scandolo04f487c2017-09-12 10:37:48 -0700214 $onDestroy() {
Matteo Scandolo39e04152017-11-29 14:24:45 -0800215 if (this.subscription) {
216 this.subscription.unsubscribe();
217 }
Matteo Scandolo04f487c2017-09-12 10:37:48 -0700218 this.$log.info(`[XosCrud] Destroying component`);
219 }
220
Matteo Scandoloc8a58c82017-08-17 17:14:38 -0700221 public iterateItems() {
222 const rowCount = this.tableCfg.filteredData.length > this.tableCfg.pagination.pageSize ? this.tableCfg.pagination.pageSize : this.tableCfg.filteredData.length;
223 if ((this.tableCfg.selectedRow + 1) < rowCount) {
224 this.tableCfg.selectedRow++;
225 }
226 else {
227 this.tableCfg.selectedRow = 0;
228 }
229 this.$scope.$apply();
230 }
Matteo Scandolo5d962a32017-08-01 18:16:14 -0700231
232 public getRelatedItemId(relation: IXosModelRelation, item: any): boolean {
233 return this.XosCrudRelation.existsRelatedItem(relation, item);
234 }
235
236 public getHumanReadableOnField(r: IXosModelRelation) {
237 return this.XosCrudRelation.getHumanReadableOnField(r, this.data.model);
238 }
239
Matteo Scandolo39e04152017-11-29 14:24:45 -0800240 private _getRelatedModels(relations: {manytoone: IXosModelRelation[], onetomany: IXosModelRelation[]}, item: any) {
Matteo Scandoloa4718592017-08-10 14:54:51 -0700241 this.$log.debug(`[XosCrud] Managing relation for ${this.data.model}:`, relations);
Matteo Scandolo5d962a32017-08-01 18:16:14 -0700242
243 // loading many to one relations (you'll get a model)
244 _.forEach(relations.manytoone, (r: IXosModelRelation) => {
245 if (!item || !item[`${r.on_field.toLowerCase()}_id`]) {
246 return;
247 }
248
249 this.$log.debug(`[XosCrud] Loading manytoone relation with ${r.model} on ${r.on_field}`);
250
251 if (!angular.isDefined(this.relatedModels.manytoone[r.model])) {
252 this.relatedModels.manytoone[r.model] = {};
253 }
254
255 this.XosCrudRelation.getModel(r, item[`${r.on_field.toLowerCase()}_id`])
256 .then(res => {
257 this.relatedModels.manytoone[r.model][r.on_field] = res;
258 })
259 .catch(err => {
260 this.$log.error(`[XosCrud] Error loading manytoone relation with ${r.model} on ${r.on_field}`, err);
261 });
262 });
263
264 // loading onetomany relations (you'll get a list of models)
265 _.forEach(relations.onetomany, (r: IXosModelRelation) => {
266 if (!item) {
267 return;
268 }
269
270 this.$log.debug(`[XosCrud] Loading onetomany relation with ${r.model} on ${r.on_field}`);
271
272 if (!angular.isDefined(this.relatedModels.onetomany[r.model])) {
273 this.relatedModels.onetomany[r.model] = {};
274 }
275
276 this.XosCrudRelation.getModels(r, item.id)
277 .then(res => {
278 this.relatedModels.onetomany[r.model][r.on_field] = res;
279 })
280 .catch(err => {
281 this.$log.error(`[XosCrud] Error loading onetomany relation with ${r.model} on ${r.on_field}`, err);
282 });
283 });
Matteo Scandolo00d97892016-12-23 17:53:12 -0800284 }
Matteo Scandolo9f87f302016-12-13 18:11:10 -0800285}
286
287export const xosCrud: angular.IComponentOptions = {
288 template: require('./crud.html'),
289 controllerAs: 'vm',
290 controller: CrudController
291};