blob: 325790f7fb3d42e8140bcc5168ed9d9e5de75473 [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 Scandolo1aee1982017-02-17 08:33:23 -080025import {IXosModelDiscovererService} 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 Scandolo1aee1982017-02-17 08:33:23 -080030
31export interface IXosModelRelation {
32 model: string;
33 type: string;
Matteo Scandolo5d962a32017-08-01 18:16:14 -070034 on_field: string;
Matteo Scandolo9f87f302016-12-13 18:11:10 -080035}
36
37class CrudController {
Matteo Scandolo1aee1982017-02-17 08:33:23 -080038 static $inject = [
39 '$scope',
Matteo Scandolo5d962a32017-08-01 18:16:14 -070040 '$log',
Matteo Scandolo1aee1982017-02-17 08:33:23 -080041 '$state',
42 '$stateParams',
43 'XosModelStore',
44 'ConfigHelpers',
45 'ModelRest',
46 'StoreHelpers',
Matteo Scandolo5d962a32017-08-01 18:16:14 -070047 'XosModelDiscoverer',
Matteo Scandoloc8a58c82017-08-17 17:14:38 -070048 'XosCrudRelation',
49 'XosDebug',
50 'XosKeyboardShortcut'
Matteo Scandolo1aee1982017-02-17 08:33:23 -080051 ];
Matteo Scandolo9f87f302016-12-13 18:11:10 -080052
Matteo Scandolo5d962a32017-08-01 18:16:14 -070053 // bindings
54
Matteo Scandolo1aee1982017-02-17 08:33:23 -080055 public data: {model: string};
Matteo Scandolo9f87f302016-12-13 18:11:10 -080056 public tableCfg: IXosTableCfg;
Matteo Scandoloee655a12016-12-19 15:38:43 -080057 public formCfg: any;
Matteo Scandoloee655a12016-12-19 15:38:43 -080058 public baseUrl: string;
59 public list: boolean;
Matteo Scandolo9f87f302016-12-13 18:11:10 -080060 public title: string;
Matteo Scandolo9f87f302016-12-13 18:11:10 -080061 public tableData: any[];
Matteo Scandoloee655a12016-12-19 15:38:43 -080062 public model: any;
Matteo Scandolo5d962a32017-08-01 18:16:14 -070063 public related: {manytoone: IXosModelRelation[], onetomany: IXosModelRelation[]} = {
64 manytoone: [],
65 onetomany: []
66 };
67 public relatedModels: {manytoone: any, onetomany: any} = {
68 manytoone: {},
69 onetomany: {}
70 };
Matteo Scandoloc8a58c82017-08-17 17:14:38 -070071 public debugTab: boolean;
Matteo Scandolo9f87f302016-12-13 18:11:10 -080072
73 constructor(
Matteo Scandolof2c3ed62016-12-15 14:32:50 -080074 private $scope: angular.IScope,
Matteo Scandolo5d962a32017-08-01 18:16:14 -070075 private $log: angular.ILogService,
Matteo Scandoloee655a12016-12-19 15:38:43 -080076 private $state: angular.ui.IStateService,
77 private $stateParams: ng.ui.IStateParamsService,
Matteo Scandolo47860fe2017-02-02 12:05:55 -080078 private store: IXosModelStoreService,
Matteo Scandolo80c3a652017-01-06 10:48:31 -080079 private ConfigHelpers: IXosConfigHelpersService,
Matteo Scandolo04964232017-01-07 12:53:46 -080080 private ModelRest: IXosResourceService,
Matteo Scandolo1aee1982017-02-17 08:33:23 -080081 private StoreHelpers: IStoreHelpersService,
Matteo Scandolo5d962a32017-08-01 18:16:14 -070082 private XosModelDiscovererService: IXosModelDiscovererService,
Matteo Scandoloc8a58c82017-08-17 17:14:38 -070083 private XosCrudRelation: IXosCrudRelationService,
84 private XosDebug: IXosDebugService,
85 private XosKeyboardShortcut: IXosKeyboardShortcutService
Matteo Scandolo9f87f302016-12-13 18:11:10 -080086 ) {
Matteo Scandoloa4718592017-08-10 14:54:51 -070087 this.$log.info('[XosCrud] Setup', $state.current.data);
Matteo Scandolo5d962a32017-08-01 18:16:14 -070088
Matteo Scandolo9f87f302016-12-13 18:11:10 -080089 this.data = this.$state.current.data;
Matteo Scandolo1aee1982017-02-17 08:33:23 -080090 this.model = this.XosModelDiscovererService.get(this.data.model);
Matteo Scandolod58d5042016-12-16 16:59:21 -080091 this.title = this.ConfigHelpers.pluralize(this.data.model);
Matteo Scandolo9f87f302016-12-13 18:11:10 -080092
Matteo Scandoloee655a12016-12-19 15:38:43 -080093 this.list = true;
Matteo Scandolo1aee1982017-02-17 08:33:23 -080094
95 // TODO get the proper URL from model discoverer
96 this.baseUrl = '#/' + this.model.clientUrl.replace(':id?', '');
97
Matteo Scandolo1aee1982017-02-17 08:33:23 -080098 this.tableCfg = this.model.tableCfg;
99 this.formCfg = this.model.formCfg;
Matteo Scandoloee655a12016-12-19 15:38:43 -0800100
Matteo Scandoloc8a58c82017-08-17 17:14:38 -0700101 this.debugTab = this.XosDebug.status.modelsTab;
102 this.$scope.$on('xos.debug.status', (e, status: IXosDebugStatus) => {
103 this.debugTab = status.modelsTab;
104 this.$scope.$apply();
105 });
106
Matteo Scandolof2c3ed62016-12-15 14:32:50 -0800107 this.store.query(this.data.model)
Matteo Scandolo035c5932016-12-14 09:55:15 -0800108 .subscribe(
109 (event) => {
110 // NOTE Observable mess with $digest cycles, we need to schedule the expression later
111 $scope.$evalAsync(() => {
Matteo Scandolod58d5042016-12-16 16:59:21 -0800112 this.title = this.ConfigHelpers.pluralize(this.data.model, event.length);
Matteo Scandolo035c5932016-12-14 09:55:15 -0800113 this.tableData = event;
Matteo Scandoloee655a12016-12-19 15:38:43 -0800114
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800115 // if it is a detail page for an existing model
116 if ($stateParams['id'] && $stateParams['id'] !== 'add') {
Matteo Scandolo5d962a32017-08-01 18:16:14 -0700117 this.related.onetomany = _.filter($state.current.data.relations, {type: 'onetomany'});
118 this.related.manytoone = _.filter($state.current.data.relations, {type: 'manytoone'});
Matteo Scandoloee655a12016-12-19 15:38:43 -0800119 this.model = _.find(this.tableData, {id: parseInt($stateParams['id'], 10)});
Matteo Scandolo5d962a32017-08-01 18:16:14 -0700120 this.getRelatedModels(this.related, this.model);
Matteo Scandoloee655a12016-12-19 15:38:43 -0800121 }
Matteo Scandolo035c5932016-12-14 09:55:15 -0800122 });
123 }
124 );
Matteo Scandoloee655a12016-12-19 15:38:43 -0800125
126 // if it is a detail page
127 if ($stateParams['id']) {
128 this.list = false;
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800129
130 // if it is the create page
131 if ($stateParams['id'] === 'add') {
132 // generate a resource for an empty model
Matteo Scandolo47c53fc2017-03-23 14:11:32 -0700133 const endpoint = this.XosModelDiscovererService.getApiUrlFromModel(this.XosModelDiscovererService.get(this.data.model));
Matteo Scandolo80c3a652017-01-06 10:48:31 -0800134 const resource = this.ModelRest.getResource(endpoint);
135 this.model = new resource({});
136 }
Matteo Scandoloc8a58c82017-08-17 17:14:38 -0700137
138 this.XosKeyboardShortcut.registerKeyBinding({
139 key: 'A',
140 cb: () => this.XosDebug.toggleDebug('modelsTab'),
141 description: 'Toggle Debug tab in model details view'
142 }, 'view');
143
144 this.XosKeyboardShortcut.registerKeyBinding({
145 key: 'delete',
146 cb: () => {
147 this.$state.go(this.$state.current.name, {id: null});
148 },
149 description: 'Go back to the list view'
150 }, 'view');
151 }
152 // list page
153 else {
154 this.tableCfg.selectedRow = -1;
155
156 this.XosKeyboardShortcut.registerKeyBinding({
157 key: 'Tab',
158 cb: () => this.iterateItems(),
159 description: 'Iterate trough items in the list'
160 }, 'view');
161
162 this.XosKeyboardShortcut.registerKeyBinding({
163 key: 'Enter',
164 cb: () => {
165 if (this.tableCfg.selectedRow < 0) {
166 return;
167 }
168 this.$state.go(this.$state.current.name, {id: this.tableCfg.filteredData[this.tableCfg.selectedRow].id});
169 },
170 description: 'View details of selected item'
171 }, 'view');
172
173 this.XosKeyboardShortcut.registerKeyBinding({
174 key: 'Delete',
175 cb: () => {
176 if (this.tableCfg.selectedRow < 0) {
177 return;
178 }
179 const deleteFn = _.find(this.tableCfg.actions, {label: 'delete'});
180 deleteFn.cb(this.tableCfg.filteredData[this.tableCfg.selectedRow]);
181 },
182 description: 'View details of selected item'
183 }, 'view');
184
185 // FIXME XosKeyboardShortcut modifiers does not look to work
186 // this.XosKeyboardShortcut.registerKeyBinding({
187 // key: 'Tab',
188 // modifiers: ['alt'],
189 // cb: () => {
190 // this.tableCfg.selectedRow = -1;
191 // },
192 // description: 'Clear selected item'
193 // }, 'view');
Matteo Scandoloee655a12016-12-19 15:38:43 -0800194 }
Matteo Scandolo9f87f302016-12-13 18:11:10 -0800195 }
Matteo Scandolo00d97892016-12-23 17:53:12 -0800196
Matteo Scandoloc8a58c82017-08-17 17:14:38 -0700197 public iterateItems() {
198 const rowCount = this.tableCfg.filteredData.length > this.tableCfg.pagination.pageSize ? this.tableCfg.pagination.pageSize : this.tableCfg.filteredData.length;
199 if ((this.tableCfg.selectedRow + 1) < rowCount) {
200 this.tableCfg.selectedRow++;
201 }
202 else {
203 this.tableCfg.selectedRow = 0;
204 }
205 this.$scope.$apply();
206 }
Matteo Scandolo5d962a32017-08-01 18:16:14 -0700207
208 public getRelatedItemId(relation: IXosModelRelation, item: any): boolean {
209 return this.XosCrudRelation.existsRelatedItem(relation, item);
210 }
211
212 public getHumanReadableOnField(r: IXosModelRelation) {
213 return this.XosCrudRelation.getHumanReadableOnField(r, this.data.model);
214 }
215
216 public getRelatedModels(relations: {manytoone: IXosModelRelation[], onetomany: IXosModelRelation[]}, item: any) {
Matteo Scandoloa4718592017-08-10 14:54:51 -0700217 this.$log.debug(`[XosCrud] Managing relation for ${this.data.model}:`, relations);
Matteo Scandolo5d962a32017-08-01 18:16:14 -0700218
219 // loading many to one relations (you'll get a model)
220 _.forEach(relations.manytoone, (r: IXosModelRelation) => {
221 if (!item || !item[`${r.on_field.toLowerCase()}_id`]) {
222 return;
223 }
224
225 this.$log.debug(`[XosCrud] Loading manytoone relation with ${r.model} on ${r.on_field}`);
226
227 if (!angular.isDefined(this.relatedModels.manytoone[r.model])) {
228 this.relatedModels.manytoone[r.model] = {};
229 }
230
231 this.XosCrudRelation.getModel(r, item[`${r.on_field.toLowerCase()}_id`])
232 .then(res => {
233 this.relatedModels.manytoone[r.model][r.on_field] = res;
234 })
235 .catch(err => {
236 this.$log.error(`[XosCrud] Error loading manytoone relation with ${r.model} on ${r.on_field}`, err);
237 });
238 });
239
240 // loading onetomany relations (you'll get a list of models)
241 _.forEach(relations.onetomany, (r: IXosModelRelation) => {
242 if (!item) {
243 return;
244 }
245
246 this.$log.debug(`[XosCrud] Loading onetomany relation with ${r.model} on ${r.on_field}`);
247
248 if (!angular.isDefined(this.relatedModels.onetomany[r.model])) {
249 this.relatedModels.onetomany[r.model] = {};
250 }
251
252 this.XosCrudRelation.getModels(r, item.id)
253 .then(res => {
254 this.relatedModels.onetomany[r.model][r.on_field] = res;
255 })
256 .catch(err => {
257 this.$log.error(`[XosCrud] Error loading onetomany relation with ${r.model} on ${r.on_field}`, err);
258 });
259 });
Matteo Scandolo00d97892016-12-23 17:53:12 -0800260 }
Matteo Scandolo9f87f302016-12-13 18:11:10 -0800261}
262
263export const xosCrud: angular.IComponentOptions = {
264 template: require('./crud.html'),
265 controllerAs: 'vm',
266 controller: CrudController
267};