2 * Report template builder
5 angular.module('egReporter',
6 ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egGridMod', 'egReportMod', 'treeControl', 'ngToast'])
8 .config(['ngToastProvider', function(ngToastProvider) {
9 ngToastProvider.configure({
10 verticalPosition: 'bottom',
15 .config(function($routeProvider, $locationProvider, $compileProvider) {
16 $locationProvider.html5Mode(true);
17 $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|mailto|blob):/); // grid export
19 var resolver = {delay : function(egStartup) {return egStartup.go()}};
21 $routeProvider.when('/reporter/template/clone/:folder/:id', {
22 templateUrl: './reporter/t_edit_template',
23 controller: 'ReporterTemplateEdit',
27 $routeProvider.when('/reporter/legacy/template/clone/:folder/:id', {
28 templateUrl: './reporter/t_legacy',
29 controller: 'ReporterTemplateLegacy',
33 $routeProvider.when('/reporter/template/new/:folder', {
34 templateUrl: './reporter/t_edit_template',
35 controller: 'ReporterTemplateEdit',
39 $routeProvider.when('/reporter/legacy/main', {
40 templateUrl: './reporter/t_legacy',
41 controller: 'ReporterTemplateLegacy',
46 $routeProvider.otherwise({redirectTo : '/reporter/legacy/main'});
50 * controller for legacy template stuff
52 .controller('ReporterTemplateLegacy',
53 ['$scope','$routeParams','$location','egCore',
54 function($scope , $routeParams , $location , egCore) {
56 var template_id = $routeParams.id;
57 var folder_id = $routeParams.folder;
59 $scope.rurl = '/reports/oils_rpt.xhtml?ses=' + egCore.auth.token();
62 $scope.rurl = '/reports/oils_rpt_builder.xhtml?ses=' +
63 egCore.auth.token() + '&folder=' + folder_id;
65 if (template_id) $scope.rurl += '&ct=' + template_id;
71 * Uber-controller for template editing
73 .controller('ReporterTemplateEdit',
74 ['$scope','$q','$routeParams','$location','$timeout','$window','egCore','$uibModal','egPromptDialog',
75 'egGridDataProvider','egReportTemplateSvc','$uibModal','egConfirmDialog','egSelectDialog','ngToast',
76 function($scope , $q , $routeParams , $location , $timeout , $window, egCore , $uibModal , egPromptDialog ,
77 egGridDataProvider , egReportTemplateSvc , $uibModal , egConfirmDialog , egSelectDialog , ngToast) {
79 function values(o) { return Object.keys(o).map(function(k){return o[k]}) };
81 var template_id = $routeParams.id;
82 var folder_id = $routeParams.folder;
84 $scope.grid_display_fields_provider = egGridDataProvider.instance({
85 get : function (offset, count) {
86 return this.arrayNotifier(egReportTemplateSvc.display_fields, offset, count);
89 $scope.grid_filter_fields_provider = egGridDataProvider.instance({
90 get : function (offset, count) {
91 return this.arrayNotifier(egReportTemplateSvc.filter_fields, offset, count);
95 var dgrid = $scope.display_grid_controls = {};
96 var fgrid = $scope.filter_grid_controls = {};
98 var default_filter_obj = {
100 label : egReportTemplateSvc.Filters['='].label
103 var default_transform_obj = {
105 label : egReportTemplateSvc.Transforms.Bare.label,
109 function mergePaths (items) {
112 items.forEach(function (item) {
116 item.path.forEach(function (p, i, a) {
117 var alias; // unpredictable hashes are fine for intermediate tables
119 if (i) { // not at the top of the tree
120 if (i == 1) join_path = join_path.split('-')[0];
122 var uplink = p.uplink.name;
123 join_path += '-' + uplink;
124 alias = hex_md5(join_path);
126 var uplink_alias = uplink + '-' + alias;
128 if (!t.join) t.join = {};
129 if (!t.join[uplink_alias]) t.join[uplink_alias] = {};
131 t = t.join[uplink_alias];
133 var djtype = 'inner';
134 if (p.uplink.reltype != 'has_a') djtype = 'left';
136 t.type = p.jtype || djtype;
137 t.key = p.uplink.key;
139 join_path = p.classname + '-' + p.classname;
140 alias = hex_md5(join_path);
143 if (!t.alias) t.alias = alias;
146 t.table = p.struct.source ? p.struct.source : p.table;
147 t.idlclass = p.classname;
149 if (a.length == i + 1) { // end of the path array, need a predictable hash
150 t.label = item.path_label;
151 t.alias = hex_md5(item.path_label);
159 // expose for testing
160 $scope._mergePaths = mergePaths;
162 $scope.constructTemplate = function () {
163 var param_counter = 0;
166 doc_url : $scope.templateDocURL,
167 core_class : egCore.idl.classTree.top.classname,
168 select : dgrid.allItems().map(function (i) {
171 path : i.path[i.path.length - 1].classname + '-' + i.name,
172 field_doc : i.doc_text,
173 relation : hex_md5(i.path_label),
176 transform : i.transform ? i.transform.transform : '',
177 transform_label : i.transform ? i.transform.label : '',
178 aggregate : !!i.transform.aggregate
182 from : mergePaths( dgrid.allItems().concat(fgrid.allItems()) ),
183 where : fgrid.allItems().filter(function(i) {
184 return !i.transform.aggregate;
185 }).map(function (i) {
188 i.operator.op == 'is' ||
189 i.operator.op == 'is not' ||
190 i.operator.op == 'is blank' ||
191 i.operator.op == 'is not blank'
193 cond[i.operator.op] = null;
195 if (i.value === undefined) {
196 cond[i.operator.op] = '::P' + param_counter++;
198 cond[i.operator.op] = i.value;
203 path : i.path[i.path.length - 1].classname + '-' + i.name,
204 field_doc : i.doc_text,
205 relation : hex_md5(i.path_label),
208 transform : i.transform.transform,
209 transform_label : i.transform.label,
212 condition : cond // constructed above
215 having : fgrid.allItems().filter(function(i) {
216 return !!i.transform.aggregate;
217 }).map(function (i) {
219 cond[i.operator.op] = '::P' + param_counter++;
222 path : i.path[i.path.length - 1].classname + '-' + i.name,
223 field_doc : i.doc_text,
224 relation : hex_md5(i.path_label),
227 transform : i.transform.transform,
228 transform_label : i.transform.label,
231 condition : cond // constructed above
234 display_cols: angular.copy( dgrid.allItems() ).map(strip_item),
235 filter_cols : angular.copy( fgrid.allItems() ).map(strip_item)
238 function strip_item (i) {
240 i.path.forEach(function(p){
244 delete p.struct.permacrud;
245 delete p.struct.field_map;
246 delete p.struct.fields;
253 function loadTemplate () {
254 if (!template_id) return;
255 egCore.pcrud.retrieve( 'rt', template_id)
256 .then( function(template) {
257 template.data = angular.fromJson(template.data());
258 if (template.data.version < 5) { // redirect to old editor...
259 $window.location.href = egCore.env.basePath + 'reporter/legacy/template/clone/'+folder_id + '/' + template_id;
260 // } else if (template.data.version < 5) { // redirect to old editor...
262 $scope.templateName = template.name() + ' (clone)';
263 $scope.templateDescription = template.description();
264 $scope.templateDocURL = template.data.doc_url;
266 $scope.changeCoreSource( template.data.core_class );
268 egReportTemplateSvc.display_fields = template.data.display_cols;
269 egReportTemplateSvc.filter_fields = template.data.filter_cols;
280 $scope.saveTemplate = function () {
281 var tmpl = new egCore.idl.rt();
282 tmpl.name( $scope.templateName );
283 tmpl.description( $scope.templateDescription );
284 tmpl.owner(egCore.auth.user().id());
285 tmpl.folder(folder_id);
286 tmpl.data(angular.toJson($scope.constructTemplate()));
288 egConfirmDialog.open(tmpl.name(), egCore.strings.TEMPLATE_CONF_CONFIRM_SAVE,
290 return egCore.pcrud.create( tmpl )
293 ngToast.create(egCore.strings.TEMPLATE_CONF_SUCCESS_SAVE);
296 $window.location.href = egCore.env.basePath + 'reporter/legacy/main';
302 ngToast.warning(egCore.strings.TEMPLATE_CONF_FAIL_SAVE);
309 $scope.addDisplayFields = function () {
310 var t = $scope.selected_transform;
311 if (!t) t = default_transform_obj;
313 egReportTemplateSvc.addFields(
315 $scope.selected_source_field_list,
317 $scope.currentPathLabel,
323 $scope.addFilterFields = function () {
324 var t = $scope.selected_transform;
325 if (!t) t = default_transform_obj;
326 f = default_filter_obj;
328 egReportTemplateSvc.addFields(
330 $scope.selected_source_field_list,
332 $scope.currentPathLabel,
339 $scope.moveDisplayFieldUp = function (items) {
340 items.reverse().forEach(function(item) {
341 egReportTemplateSvc.moveFieldUp('display_fields', item);
346 $scope.moveDisplayFieldDown = function (items) {
347 items.forEach(function(item) {
348 egReportTemplateSvc.moveFieldDown('display_fields', item);
353 $scope.removeDisplayField = function (items) {
354 items.forEach(function(item) {egReportTemplateSvc.removeField('display_fields', item)});
358 $scope.changeDisplayLabel = function (items) {
359 items.forEach(function(item) {
360 egPromptDialog.open(egCore.strings.TEMPLATE_CONF_PROMPT_CHANGE, item.label || '',
361 {ok : function(value) {
362 if (value) egReportTemplateSvc.display_fields[item.index].label = value;
369 $scope.changeDisplayFieldDoc = function (items) {
370 items.forEach(function(item) {
371 egPromptDialog.open(egCore.strings.TEMPLATE_FIELD_DOC_PROMPT_CHANGE, item.doc_text || '',
372 {ok : function(value) {
373 if (value) egReportTemplateSvc.display_fields[item.index].doc_text = value;
380 $scope.changeFilterFieldDoc = function (items) {
381 items.forEach(function(item) {
382 egPromptDialog.open(egCore.strings.TEMPLATE_FIELD_DOC_PROMPT_CHANGE, item.doc_text || '',
383 {ok : function(value) {
384 if (value) egReportTemplateSvc.filter_fields[item.index].doc_text = value;
391 $scope.changeFilterValue = function (items) {
392 items.forEach(function(item) {
394 egPromptDialog.open(egCore.strings.TEMPLATE_CONF_DEFAULT, item.value || '',
395 {ok : function(value) {
396 if (value) egReportTemplateSvc.filter_fields[item.index].value = value;
403 $scope.changeTransform = function (items) {
408 angular.forEach(egReportTemplateSvc.Transforms, function (o,n) {
409 if ( o.datatype.indexOf(f.datatype) > -1) {
410 if (tlist.indexOf(o.label) == -1) tlist.push( o.label );
414 items.forEach(function(item) {
416 egCore.strings.SELECT_TFORM, tlist, item.transform.label,
417 {ok : function(value) {
419 var t = egReportTemplateSvc.getTransformByLabel(value);
423 aggregate : egReportTemplateSvc.Transforms[t].aggregate ? true : false
433 $scope.changeOperator = function (items) {
436 Object.keys(egReportTemplateSvc.Filters).forEach(function(k){
437 var v = egReportTemplateSvc.Filters[k];
438 if (flist.indexOf(v.label) == -1) flist.push(v.label);
439 if (v.labels && v.labels.length > 0) {
440 v.labels.forEach(function(l) {
441 if (flist.indexOf(l) == -1) flist.push(l);
446 items.forEach(function(item) {
447 var l = item.operator ? item.operator.label : '';
449 egCore.strings.SELECT_OP, flist, l,
450 {ok : function(value) {
452 var t = egReportTemplateSvc.getFilterByLabel(value);
453 item.operator = { label: value, op : t };
462 $scope.removeFilterValue = function (items) {
463 items.forEach(function(item) {delete egReportTemplateSvc.filter_fields[item.index].value});
467 $scope.removeFilterField = function (items) {
468 items.forEach(function(item) {egReportTemplateSvc.removeField('filter_fields', item)});
472 $scope.allSources = values(egCore.idl.classes).sort( function(a,b) {
473 if (a.core && !b.core) return -1;
474 if (b.core && !a.core) return 1;
475 aname = a.label ? a.label : a.name;
476 bname = b.label ? b.label : b.name;
477 if (aname > bname) return 1;
481 $scope.class_tree = [];
482 $scope.selected_source = null;
483 $scope.selected_source_fields = [];
484 $scope.selected_source_field_list = [];
485 $scope.available_field_transforms = [];
486 $scope.coreSource = null;
487 $scope.coreSourceChosen = false;
488 $scope.currentPathLabel = '';
490 $scope.treeExpand = function (node, expanding) {
491 if (expanding) node.children.map(egCore.idl.classTree.fleshNode);
494 $scope.filterFields = function (n) {
495 return n.virtual ? false : true;
496 // should we hide links?
497 return n.datatype && n.datatype != 'link'
500 $scope.field_tree_opts = {
501 multiSelection: true,
502 equality : function(node1, node2) {
503 return node1.name == node2.name;
507 $scope.field_transforms_tree_opts = {
508 equality : function(node1, node2) {
509 if (!node2) return false;
510 return node1.transform == node2.transform;
514 $scope.selectFields = function () {
515 while ($scope.available_field_transforms.length) {
516 $scope.available_field_transforms.pop();
519 angular.forEach( $scope.selected_source_field_list, function (f) {
520 angular.forEach(egReportTemplateSvc.Transforms, function (o,n) {
521 if ( o.datatype.indexOf(f.datatype) > -1) {
524 angular.forEach($scope.available_field_transforms, function (t) {
525 if (t.transform == n) include = false;
528 if (include) $scope.available_field_transforms.push({
531 aggregate : o.aggregate ? true : false
539 $scope.selectSource = function (node, selected, $path) {
541 while ($scope.selected_source_field_list.length) {
542 $scope.selected_source_field_list.pop();
544 while ($scope.selected_source_fields.length) {
545 $scope.selected_source_fields.pop();
549 $scope.currentPath = angular.copy( $path().reverse() );
550 $scope.selected_source = node;
551 $scope.currentPathLabel = $scope.currentPath.map(function(n,i){
553 if (i) l += ' (' + n.jtype + ')';
556 angular.forEach( node.fields, function (f) {
557 $scope.selected_source_fields.push( f );
560 $scope.currentPathLabel = '';
563 // console.log($scope.selected_source);
566 $scope.changeCoreSource = function (new_core) {
567 console.log('changeCoreSource: '+new_core);
568 function change_core () {
569 if (new_core) $scope.coreSource = new_core;
570 $scope.coreSourceChosen = true;
572 $scope.class_tree.pop();
573 $scope.class_tree.push(
574 egCore.idl.classTree.setTop($scope.coreSource)
577 while ($scope.selected_source_fields.length) {
578 $scope.selected_source_fields.pop();
581 while ($scope.available_field_transforms.length) {
582 $scope.available_field_transforms.pop();
585 $scope.currentPathLabel = '';
588 if ($scope.coreSourceChosen) {
589 egConfirmDialog.open(
590 egCore.strings.FOLDERS_TEMPLATE,
591 egCore.strings.SOURCE_SETUP_CONFIRM_EXIT,