PageSourceSearch

https://cinemaportuguesmemoriale.pt/DesktopModules/MVC/AlgoritmiaAngular/js/APP/Common/DataUI.js?cdv=615

js cinemaportuguesmemoriale.pt collected 2026-10-02 06:19:48 UTC 118,398 bytes, 2,800 lines download raw bytes

1angular.module('DataUI', ["ApiService", "MessagesUI","rzModule"]);
2
3/*Templates*/
4angular.module("DataUI").run(["$templateCache",function ($templateCache) {
5    /*common*/
6    $templateCache.put('uiApiToolbar.html', '<div class="clearfix">  <div class="pull-right"><div ui-include="uiApiToolbarCommands.html"></div></div>  <div class="pull-right" style="margin-right:15px;" ng-show="inProgress">{{inProgressOperation}}</div></div> <div class="progress-bar progress-bar-info progress-bar-striped progress-bar-auto-advance" style="width: 100%; height:3px; margin-top:-3px;" ng-show="inProgress"></div>');
7    $templateCache.put('uiApiToolbarCommands.html', '<div ui-include="uiApiCommand_Add.html"></div>');
8
9    /*Common commands*/
10    $templateCache.put('uiApiCommand_Add.html', '<button type="button" class="btn btn-default btn-sm" ng-click="new()"><i class="fas fa-plus"></i> Add</button>');
11    $templateCache.put('uiApiCommand_Edit.html', '<button type="button" class="btn btn-default btn-sm" ng-click="sendCommand(\'EditRow\', row)"><i class="fas fa-pencil"></i> Edit</button>');
12
13    /*List Templates*/
14    $templateCache.put('uiApiListFull.html', '<div class="list-group">  <div class="list-group-item clearfix" ui-include="uiApiToolbar.html"></div>  <a href="#" class="list-group-item" ng-repeat="row in $data" ng-click="select(row)">{{row[titleField]}}</a>  </div>');
15    $templateCache.put('uiApiList.html', '<div class="list-group"> <a href="#" class="list-group-item" ng-repeat="row in $data" ng-click="select(row)">{{row[titleField]}}</a>  </div>');
16
17
18    /*Side Nav Lists*/
19    $templateCache.put('uiApiNavListFull.html', '<div class="clearfix container-fluid" ui-include="uiApiToolbar.html"></div><ul><li ng-repeat="row in $data" ng-click="select(row)" ng-class="{\'active\': row==selectedRow}" data-toggle="collapse" data-target=".side-nav"><a href="#">{{row[titleField]}}</a></li></ul>');
20    $templateCache.put('uiApiNavList.html', '<ul><li ng-repeat="row in $data" ng-click="select(row)"><a href="#" ng-class="{\'active\': row==selectedRow}">{{row[titleField]}}</a></li></ul>');
21
22    /*Table base templates*/
23    $templateCache.put('uiApiTableFull.html', '<div>  <div ui-include="uiApiTableHeader.html"></div>  <div ui-include="uiApiToolbar.html"></div>  <div ui-include="uiApiTable.html"></div>  <div ui-include="uiApiTableFooter.html"></div>  </div>');
24    $templateCache.put('uiApiTableHeader.html', '');
25    $templateCache.put('uiApiTableFooter.html', '');
26    $templateCache.put('uiApiTable.html', '<table class="table table-hover" ng-table-dynamic="tableParams with columns" show-filter="!listSettings.hideFilters"><tr ng-repeat="row in $data track by row.Id" ng-click="select(row)" ui-include="uiApiTableCell.html"></tr></table>');
27    $templateCache.put('uiApiTableRow.html', '<tr ng-repeat="row in $data track by row.Id" ng-click="select(row)" ui-include="uiApiTableCell.html"></tr>');
28    $templateCache.put('uiApiTableCell.html', '<td ng-repeat="column in $columns" class="{{column.class()}}" ui-include="uiApiTableCellValue.html"></td>');
29    $templateCache.put('uiApiTableCellValue.html', '<span ui-display-field="column" data="row"></span>');
30
31    $templateCache.put('uiApiTableTools.html', '<div class="dropdown"><button class="btn btn-default btn-sm dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"><i class="fas fa-elipsis-v"></i></button><ul class="dropdown-menu dropdown-menu-right "><li><a href="#" ng-click="toggleFilters()"><i class="fas fa-filter"></i> Show / hide filters</a></li></ul></div>');
32
33    /*pagination*/
34    $templateCache.put('uiTablePagination.html', ['' +
35        '<div class="btn-group btn-group-sm" role="group" aria-label="Page"> ' +
36        '<button type="button" class="btn btn-default" aria-label="Previous" ng-if="pagination.showPrev"  ng-click="ChangePage(pagination.page - 1)"><span aria-hidden="true">&laquo;</span></button>'+
37        '<button type="button" class="btn btn-default" aria-label="First"    ng-if="pagination.showFirst" ng-click="ChangePage(pagination.firstPage)">{{(pagination.firstPage + 1)}}</li>'+
38        '<button type="button" class="btn btn-default" ng-repeat="page in pagination.middlePages"         ng-click="ChangePage(page)" ng-class="{\'btn-primary\': pagination.page == page}">{{(page + 1)}}</button>'+
39        '<button type="button" class="btn btn-default" aria-label="Last"     ng-if="pagination.showLast"  ng-click="ChangePage(pagination.lastPage)">{{(pagination.lastPage + 1)}}</button>'+
40        '<button type="button" class="btn btn-default" aria-label="Next"     ng-if="pagination.showNext"  ng-click="ChangePage(pagination.page + 1)"><span aria-hidden="true">&raquo;</span></button>'+
41        '</div >'+
42        ''].join(''));
43
44    $templateCache.put('uiTablePaginationSize.html', ['' +
45        '<div class="btn-group btn-group-sm right" role="group" aria-label="Page navigation size"> ' +
46        '<button type="button" class="btn btn-default" ng-repeat="size in pagination.pagesizes" ng-class="{\'btn-primary\': pagination.pagesize == size}" ng-click="Changepagesize(size)">{{size}}</button>' +
47        '</div >'].join(''));
48
49    /*Pivot Templates*/
50    $templateCache.put('uiPivot.html', [
51        '<div>',
52        '   <div class="row" style="border-bottom: 1px solid #CCC;">',
53        '       <div class="col-xs-12 col-sm-2"></div>',
54        '       <div ng-repeat="(colKey, col) in pivotColumns track by $index" class="{{::colCssClass}} text-center">',
55        '           <h4>{{colKey}}</h4>',
56        '       </div>',
57        '   </div>',
58        '   <div ng-repeat="(rowKey, d1) in pivotData track by $index" class="row" style="border-bottom: 1px solid #CCC;">',
59        '       <div class="col-xs-12 col-sm-2 text-center"><h4>{{rowKey}}</h4></div>',
60        '       <div ng-repeat="(colKey, row) in d1 track by $index" class="{{::colCssClass}} pivotwidgetPlaceholder">',
61        '           <div ng-transclude ng-show="row.hasValue"></div>',
62        '       </div>',
63        '   </div>',
64        '</div>'
65    ].join(''));
66
67    $templateCache.put('uiNestedList.html', [
68        '<div class="list-group-item" ng-click="parentScope.toggle(row)" ng-class="{\'active\':parentScope.isSelected(row)}">',
69        '    {{row.Label || row.Name || row.Title }}',
70        '</div>',
71        '<ul ng-if="row.children.length>0" class="children collapse" id="x-{{row._UniqueId || row.Id}}">',
72        '	<li ng-repeat="row in row.children" ng-include="\'uiNestedList.html\'" ></li>',
73        '</ul>',
74        '<span  ng-if="row.children.length>0" data-toggle="collapse" data-target="#x-{{row._UniqueId || row.Id}}" class="expand-collapse"></span>'
75    ].join(''));
76
77    $templateCache.put('uiFileDropZone.html', [
78        '<div class="drop-text"><span translate="algoapps.DropFileHere">Drag and drop files here to upload</span></div>'
79    ].join(''));
80
81}]);
82
83/*Controller for API List Data*/
84angular.module("DataUI").controller('UIApiList', ["$scope", 'CommonApi', '$rootScope', "$attrs", "$q", "MessagesService", "$timeout", '$interval', '$window', '$translate', 'jqDialogService', 'SendCommandService', 'AlgoDataUtils', function ($scope, CommonApi, $rootScope, $attrs, $q, MessagesService, $timeout, $interval, $window, $translate, jqDialogService, SendCommandService, AlgoDataUtils) {
85    $scope.$data = new Array();
86    $scope.AlgoDataUtils = AlgoDataUtils;
87    //Default settings
88    $scope.listSettings = { hideFilters: false, pagesize: 20, filter: {}, filterOperators: {} };
89    $scope.querySettings = {};
90    $scope.inProgress = false;
91    $scope.dataLoadSeq = 0;
92
93    //If settings sould inherit from parent scope
94    $scope.InheritSettings = ($attrs.uiInheritSettings != "false" && !!$scope.$parent);
95    $scope.SettingsVersion = 0;
96
97    /*Load data - based on listSettings*/
98    var abort;
99    $scope._loadData = function () {
100        var query = buildQUery();
101        
102        if (abort) abort.resolve("Canceled");//Abort previos http request if exists
103        abort = $q.defer();//Create a new abort defer object
104        var deferred = $q.defer();//Create the data completed defer object
105        $scope.inProgress = true; $scope.inProgressOperation = "Loading..."; $scope.dataLoadSeq++;
106        deferred.dataLoadSeq = $scope.dataLoadSeq;
107        var api = CommonApi.all($scope.getApiName()).withHttpConfig({ timeout: abort.promise });
108        $scope.listSettings.listUrl = api.buildUrl((api.action || 'List'), query);
109        //We can disable querying through attribute - usefull when we only need to produce a listUrl.
110        if ($attrs.uiDisableQuerying || $scope.listSettings.disableQuerying) {
111            deferred.resolve();
112        }
113        else {
114
115            var dataLoaded = function (data) {
116                $scope.dataIsLoaded = true;
117                if (data && data.paging) $scope.listSettings.total = data.paging.totalRecordCount;
118
119                var _data = data;
120                if ($attrs.transformResult) {
121                    transformResult = $attrs.transformResult;
122                    _data = _.chain(data).keyBy('Id').value();
123                }
124                $scope.$data = _data;
125                if ($attrs.uiDataVariable) $rootScope[$attrs.uiDataVariable] = _data;
126                deferred.resolve($scope.$data);
127                if ($scope.sendCommand) $scope.sendCommand("DataLoaded", $scope.queryParams);
128                else SendCommandService.sendCommand($scope.apiName, "DataLoaded", $scope.queryParams);
129            };
130            var dataFailed = function (response) {
131                $scope.dataHasError = true;
132                deferred.reject(response);
133                if (response.status >= 0) MessagesService.showError(response);
134            };
135            
136            if ($scope.querySettings.postQuery) {
137                api.customPost((api.action || 'List'), query, $scope.querySettings.postQuery)
138                    .then(dataLoaded, dataFailed);
139            }
140            else {
141                api.getList(query)
142                    .then(dataLoaded, dataFailed);
143            }
144        }
145
146        deferred.promise.finally(function () {
147            //Check if this finallyty is for te current request.
148            if (deferred.dataLoadSeq == $scope.dataLoadSeq) {
149                $scope.inProgress = false;
150                $scope.inProgressOperation = "";
151            }
152            else
153            {
154                //When finallyty is not for te current request, fill progress in 30 sec so it does not lock.
155                $timeout(function () {$scope.inProgress = false;$scope.inProgressOperation = "";}, 30000);
156            }
157            $scope.$applyAsync();
158        });
159
160        return deferred.promise;
161    };
162    $scope.loadData = _.debounce($scope._loadData, 200);
163
164    $scope.export = function (format, params) {
165        var query = buildQUery();
166        query.format = format;
167        //call export API
168        CommonApi
169            .all($scope.querySettings.exportApiName || $scope.getApiName())
170            .download('Export', _.merge({}, query, params), null, 'GET').then(
171            function (data) {
172                scope.inProgress = false;
173            }, function (response) {
174                scope.inProgress = false;
175                MessagesService.showError(response);
176            }
177        );
178    //    var downloadUrl = CommonApi.all($scope.querySettings.exportApiName || $scope.getApiName()).buildUrl('Export', _.merge({}, query, params) );
179    //    $window.open(downloadUrl);
180    }
181
182    //Advanced export
183    $scope.exportAdvanced = function (apiName, apiAction, method, filename, sheetname) {
184        var query = buildQUery();
185        if (sheetname) query.sheetName = sheetname;
186        if (filename) query.fileName = filename;
187        //call export API
188        CommonApi.all(apiName || $scope.querySettings.exportApiName || $scope.apiName).withHttpConfig({ method: (method || 'POST') }).download(apiAction || 'Export', query).then(
189            function (data) {
190                $scope.inProgress = false;
191            }, function (response) {
192                $scope.inProgress = false;
193                MessagesService.showError(response);
194            }
195        );
196    }
197
198    /*Gets list settings merging with defaults and parent settings*/
199    $scope.getListSettings = function (queryOverrides) {
200        //default
201        var r = { pagesize: 20, page: 0, order: new Array(), filter: {}, filterOperators: {}, select: new Array(), groupby: '' };
202
203        //Inherit from parent
204        var p = {}, ps = $scope.$parent, psi = 0;
205        if ($scope.InheritSettings) {
206            while (ps && psi < 90) {
207                if (ps.getListSettings) {
208                    _.mergeWith(p, ps.getListSettings(), function (a, b) { return (_.isArray(b) ? b : undefined); });
209                    break;
210                }
211                else if (ps.listSettings) {
212                    _.mergeWith(p, ps.listSettings, function (a, b) { return (_.isArray(b) ? b : undefined); });
213                }
214                ps = ps.$parent; psi++;
215            }
216        }
217
218        _.mergeWith(r, p, 
219            $scope.listSettings,//from list settings (default overrides)            
220            $scope.overrideSettings, //from list settings (forced values)
221            queryOverrides, //local overrides
222            function (a, b) { return (_.isArray(b) ? b : undefined); }
223        );
224        return r;
225    }
226
227    $scope.getApiName = function () {
228        if ($scope.apiName) return $scope.apiName;
229
230        if ($scope.InheritSettings) {
231            var p = {}, ps = $scope.$parent, psi = 0;
232            while (ps && psi < 90) {
233                if ($scope.$parent.apiName) return $scope.$parent.apiName;
234                if ($scope.$parent.getApiName) return $scope.$parent.getApiName;
235                ps = ps.$parent; psi++;
236            }
237        }
238
239        return '';
240    }
241    
242    var buildQUery = function () {
243        $scope.SettingsVersion++;
244        var prm = $scope.getListSettings();
245        $scope.querySettings = prm;
246
247        var query = AlgoDataUtils.getQueryObject(prm);
248        $scope.query = query;
249        return query;
250    }
251
252    $scope.buildQuery = buildQUery;
253    $scope.$watch('listSettings.filter', $scope.loadData, true);
254    $scope.$watch('listSettings.postQuery', $scope.loadData, true);
255    $scope.$watch('listSettings.order', $scope.loadData, true);
256    $scope.$watch('listSettings.groupby', $scope.loadData, true);
257    $scope.$watch('listSettings.select', $scope.loadData, true);
258    $scope.$watch('listSettings.page', $scope.loadData, true);
259    $scope.$watch('listSettings.pagesize', $scope.loadData, true);
260    $scope.$watch('apiName', $scope.loadData, true);
261
262    //Watch parent filters
263    if ($scope.InheritSettings) {
264        $scope.$parent.$watch('querySettings.filter', $scope.loadData, true);
265        $scope.$parent.$watch('listSettings.filter', $scope.loadData, true);
266        if (!$scope.apiName) $scope.$parent.$watch('apiName', $scope.loadData);
267        $scope.$parent.$watch('listSettings.postQuery', $scope.loadData, true);
268    }
269
270    /*Auto update data on intervals*/
271    if ($attrs.updateInterval) {
272        var delay = parseInt($attrs.updateInterval);
273        if (delay > 0) $interval($scope.loadData, delay*1000);
274    }
275
276    $scope.new = function () {
277        var data = {Id:0};
278        if ($scope.defaultRow) angular.extend(data, $scope.defaultRow);
279        if ($scope.sendCommand) $scope.sendCommand("NewRow", data);
280        else SendCommandService.sendCommand($scope.apiName, "NewRow", data);
281    }
282
283    $scope.select = function (data) {
284        $scope.selectedRow = data;
285        if ($scope.sendCommand) $scope.sendCommand("SelectRow", data);
286        else SendCommandService.sendCommand($scope.apiName, "SelectRow", data);
287    }
288
289    $scope.delete = function (data, what) {
290        MessagesService.confirm($translate.instant('algoapps.ConfirmDelete', { what: what }), $translate.instant('algoapps.Delete')).then(function () {
291            $scope.inProgress = true;
292            $scope.inProgressOperation = "Deleting";
293            CommonApi.all($scope.apiName).customPost('delete', null, data).then(
294                function (data) {
295                    $scope.inProgress = false;
296                    if ($scope.sendCommand) $scope.sendCommand("RowDeleted", $scope.row);
297                    else SendCommandService.sendCommand($scope.apiName, "RowDeleted", $scope.row);
298                    $scope.loadData();
299                    MessagesService.showMessage($translate.instant('algoapps.DeleteSuccess', { what: what }), 'success', 3000);
300                }, function (response) {
301                    $scope.inProgress = false;
302                    MessagesService.showError(response);
303                });
304        }, function () { });
305    }
306
307    $scope.setStatus = function (row, newStatus) {
308        $scope.inProgress = true;
309        var deferred = $q.defer();
310        CommonApi.all($scope.apiName).customPost('UpdateStatus', null, { Id: row.Id, NewStatus: newStatus }).then(
311            function (data) {
312                $scope.inProgress = false;
313                MessagesService.showMessage($translate.instant('algoapps.UpdateSuccess', { what: $translate.instant('algoapps.Status') }), 'success', 3000);
314                row.Status = newStatus;
315                deferred.resolve();
316            },
317            function (error) {
318                $scope.inProgress = false;
319                MessagesService.showError(error);
320                deferred.reject();
321            });
322        return deferred.promise;
323    }
324
325    $timeout(function(){
326        if ($scope.sendCommand) $scope.sendCommand("Ready");
327        else SendCommandService.sendCommand($scope.apiName, "Ready");
328    }, 0);
329
330    var redirectUrl='/';
331    var redirectFn = function (data) {
332        window.location = redirectUrl + data.Id;
333    }; 
334    $scope.createRedirectWithId = function (url) {
335        redirectUrl = url;
336        return redirectFn;
337    }
338
339    $scope.openDetail = function (template, queryOverrides) {
340        var model = {}, scope = $scope.$new();
341        scope.listSettings = $scope.getListSettings(queryOverrides);
342        scope.getListSettings = function () { return scope.listSettings; }
343
344        jqDialogService.open(jqDialogService.generateId(), template, model,
345            { title: '', modal: true, resizable: false, width: 'auto', dialogClass: "modal-auto-height modal-full", parentScope: scope }
346        ).then(function (result) {},function (result) {});
347
348    }
349}]);
350
351
352/*Service for data utilities*/
353angular.module("DataUI").service('AlgoDataUtils', ['$rootScope', '$timeout', function ($rootScope, $timeout) {
354    var svc = {};
355
356    svc.getFiltersString = function (prm) {
357        if (!prm) return '';
358
359        var filtersStr = $.map(prm.filter, function (v, k) {
360            if (!v) return null;
361            if (prm.ignoreFilters && (k == prm.ignoreFilters || (_.isArray(prm.ignoreFilters) && _.find(prm.ignoreFilters, function (f) { return (f == k); })))) return null;//Filter set to be ignored in this context
362            if (v.range) //Compose range filter
363            {
364                if (v.range.from || v.range.to) {
365                    var f = new Array();
366                    if (v.range.from) f.push(k + ':(ge)\'' + v.range.from + '\'');
367                    if (v.range.to) f.push(k + ':(le)\'' + v.range.to + '\'');
368                    return f.join(',');
369                }
370                else {
371                    return null; //range with no values => null
372                }
373            }
374            else if (_.isArray(v)) {
375                //use In clause
376                if (v.length == 0) return null;
377                return k + ':(in)\'' + _.map(v, function (i) { return (_.isUndefined(i.Id) ? i : i.Id); }).join(';') + '\'';
378            }
379            else if (_.startsWith(v, '(')) {
380                //Forced operator
381                var i = v.indexOf(')');
382                if (i !== -1) {
383                    var op = v.slice(0, i);
384                    const rest = v.slice(i + 1);
385                    return k + ':' + op + ')\'' + rest +'\'';
386                } else {
387                    return k + ':\'' + v + '\'';
388                }
389
390
391            }
392            else {
393                v = _.trim(v, '\'');
394                if (prm.filterOperators && prm.filterOperators[k]) {
395                    return v == '' ? null : k + ':' + (prm.filterOperators[k] == '(lk)' ? '' : prm.filterOperators[k]) + '\'' + v + '\'';
396                }
397                else if (!isNaN(v)) {
398                    //Number uses equal
399                    return k + ':(eq)\'' + v + '\'';
400                }
401                else {
402                    //Default Contains filter
403                    return v == '' ? null : k + ':\'' + v + '\'';
404                }
405            }
406        }).join(',');
407
408        return filtersStr;
409    };
410
411    svc.getQueryObject = function (prm) {
412        var query = {
413            pagesize: prm.pagesize,
414            page: prm.page,
415            filters: svc.getFiltersString(prm),
416            groupby: prm.groupby,
417            select: (_.isArray(prm.select) ? prm.select.join(',') : prm.select),
418            orderby: prm.order.join(',')
419        };
420
421        if (prm.groupby && query.select) {
422            //Group by queries (select list except group by column)
423            query.select = _.filter(query.select.split(','), function (o) { return o.toLowerCase() != prm.groupby.toLowerCase(); }).join(',');
424        }
425
426        if (prm.timeout) query.timeout = prm.timeout;
427        query.page = query.page + 1; //Fix zero base pagination:
428
429        return query;
430    };
431
432
433    return svc;
434}]);
435
436
437
438/*Controller for API Row Data*/
439angular.module("DataUI").controller('UIApiRow', ['$scope', 'CommonApi', '$rootScope', '$attrs', 'MessagesService', '$translate', 'SendCommandService', function ($scope, CommonApi, $rootScope, $attrs, MessagesService, $translate, SendCommandService) {
440    $scope.row = {};
441
442    /*Data management actions*/
443    //Load data (data.Id) and use default values if (data.Id==0)
444    var _loadData = function (row) {
445        if (!$scope.apiName) { $scope.loadData(); $scope.inProgress = false; return; };
446        $scope.inProgress = true;
447        $scope.inProgressOperation = 'Loading';
448        var controllerAPI = CommonApi.all($scope.apiName);
449        if (controllerAPI.action == 'List') controllerAPI.action = 'Get';
450        var id = 0;
451        //id can come from various places:
452        if ($scope.row && $scope.row.Id) id = $scope.row.Id;
453        if (row && row.Id) id = row.Id;
454        //If Id is specified in Api params, then it is more important
455        if (/*id == 0 &&*/ controllerAPI.params && controllerAPI.params.Id) id = controllerAPI.params.Id;
456        var scopeVariable = $attrs.uiScopeVar;
457        $scope.row = { Id: id };
458        if (scopeVariable) $scope[scopeVariable] = $scope.row;
459
460        controllerAPI.customGet(controllerAPI.action || 'Get', { Id: id }).then(
461            function (data) {
462                $scope.inProgress = false;
463                //404 redirect
464                if (!data && $attrs.ui404Redirect) window.location = $attrs.ui404Redirect;
465                if (!data) data = { Id: 0 };
466
467                $scope.setRow(data, row);
468            },
469            function (response) {
470                if (response.status == '404' && $attrs.ui404Redirect) {
471                    window.location = $attrs.ui404Redirect;
472                }
473                $scope.inProgress = false;
474                MessagesService.showError(response);
475            }
476        );
477    };
478    $scope.loadData = _.debounce(_loadData, 50);
479    //Load data when DataAPI Changes
480    $scope.$watch('apiName', function (newVal, oldVal) {
481        if (newVal != oldVal) $scope.loadData();
482    });
483
484    $scope.setRow = function (data, defaultData) {
485        $scope.row = data;
486        if (data.Content) {
487            $scope.row = angular.extend({}, data.Content, data);
488            $scope.row.Id = data.Id;
489            $scope.row.Content = null;
490        }
491        if (data.Id == 0) angular.extend($scope.row, defaultData);
492
493        var scopeVariable = $attrs.uiScopeVar;
494        if (scopeVariable) $scope[scopeVariable] = $scope.row;
495
496        if ($scope.sendCommand) $scope.sendCommand('DataLoaded', data);
497        else SendCommandService.sendCommand($scope.apiName, "DataLoaded", data);
498
499        $scope.$applyAsync();
500    }
501
502    $scope.edit = function () {
503        $scope.viewMode = 'edit';
504    }
505
506    $scope.delete = function () {
507        MessagesService.confirm($translate.instant('algoapps.ConfirmDelete', { what: '' }), $translate.instant('algoapps.Delete')).then(function () {
508                $scope.inProgress = true;
509                $scope.inProgressOperation = 'Deleting';
510                $scope.row.remove().then(
511                    function (data) {
512                        $scope.inProgress = false;
513                        if ($scope.sendCommand) $scope.sendCommand('RowDeleted', $scope.row);
514                        else SendCommandService.sendCommand($scope.apiName, "RowDeleted", $scope.row);
515                        MessagesService.showMessage($translate.instant('algoapps.DeleteSuccess', { what: '' }), 'success', 3000);
516                    }, function (response) {
517                        $scope.inProgress = false;
518                        MessagesService.showError(response);
519                    });
520        }, function () { });
521
522        }
523
524}]);
525
526/*Controller for posting multipart form data*/
527angular.module('DataUI').controller('UIMultipartFormData', ['$scope', '$attrs', '$element', 'CommonApi', 'MessagesService', function ($scope, $attrs, $element, CommonApi, MessagesService) {
528    $scope.FormData = {};
529    $scope.inProgress = false;
530    $scope.validation = $scope.validation || {};
531    $scope.isDownload = ($attrs.uiIsDownload == 'true');
532
533    var _post = function (isDownload) {
534        if ($scope.inProgress || $scope.validation.$invalid) return;
535        $scope.inProgress = true;
536
537        var _api = CommonApi.createApi($attrs.uiApiName);
538        var formData = new FormData();
539        //Prepare files
540        $($element).find('input[type="file"]').each(function(i, el){
541            _.forEach(el.files, function (file) {
542                var filename = file.name;
543                formData.append('file', file, filename);
544            });
545        });
546
547        //Prepare model
548        formData.append('model', JSON.stringify($scope.FormData));
549
550        //Post the data
551        if ($scope.isDownload) {
552            _api.withHttpConfig({ transformRequest: angular.identity, headers: { 'Content-Type': undefined } })
553                .download(_api.action, null, formData)
554                .then(successCallback, errorCallback);
555        }
556        else {
557            _api.withHttpConfig({ transformRequest: angular.identity, headers: { 'Content-Type': undefined } })
558                .customPost(_api.action, null, formData)
559                .then(successCallback, errorCallback);
560        }
561
562    }
563    var successCallback = function (data) {
564        $scope.inProgress = false;
565        MessagesService.showMessage('Sucesso.', 'success', 5000);
566    };
567    var errorCallback = function (response) {
568        $scope.inProgress = false;
569        MessagesService.showError(response);
570    };
571
572    var post = _.debounce(_post, 50);
573    $scope.post = function () { post(); }
574
575}]);
576
577/*Directive to get row.id from URL*/
578angular.module("DataUI").directive('uiUrlPattern', ['$timeout', '$location', function ($timeout, $location) {
579    return {
580        restrict: 'EA',
581        priority: 1000,
582        scope: {
583            ngModel: '=ngModel',
584            urlPattern: '@uiUrlPattern',
585            loadDataCallback: '&uiLoadData'
586        },
587        link: function (scope, element, attrs) {
588
589            var _evalPattern = function () {
590                if (scope.urlPattern) {
591                    ///*Find procid in url*/
592                    //var regexp = /\/procId\/(\d+)/g;
593                    var regexp = new RegExp(scope.urlPattern + '(?:/)?', "gi");
594                    var loc = $location.absUrl(), match;
595                    while ((match = regexp.exec(loc)) != null) {
596                        if (match && match.length > 1) scope.ngModel = match[1];
597                    }
598                }
599                else {
600                    loadData();
601                }
602            };
603
604            var _loadData = function () {
605                if (scope.urlPattern && scope.loadDataCallback) scope.loadDataCallback()();
606            };
607            var loadData = _.debounce(_loadData, 100);
608            scope.$watch("ngModel", function (newVal, oldVal) {
609                if (newVal != oldVal) loadData();
610            });
611
612            //Eval on init
613            $timeout(_evalPattern, 0);
614        }
615    };
616}]);
617
618
619
620/*Save button with API connection:*/
621angular.module("DataUI").directive('uiGetItemApi', ['CommonApi', 'MessagesService', function (CommonApi, MessagesService) {
622    return {
623        restrict: 'EA',
624        priority: 1000,
625        scope: {
626            apiName: '@uiGetItemApi',
627            data: '=uiData',
628            contentMap:'@?uiDataContent'
629        },
630        link: function (scope, element, attrs) {
631
632            var _loadData = function () {
633                scope.inProgress = true;
634                scope.inProgressOperation = "Loading";
635                CommonApi.all(scope.apiName).get(scope.data.Id).then(
636                    function (data) {
637                        scope.inProgress = false;
638                        _.extend(scope.data, data);
639                    },
640                    function (response) {
641                        scope.inProgress = false;
642                        MessagesService.showError(response);
643                    }
644                );
645            }
646            scope.loadData = _.debounce(_loadData, 100);
647            scope.$watch('data.Id', scope.loadData);
648            scope.$on('loadData', scope.loadData);
649        }
650    };
651}]);
652
653/*Save button with API connection:*/
654angular.module("DataUI").directive('uiSaveApi', ['CommonApi', 'MessagesService', '$translate', function (CommonApi, MessagesService, $translate) {
655    return {
656        restrict: 'EA',
657        priority: 1000,
658        scope: {
659            apiName: '@uiSaveApi',
660            apiAction: '@uiSaveApiAction',
661            data: '=data',
662            onSuccess: "=?onSuccess",
663            validation: "=?uiValidationState",
664            contentMap: '@?uiDataContent'
665        },
666        transclude:true,
667        replace:true,
668        template: '<a href ng-click="save()" ng-disabled="validation.$invalid || inProgress" ng-transclude=""><span translate="algoapps.Save">Save</span></a>',
669        link: function (scope, element, attrs) {
670
671            var _save = function () {
672                if (scope.inProgress || scope.validation.$invalid) return;
673                scope.inProgress = true;
674                var api = CommonApi.all(attrs.uiSaveApi).customPost(scope.apiAction || 'createOrUpdate', null, scope.data).then(
675                    function (data) {
676                        scope.inProgress = false;
677                        if (data) angular.extend(scope.data, data);
678                        MessagesService.showMessage(attrs.successMessage || $translate.instant('algoapps.UpdateSuccess', { what: '' }), 'success', 3000);
679                        if (scope.onSuccess) scope.onSuccess(scope.data);
680                    }, function (response) {
681                        scope.inProgress = false;
682                        MessagesService.showError(response);
683                    });
684            }
685            var save = _.debounce(_save, 50);
686            scope.save = function () { save(); }
687            
688        }
689    };
690}]);
691
692/*Save button with API connection:*/
693angular.module("DataUI").directive('uiCallApi', ['CommonApi', 'MessagesService', '$translate', 'SendCommandService', function (CommonApi, MessagesService, $translate, SendCommandService) {
694    return {
695        restrict: 'EA',
696        priority: 1000,
697        scope: {
698            apiName: '@uiCallApi',
699            apiAction: '@uiApiAction',
700            isPost:'@uiIsPost',
701            data: '=uiData',
702            urlData: '=uiUrlData',
703            isDownload: '@uiIsDownload',
704            onSuccess: '&?onSuccess',
705            validation: '=?uiValidationState',
706            isDisabled: '=?uiDisabled',
707            confirmMessage: '@uiConfirmMessage'
708        },
709        transclude: true,
710        replace: true,
711        template: '<a href ng-click="callApi(); $event.stopPropagation();" ng-disabled="isDisabled || validation.$invalid || inProgress" ng-class="{\'action-in-progress\':inProgress}" ng-transclude="">Do Something...</a>',
712        link: function (scope, element, attrs) {
713
714            var _callApi = function () {
715                if (scope.inProgress) return;
716                scope.inProgress = true;
717
718                if (!scope.isDownload) {
719                    if (!scope.isPost) {
720                        CommonApi.all(scope.apiName).customGet(scope.apiAction, _.merge({}, scope.data, scope.urlData)).then(
721                            function (data) {
722                                scope.inProgress = false;
723                                if (!data) data = {};
724                                MessagesService.showMessage(data.message || $translate.instant('algoapps.Success'), 'success', 5000);
725                                if (scope.onSuccess) scope.onSuccess({ result: data });
726                                if (scope.$parent.sendCommand) scope.$parent.sendCommand(scope.apiAction);
727                                else SendCommandService.sendCommand(scope.apiName, scope.apiAction);
728
729                            }, function (response) {
730                                scope.inProgress = false;
731                                MessagesService.showError(response);
732                            });
733                    }
734                    else {
735                        CommonApi.all(scope.apiName).customPost(scope.apiAction, scope.urlData, scope.data).then(
736                            function (data) {
737                                scope.inProgress = false;
738                                if (!data) data = {};
739                                MessagesService.showMessage(data.message || $translate.instant('algoapps.Success'), 'success', 5000);
740                                if (scope.onSuccess) scope.onSuccess({ result: data });
741                                if (scope.$parent.sendCommand) scope.$parent.sendCommand(scope.apiAction);
742                                else SendCommandService.sendCommand(scope.apiName, scope.apiAction);
743
744                            }, function (response) {
745                                scope.inProgress = false;
746                                MessagesService.showError(response);
747                            });
748                    }
749                } else {
750                    if (!scope.isPost) {
751                        CommonApi.all(scope.apiName).download(scope.apiAction, _.merge({}, scope.data, scope.urlData), null, 'GET').then(
752                            function (data) {
753                                scope.inProgress = false;
754                            }, function (response) {
755                                scope.inProgress = false;
756                                MessagesService.showError(response);
757                            }
758                        );
759                    }
760                    else {
761                        CommonApi.all(scope.apiName).download(scope.apiAction, scope.urlData, scope.data, 'POST').then(
762                            function (data) {
763                                scope.inProgress = false;
764                            }, function (response) {
765                                scope.inProgress = false;
766                                MessagesService.showError(response);
767                            }
768                        );
769                    }
770                }
771
772            }
773
774            var _execute = function () {
775                if (scope.confirmMessage) {
776                    MessagesService.confirm(scope.confirmMessage, '').then(
777                        function () { _callApi(); },function () { }
778                    );
779                }
780                else {
781                    _callApi();
782                }
783            }
784            var callApi = _.debounce(_execute, 50);
785            scope.callApi = function () { callApi(); }
786
787        }
788    };
789}]);
790
791
792/*Default list settings:*/
793angular.module("DataUI").directive('uiListSettings', [function () {
794    return {
795        restrict: 'A',
796        priority: 1000,
797        scope: false,
798        link: {
799            pre: function (scope, element, attrs) {
800                if (!scope.listSettings) scope.listSettings = {};
801
802                attrs.$observe("uiListSettings", function (newVal) {
803                    angular.merge(scope.listSettings, angular.fromJson(newVal));
804                });
805
806                angular.merge(scope.listSettings, angular.fromJson(attrs.uiListSettings));
807
808            }
809        }
810    };
811}]);
812
813
814/*Set filters from object:*/
815angular.module("DataUI").directive('uiFilterFromObject', [function () {
816    return {
817        restrict: 'A',
818        priority: 1000,
819        scope: {
820            filterObj: '=uiData'
821        },
822        link: function(scope, element, attrs){
823            if (!scope.$root.listSettings) scope.$root.listSettings = {};
824
825            var _updateListSettings = function () {
826                if (!scope.$root.listSettings.filter) scope.$root.listSettings.filter = {};
827                _.merge(scope.$root.listSettings.filter, scope.filterObj);
828            }
829            var updateListSettings = _.debounce(_updateListSettings, 100);
830            scope.$watch("filterObj", updateListSettings);
831            scope.$watch("filterObj", updateListSettings, true);
832
833            angular.merge(scope.listSettings, angular.fromJson(attrs.uiListSettings));
834
835        }
836    };
837}]);
838/*Directive to specify wich Api to use*/
839angular.module("DataUI").directive('uiLookup', ['CommonApi', function (CommonApi) {
840    return {
841        restrict: 'A',
842        priority: 1000,
843        scope: {
844            lookupId: '=uiLookup',
845            apiName: '@uiApiName'
846        },
847        replace: true,
848        template: '<span class="lookup-label"></span>',
849        link: function (scope, element, attrs) {
850
851            var _reload = function () {
852                CommonApi.getLookup(scope.apiName, scope.lookupId).then(
853                    function (data) { $(element).text(data.Label || data.Name || data.Title || data.Id || scope.lookupId); },
854                    function () { $(element).text('');});
855            };
856            var reload = _.debounce(_reload, 50);
857            scope.$watch('lookupId', reload);
858            scope.$watch('apiName', reload);
859        }
860    };
861}]);
862
863
864/*Directive to specify wich Api to use*/
865angular.module("DataUI").directive('uiApiName', ['CommonApi', '$rootScope', '$compile', function (CommonApi, $rootScope, $compile) {
866    return {
867        restrict: 'A',
868        priority: 1000,
869        scope: false,
870        link: function (scope, element, attrs) {
871            scope.fields = new Array();
872
873            attrs.$observe("uiApiName", function (newVal) {
874                scope.apiName = newVal;
875            });
876
877            scope.findField = function (fieldId) {
878                return _.chain(scope.fields).find(function (r) { return r.Id == fieldId }
878).value();
879            };
880        }
881    };
882}]);
883
884/*Directive to specify wich Api to for exporting data*/
885angular.module("DataUI").directive('uiExportApiName', [function () {
886    return {
887        restrict: 'A',
888        priority: 1000,
889        scope: false,
890        link: function (scope, element, attrs) {
891            attrs.$observe("uiExportApiName", function (newVal) {
892                if (scope.listSettings) scope.listSettings.exportApiName = newVal;
893            });
894        }
895    };
896}]);
897
898/*Directive to default values for new objects*/
899angular.module("DataUI").directive('uiDataDefault', [function () {
900    return {
901        restrict: 'EA',
902        priority: 1000,
903        scope: false,
904        link: function (scope, element, attrs) {
905
906            attrs.$observe("uiDataDefault", function (newVal) {
907                if (!scope.defaultRow) scope.defaultRow = {};
908                angular.extend(scope.defaultRow, angular.fromJson(newVal));
909            });
910
911        }
912    };
913}]);
914
915/*Directive to default values for new objects*/
916angular.module("DataUI").directive('uiDataOverride', [function () {
917    return {
918        restrict: 'EA',
919        priority: 1000,
920        scope: false,
921        link: function (scope, element, attrs) {
922
923            attrs.$observe("uiDataOverride", function (newVal) {
924                if (!scope.dataOverride) scope.dataOverride = {};
925                angular.extend(scope.dataOverride, angular.fromJson(newVal));
926            });
927
928        }
929    };
930}]);
931
932/*Directive to extend a object*/
933angular.module("DataUI").directive('uiDataExtend', [function () {
934    return {
935        restrict: 'EA',
936        priority: 1000,
937        scope: {
938            data: '=?uiDataExtend',
939            extendData:'=?uiDataExtendWith'
940        },
941        link: function (scope, element, attrs) {
942
943            var doExtend = function () {
944                if (!scope.data) scope.data = {};
945                if (!scope.extendData) scope.extendData = {};
946                angular.merge(scope.data, scope.extendData);
947            }
948            scope.$watch("data", doExtend);
949            scope.$watch("extendData", doExtend);
950
951        }
952    };
953}]);
954
955
956/*Directive to show data as a list ul > li with commands*/
957angular.module("DataUI").directive('uiDataList', ['$timeout', function ($timeout) {
958    return {
959        restrict: 'A',
960        priority:100,
961        scope: false,
962        templateUrl: function (element, attrs) { return attrs.uiTemplate || 'uiApiListFull.html'; },
963        link: function (scope, element, attrs) {
964            if (scope.loadData) $timeout(scope.loadData, 100);
965
966            scope.refresh = function () {
967                if(scope.loadData) scope.loadData();
968            }
969        }
970    };
971}]);
972
973/*Directive to show data as masonry list*/
974angular.module("DataUI").directive('uiMasonry', ['$window', function ($window) {
975    return {
976        restrict: 'A',
977        priority: 100,
978        scope: false,
979        link: function (scope, element, attrs) {
980            var msnry, imgLoader;
981
982            var _update = function () {
983                if (!msnry) {
984                    msnry = $(element).masonry({ itemSelector: '.masonry-item', columnWidth: '.masonry-item', percentPosition: true });
985                }
986                msnry.masonry('reloadItems').masonry('layout');
987                //Images have loaded
988                msnry.imagesLoaded()
989                    .progress(function (imgLoad, image) {
990                        var s = image.img.src;
991                        if (!window.allimages) window.allimages = {};
992                        if (s) {
993                            if (!window.allimages[s]) allimages[s] = false;
994                            if (window.allimages[s] != image.isLoaded) update();
995                            window.allimages[s] = image.isLoaded;
996                        }
997                    });
998            }
999            var update = _.debounce(_update, 50);
1000            //Watch for dom change and other events that must update the masonry
1001            scope.$watch(function () { return element[0].childNodes.length; }, update);
1002            //Init
1003            update();
1004        }
1005    };
1006}]);
1007
1008/*Directive to show data as grouped by columns and row fields*/
1009angular.module("DataUI").directive('uiPivot', [function () {
1010    return {
1011        restrict: 'A',
1012        priority: 100,
1013        scope: false,
1014        templateUrl: 'uiPivot.html',
1015        transclude:true,
1016        link: {
1017            pre: function (scope, element, attrs, ctrl, transcludeFn) {
1018                scope.pivotData = {};
1019
1020                var _buildPivot = function () {
1021                    scope.pivotData = {}; scope.pivotColumns = {};
1022                    scope.colCssClass = attrs.uiColClass;
1023                    //Prepare pivot with all row and col values;
1024                    _.forEach(scope.$data, function (r) {
1025                        var rv = r[attrs.uiRowField] || 'ND';
1026                        scope.pivotData[rv] = {};
1027                        _.forEach(scope.$data, function (c) {
1028                            var cv = c[attrs.uiColField] || 'ND';
1029                            scope.pivotData[rv][cv] = {};
1030                            scope.pivotColumns[cv] = {};
1031                        });
1032                    });
1033                    //Set object values
1034                    _.forEach(scope.$data, function (o) {
1035                        var rv = o[attrs.uiRowField] || 'ND';
1036                        var cv = o[attrs.uiColField] || 'ND';
1037                        scope.pivotData[rv][cv] = o;
1038                        scope.pivotData[rv][cv].hasValue = true;
1039                    });
1040
1041                    scope.$applyAsync();
1042                };
1043
1044                var buildPivot = _.debounce(_buildPivot, 200);
1045                scope.$watchCollection('$data', buildPivot);
1046                attrs.$observe('rowField', buildPivot);
1047                attrs.$observe('colField', buildPivot);
1048            }
1049        }
1050    };
1051}]);
1052
1053/*Directive to show data as grouped by columns and row fields*/
1054angular.module("DataUI").directive('uiPivotData', [function () {
1055    return {
1056        restrict: 'A',
1057        priority: 100,
1058        scope: {
1059            data: '=uiData',
1060            pivotData: '=uiPivotData',
1061            rowField: '@uiRowField',
1062            colField: '@uiColField',
1063        },
1064        link: {
1065            pre: function (scope, element, attrs, ctrl, transcludeFn) {
1066
1067                var _buildPivot = function () {
1068                    scope.pivotData.pivotColumns = [];
1069                    scope.pivotData.data = {};
1070                    var cols = {};
1071                    
1072                    //Prepare pivot with all row and col values;
1073                    _.forEach(scope.data, function (r) {
1074                        var rv = r[scope.rowField] || 'ND';
1075                        scope.pivotData.data[rv] = {};
1076                        scope.pivotData.data[rv].data = r;
1077                        _.forEach(scope.data, function (c) {
1078                            var cv = c[scope.colField] || 'ND';
1079                            scope.pivotData.data[rv][cv] = {};
1080                            cols[cv] = {};
1081                        });
1082                    });
1083
1084                    //Set object values
1085                    _.forEach(scope.data, function (o) {
1086                        var rv = o[scope.rowField] || 'ND';
1087                        var cv = o[scope.colField] || 'ND';
1088                        scope.pivotData.data[rv][cv] = o;
1089                        scope.pivotData.data[rv][cv].hasValue = true;
1090                    });
1091
1092                    if (cols.ND) delete cols.ND;
1093                    scope.pivotData.pivotColumns = _(cols).keys().sortBy().value()
1094                    scope.$applyAsync();
1095                };
1096
1097                var buildPivot = _.debounce(_buildPivot, 200);
1098                scope.$watch('data', buildPivot);
1099                scope.$watchCollection('data', buildPivot);
1100                attrs.$observe('rowField', buildPivot);
1101                attrs.$observe('colField', buildPivot);
1102            }
1103        }
1104    };
1105}]);
1106
1107
1108/*
1109Directive to show data as grouped by columns and row fields
1110see: https://www.webdatarocks.com/doc/download/
1111*/
1112angular.module("DataUI").directive('uiPivotTable', ['$ocLazyLoad', function ($ocLazyLoad) {
1113    return {
1114        restrict: 'A',
1115        priority: 100,
1116        scope: {
1117            data: '=uiData',
1118            dataUrl: '@uiDataUrl',
1119            cols: '=uiColumnsBy',
1120            rows: '=uiRowsBy',
1121            vals: '=uiValuesBy',
1122            filters: '=uiFilters',
1123            fields: '=uiFieldLabels',
1124            opts: '=uiOptions',
1125            viewMode: "@uiViewMode"
1126        },
1127        template: '<div id="ui-pivot-{{::$id}}" class="ui-pivot"></div>',
1128        transclude: true,
1129        link: function (scope, element, attrs) {
1130
1131            scope.pivotData = {};
1132            scope.pivotObj = {};
1133
1134            var getFieldObject = function (o) {
1135                var r = (_.isObject(o) ? o : { uniqueName: o });
1136                if (!r.caption && r.uniqueName && scope.fields && scope.fields[r.uniqueName]) r.caption = scope.fields[r.uniqueName];
1137                return r;
1138            }
1139
1140            var _buildPivot = function () {
1141                if (!$.fn.webdatarocks) {
1142                    buildPivot(); return;
1143                }
1144
1145                var el = $(element).find('.ui-pivot');
1146
1147
1148                //prepare datasources object
1149                var ds = {};
1150                if (scope.data) {
1151                    ds.data = scope.data;
1152                }
1153                else if (scope.dataUrl) {
1154                    ds.filename = scope.dataUrl;
1155                }
1156
1157
1158                var isEditMode = (scope.viewMode == 'edit');
1159                var opts = _.merge({
1160                    toolbar: isEditMode,
1161                    report: {
1162                        dataSource: ds,
1163                        slice: {
1164                            reportFilters: _.map(scope.filters, getFieldObject),
1165                            rows: _.map(scope.rows, getFieldObject),
1166                            columns: _.map(scope.cols, getFieldObject),
1167                            measures: _.map(scope.vals, getFieldObject),
1168                        },
1169                        options: {
1170                            grid: {
1171                                type: 'compact',
1172                                showHeaders:false
1173                            },
1174                            configuratorButton: false
1175                        }
1176                    },
1177                    global: {
1178                        localization: "/DesktopModules/MVC/AlgoritmiaAngular/js/lib/webdatarocks/loc/pt_PT.json"
1179                    }
1180                }, scope.opts);
1181
1182                scope.pivotObj = $(el).webdatarocks(opts);
1183
1184            };
1185
1186            var buildPivot = _.debounce(_buildPivot, 200);
1187            scope.$watchCollection('data', buildPivot);
1188            scope.$watch('dataUrl', buildPivot);
1189            scope.$watch('cols', buildPivot);
1190            scope.$watch('rows', buildPivot);
1191            scope.$watch('vals', buildPivot);
1192            scope.$watch('aggregatorName', buildPivot);
1193            scope.$watch('rendererName', buildPivot);
1194            scope.$watch('viewMode', buildPivot);
1195
1196            //Load dependencies
1197            $ocLazyLoad.load({
1198                serie: true,
1199                files: [
1200                    '/DesktopModules/MVC/AlgoritmiaAngular/js/lib/webdatarocks/webdatarocks.toolbar.min.js',
1201                    '/DesktopModules/MVC/AlgoritmiaAngular/js/lib/webdatarocks/webdatarocks.min.css',
1202                    '/DesktopModules/MVC/AlgoritmiaAngular/js/lib/webdatarocks/webdatarocks.js'
1203                ]
1204            });
1205        }
1206    };
1207}]);
1208
1209
1210/*
1211Directive to show data as grouped by columns and row fields
1212see: https://pivottable.js.org/examples/index.html
1213*/
1214angular.module("DataUI").directive('uiPivotTable2', ['$ocLazyLoad', function ($ocLazyLoad) {
1215    return {
1216        restrict: 'A',
1217        priority: 100,
1218        scope: {
1219            data: '=uiData',
1220            cols: '=uiColumnsBy',
1221            rows: '=uiRowsBy',
1222            vals: '=uiValuesBy',
1223            opts: '=uiOptions',
1224            aggregatorName:"@uiAggregation",
1225            rendererName:"@uiRenderer",
1226            viewMode: "@uiViewMode",
1227            pivotVersion: "@uiPivotVersion"
1228        },
1229        template: '<div class="ui-pivot"></div>',
1230        transclude: true,
1231        link: function (scope, element, attrs) {
1232
1233            scope.pivotData = {};
1234
1235            var _buildPivot = function () {
1236                var opts = _.merge({}, scope.opts);
1237                if (scope.cols) opts.cols = scope.cols;
1238                if (scope.rows) opts.rows = scope.rows;
1239                if (scope.aggregatorName) opts.aggregatorName = scope.aggregatorName;
1240
1241                if (scope.aggregatorName == 'multi' || scope.aggregatorName == 'Multifact Aggregators') {
1242                    opts.aggregatorName = 'Multifact Aggregators';
1243                    opts.vals = [];
1244                    var aggMap = {};
1245                    _.forEach(scope.vals, function (o,i) {
1246                        var aggName = 'agg' + i;
1247                        var d = {
1248                            aggType: 'Sum',
1249                            arguments: [],
1250                            name: aggName,
1251                            varName: aggName,
1252                            hidden: false,
1253                            renderEnhancement: 'none'
1254                        };
1255
1256                        if (_.isString(o)) {
1257                            d.arguments = [o];
1258                            d.name = o;
1259                        }
1260                        else {
1261                            _.extend(d, o);
1262                        }
1263                        aggMap[aggName] = d;
1264                    });
1265                    opts.rendererOptions = { aggregations: { defaultAggregations: aggMap } };
1266                    var customAggs = {};
1267                    //Lame dependency... If we need this module, wait until it's loaded...
1268                    if (!$.pivotUtilities || !$.pivotUtilities.multifactAggregatorGenerator) {
1269                        buildPivot();
1270                        return;
1271                    }
1272                    customAggs['Multifact Aggregators'] = $.pivotUtilities.multifactAggregatorGenerator(aggMap, []);
1273                    $.pivotUtilities.customAggs = customAggs;
1274                    opts.aggregators = $.extend($.pivotUtilities.aggregators, $.pivotUtilities.customAggs);
1275                    opts.renderers = $.extend($.pivotUtilities.renderers, $.pivotUtilities.gtRenderers);
1276
1277                } else {
1278                    if (scope.vals) opts.vals = scope.vals;
1279                }
1280                if (scope.rendererName) opts.rendererName = scope.rendererName;
1281
1282                opts.grouping= {
1283                    colGroupBefore: false, // this is defualt value
1284                };
1285                opts.rendererOptions= {
1286                    table: {
1287                        compactLayout: true,  // this is default value
1288                    }
1289                };
1290
1291                if (scope.viewMode != 'edit')
1292                    opts.showUI = false;
1293
1294                $(element).pivotUI(scope.data, opts, true);
1295
1296
1297            };
1298
1299            var buildPivot = _.debounce(_buildPivot, 200);
1300            scope.$watchCollection('data', buildPivot);
1301            scope.$watch('cols', buildPivot);
1302            scope.$watch('rows', buildPivot);
1303            scope.$watch('vals', buildPivot);
1304            scope.$watch('aggregatorName', buildPivot);
1305            scope.$watch('rendererName', buildPivot);
1306            scope.$watch('viewMode', buildPivot);
1307
1308            var srcFolder = 'Grouping';
1309            if (scope.pivotVersion == 'grouping') srcFolder = 'Grouping';
1310            //Load dependencies
1311            $ocLazyLoad.load({
1312                serie: true,
1313                files: [
1314                    '/DesktopModules/MVC/AlgoritmiaAngular/js/lib/pivottable/' + srcFolder + '/pivot.min.js',
1315                    '/DesktopModules/MVC/AlgoritmiaAngular/js/lib/pivottable/' + srcFolder + '/pivot.min.css',
1316                    '/DesktopModules/MVC/AlgoritmiaAngular/js/lib/pivottable/' + srcFolder + '/Grouping.css',
1317                    '/DesktopModules/MVC/AlgoritmiaAngular/js/lib/pivottable/' + srcFolder + '/pivot.pt.min.js',
1318                    '/DesktopModules/MVC/AlgoritmiaAngular/js/lib/pivottable/Plugins/multifact-pivottable.js'
1319                ]
1320            });
1321        }
1322    };
1323}]);
1324
1325
1326
1327/*Directive to specify wich one is the title field*/
1328angular.module("DataUI").directive('uiTitleField', ['$rootScope',function ($rootScope) {
1329    return {
1330        restrict: 'A',
1331        priority:1000,
1332        scope: false,
1333        link:{
1334            pre:  function (scope, element, attrs) {
1335                attrs.$observe('uiTitleField', function(newVal){scope.titleField = newVal;});
1336            }
1337        }
1338    };
1339}]);
1340
1341
1342/*Table directive*/
1343angular.module("DataUI").directive('uiDataTable', ['CommonApi', 'MessagesService', 'ContentItemsService', '$translate', function (CommonApi, MessagesService, ContentItemsService, $translate) {
1344    return {
1345        restrict: 'EA',
1346        priority:100,
1347        scope: {
1348            allowUpload: '=uiAllowUpload',
1349            apiName: '@uiUploadApiName',
1350            formName: '@uiFormName',
1351            defaultData: '=uiData'
1352        },        
1353        link: function (scope, element, attrs) {
1354            
1355            
1356            /*Drag n drop Files*/
1357            if (scope.allowUpload)
1358            { 
1359                var showDrop = function (e) {
1360                    e.preventDefault();
1361                    if (hideDropTimer) window.clearTimeout(hideDropTimer);
1362                    $(element).addClass('file-drag-hover');
1363                }
1364                var hideDropTimer;
1365                var hideDrop = function (e) {
1366                    if (e) e.preventDefault();
1367                    hideDropTimer = window.setTimeout(function () { $(element).removeClass('file-drag-hover') }, 100);
1368                }
1369                $(element).on('dragenter', showDrop);
1370                $(element).on('dragover', showDrop);
1371                $(element).on('dragleave', hideDrop);
1372
1373                $(element).find('.file-drop-zone').on('drop', function (e) {
1374                    hideDrop(); e.preventDefault(); e.stopPropagation();
1375                    if (e.originalEvent.dataTransfer) {
1376                        /*Data to go with the file*/
1377                        var model = _.merge({ FormName: scope.formName }, scope.defaultData); 
1378                        model.Content = _.cloneDeep(model);
1379                        /*UPLOAD FILES HERE*/
1380                        scope.doUpload(e.originalEvent.dataTransfer.files, model);
1381                    }
1382                });
1383            }
1384
1385
1386            /*Upload the file specified in an file input element and an optionl data object*/
1387            scope.doUpload = function (files, uploadData) {
1388                var _api = CommonApi.all(scope.apiName);
1389                if (files && files.length) {
1390                    //scope.FileName = file.name;
1391                    var formData = new FormData();
1392                    _.forEach(files, function (file) { formData.append('file', file); });
1393                    formData.append('model', JSON.stringify(uploadData || {}));
1394
1395                    _api
1396                        .withHttpConfig({ transformRequest: angular.identity, headers: { 'Content-Type': undefined } })
1397                        .customPost('UploadDocument', null, formData)
1398                        .then(function (data) {
1399                            MessagesService.showMessage($translate.instant('algoapps.UploadFileSuccess', { }), 'success', 5000);
1400
1401                            //Then data is an object or a list with only one object, open it for editing.
1402                            if (data && (data.Id || data.length == 1)) {
1403                                var row = data;
1404                                if (_.isArray(data)) row = data[0];
1405                                if (row.Id) {
1406                                    ContentItemsService.OpenModal(scope.$parent, {
1407                                        apiName: scope.apiName,
1408                                        formName: scope.formName,
1409                                        viewMode: 'edit',
1410                                        dataId: row.Id,
1411                                        onComplete: function () {
1412                                            scope.$parent.loadData();
1413                                            scope.$applyAsync();
1414}
1415                                    });
1416                                }
1417
1418                            }
1419
1420                            scope.$parent.loadData();
1421                            scope.$applyAsync();
1422                        }, function (response) {
1423                            /*error*/
1424                            MessagesService.showError(response);
1425                            scope.$applyAsync();
1426                        });
1427                }
1428            };
1429
1430        }
1431    };
1432}]);
1433
1434/*Comunication to receive eventes for load / reload Data*/
1435angular.module("DataUI").directive('uiLoadDataOn', [function () {
1436    return {
1437        restrict: 'A',
1438        priority: 200,
1439        scope: false,
1440        link: function (scope, element, attrs) {
1441
1442            if (attrs.uiLoadDataOn) {
1443                scope.$on('uiCommand', function (ev, data) {
1444                    var triggers = attrs.uiLoadDataOn ? attrs.uiLoadDataOn.split(',') : new Array();
1445                    if (triggers.indexOf(data.sendGroup + '.' + data.commandName) == -1) return;
1446                    if (scope.refresh) scope.refresh();
1447                    else if (scope.loadData) scope.loadData();
1448
1449                    if (data.commandName == 'SelectRow') scope.viewMode = 'display';
1450                    if (data.commandName == 'EditRow' || data.commandName == 'NewRow') scope.viewMode = 'edit';
1451                });
1452            }
1453        }
1454    };
1455}]);
1456
1457/*Comunication to receive eventes for Data filter change*/
1458angular.module("DataUI").directive('uiFilterOn', [function () {
1459    return {
1460        restrict: 'A',
1461        priority: 200,
1462        scope: false,
1463        link: function (scope, element, attrs) {
1464
1465            if (attrs.uiFilterOn) {
1466                scope.$on('uiCommand', function (ev, data) {
1467                    var triggers = attrs.uiFilterOn ? attrs.uiFilterOn.split(',') : new Array();
1468                    if (triggers.indexOf(data.sendGroup + '.' + data.commandName) == -1) return;
1469                    if (scope.listSettings) _.merge(scope.listSettings.filter, data.row);
1470                });
1471            }
1472        }
1473    };
1474}]);
1475
1476/*Comunication infrastructure for sending broadcast commands on the specified group*/
1477angular.module("DataUI").directive('uiSendGroup', ['SendCommandService', function (SendCommandService) {
1478    return {
1479        restrict: 'A',
1480        priority: 500,
1481        scope: false,
1482        link: function (scope, element, attrs) {
1483            attrs.$observe('uiSendGroup', function (newVal) { scope.sendGroup = newVal || scope.apiName; })
1484            scope.sendCommand = function (commandName, data) {
1485                SendCommandService.sendCommand(scope.sendGroup, commandName, data);
1486            }
1487        }
1488    };
1489}]);
1490
1491/*Service for inter component comunication*/
1492angular.module("DataUI").service('SendCommandService', ['$rootScope', '$timeout', function ($rootScope, $timeout) {
1493    var svc = {
1494        dataCommands: {
1495            ready: 'Ready',
1496            loaded: 'DataLoaded',
1497            selected: 'RowSelected',
1498            added: 'RowAdded',
1499            deleted: 'RowDeleted',
1500            updated: 'RowUpdated',
1501        },
1502        sendCommand: function (sendGroup, commandName, data) {
1503            $timeout(function () {
1504                console.log(sendGroup + ' send a ' + commandName + ' command.');
1505                $rootScope.$broadcast('uiCommand', { sendGroup: sendGroup, commandName: commandName, row: data });
1506            }, 50);
1507        }
1508    }
1509    return svc;
1510}]);
1511
1512/*Directive for file href downloads*/
1513angular.module("DataUI").directive('uiFileDownloadHref', ['CommonApi', function (CommonApi) {
1514    return {
1515        restrict: 'A',
1516        scope: {
1517            file: '=uiFileDownloadHref',
1518            apiName: '@uiFileApiName'
1519        },
1520        link: function (scope, element, attrs) {
1521            var _api = CommonApi.createApi(scope.apiName || CommonApi.defaultApis.file.apiName);
1522            scope.$watch('file', function (newVal) {
1523                if (newVal) {
1524                    var parts = newVal.toString().split("|");
1525                    var _id = parts[0] || 0;
1526                    var _Location= (parts.length >= 2 && parts[2] && parts[2] != 'undefined' ? parts[2] : '')
1527                    if (_Location) {
1528                        $(element).attr('href', _Location);
1529                    }
1530                    else if (_id) {
1531                        var downloadUrl = _api.getUrl('Download', { Id: _id });
1532                        $(element).attr('href', downloadUrl).attr('target', '_blank');
1533                    }
1534                }
1535            })
1536        }
1537    }
1538}]);
1539
1540/*Directive for file src downloads*/
1541angular.module("DataUI").directive('uiFileDownloadSrc', ['CommonApi', function (CommonApi) {
1542    return {
1543        restrict: 'A',
1544        scope: {
1545            file: '=uiFileDownloadSrc',
1546            apiName: '@uiFileApiName'
1547        },
1548        link: function (scope, element, attrs) {
1549            var _api = CommonApi.createApi(scope.apiName || CommonApi.defaultApis.file.apiName);
1550            scope.$watch('file', function (newVal) {
1551                if (newVal) {
1552                    var parts = newVal.toString().split("|");
1553                    var _id = parts[0] || 0;
1554                    var _filename = parts[1] || '';
1555                    var _location = (parts.length >= 2 && parts[2] && parts[2] != 'undefined' ? parts[2] : '');
1556
1557                    if (_location) {
1558                        $(element).attr('src', _location);
1559                    }
1560                    else if (_id) {
1561                        var downloadUrl = _api.getUrl('Download', { Id: _id });
1562                        $(element).attr('src', downloadUrl);
1563                    }
1564                }
1565            })
1566        }
1567    }
1568}]);
1569
1570/*Directive for file src downloads*/
1571angular.module("DataUI").directive('uiFileThumbnailSrc', ['CommonApi', function (CommonApi) {
1572    return {
1573        restrict: 'A',
1574        scope: {
1575            file: '=uiFileThumbnailSrc',
1576            maxWidth: '@?maxWidth',
1577            maxHeight: '@?maxHeight',
1578            apiName: '@uiFileApiName'
1579        },
1580        link: function (scope, element, attrs) {
1581            var _api = CommonApi.createApi(scope.apiName || attrs.uiApiName || CommonApi.defaultApis.file.apiName);
1582            scope.$watch('file', function (newVal) {
1583                if (newVal) {
1584                    var parts = newVal.toString().split("|");
1585                    _id = parts[0] || 0;
1586                    if (_id) {
1587                        var p = { Id: _id };
1588                        if (scope.maxHeight) p.maxHeight = scope.maxHeight;
1589                        if (scope.maxWidth) p.maxWidth = scope.maxWidth;
1590                        var downloadUrl = _api.getUrl('Thumbnail', p);
1591                        $(element).attr('src', downloadUrl); 
1592                    }
1593                }
1594            })
1595        }
1596    }
1597}]);
1598
1599
1600/*Filter to convert fileRefs in URL*/
1601angular.module("DataUI").filter('fileRefUrl', ['CommonApi', function (CommonApi) {
1602    var _api = CommonApi.createApi(CommonApi.defaultApis.file.apiName);
1603    var f = function (val) {
1604        if (val) {
1605            var parts = val.toString().split("|");
1606            var _id = parts[0] || 0;
1607            var _location = (parts.length >= 2 && parts[2] && parts[2] != 'undefined' ? parts[2] : '');
1608
1609            if (_location) return _location;
1610            if (_id) return _api.getUrl('Download', { Id: _id });
1611        }
1612
1613        return '';
1614    };
1615    
1616    return f;
1617}]);
1618
1619angular.module("DataUI").directive('uiDataTableTools', ['CommonApi', '$rootScope', '$compile', function (CommonApi, $rootScope, $compile) {
1620    return {
1621        restrict: 'EA',
1622        scope: false,
1623        priority: 100,
1624        templateUrl: function (element, attrs) { return attrs.uiTemplate || 'uiApiTableTools.html'; },
1625        link: function (scope, element, attrs) {
1626            scope.toggleFilters = function () { 
1627                scope.listSettings.hideFilters = !scope.listSettings.hideFilters;
1628            }
1629        }
1630    };
1631}]);
1632
1633
1634/*Filters*/
1635
1636/*Default filter attribute: - proxy for templated field from FormsUI*/
1637angular.module("DataUI").directive('uiDataFieldFilter', ['ParseContextUtils', '$window', '$location', function (ParseContextUtils, $window, $location) {
1638    return {
1639        restrict: 'A',
1640        priority: 1000,
1641        scope: {
1642            settings: '=uiDataFieldFilter',
1643            data: '=data',
1644            urlSync: '@?uiUrlSync',
1645            filterOperator: '=?uiDataFieldFilterOperator',
1646        },
1647        template:'<div ui-field="field" data="data" ui-view-mode="edit" ui-split-complex-id="false" ui-form-data="formData"></div>',
1648        link: function (scope, element, attrs) {
1649            scope.formData = scope.$parent;
1650            //Create field definition
1651            scope.field = angular.copy(scope.settings);
1652            scope.field.IgnoreAditionalFields = true;
1653            scope.field.ClearIfInvalid = true;
1654            var _lastLocation = $location.url();
1655
1656            var updateOperator = function () {
1657                if (scope.filterOperator) {
1658                    if (scope.settings && scope.settings.ForceOperator) {
1659                        scope.filterOperator[scope.field.Id] = scope.settings.ForceOperator;
1660                    }
1661                    else {
1662                        if (scope.field.FieldType == 'number')
1663                            scope.filterOperator[scope.field.Id] = '(eq)';
1664                        else if (scope.field.FieldType == 'text')
1665                            scope.filterOperator[scope.field.Id] = '(lk)';
1666                        else
1667                            scope.filterOperator[scope.field.Id] = '';
1668                    }
1669                }
1670            }
1671
1672            //apply default values to fields
1673            if (scope.field.defaultValue && (!angular.isDefined(scope.data[scope.field.Id]) || scope.data[scope.field.Id] == null)) {
1674                var defaultValue = scope.field.defaultValue;
1675                if (defaultValue) {
1676                    defaultValue = ParseContextUtils.evalInContext(defaultValue, scope.data);
1677                }
1678                scope.data[scope.field.Id] = defaultValue;
1679            }
1680
1681
1682            var getUrlVars = function (){
1683                var vars = [], hash;
1684                var hashes = $window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
1685                for (var i = 0; i < hashes.length; i++) {
1686                    hash = hashes[i].split('=');
1687                    vars[hash[0]] = decodeURIComponent(hash[1]);
1688                }
1689                return vars;
1690            }
1691
1692            //Sync filter from URL
1693            if (!scope.urlSync || scope.urlSync != 'none') {
1694                //First load
1695                var newVal = getUrlVars()[scope.field.Id];
1696                if (newVal) {
1697                    scope.data[scope.field.Id] = newVal;
1698                    updateOperator();
1699                }
1700
1701                scope.$on('$locationChangeSuccess', function (event) {
1702                    if (_lastLocation == $location.url()) return;
1703                    _lastLocation = $location.url();
1704                    //var newVal = $location.search()[scope.field.Id];
1705                    var newVal = getUrlVars()[scope.field.Id];
1706                    if (newVal != scope.data[scope.field.Id])
1707                        scope.data[scope.field.Id] = newVal;
1708                    updateOperator();
1709                });
1710            }
1711            //Sync filter to URL
1712            if (scope.urlSync == 'full') {
1713                scope.$watch(function () { return scope.data[scope.field.Id] }, function (newVal) {
1714                    if (newVal)
1715                        $location.search(scope.field.Id, newVal);
1716                    else
1717                        $location.search(scope.field.Id, null);
1718                });
1719            }
1720
1721            //Init operator if necessary
1722            updateOperator();
1723        }
1724    };
1725}]);
1726
1727/*Default filter attribute: - proxy for templated field from FormsUI*/
1728angular.module("DataUI").directive('uiDataFieldRangeFilter', ['$location', '$window', '$translate', function ($location, $window, $translate) {
1729    return {
1730        restrict: 'A',
1731        scope: {
1732            settings: '=uiDataFieldRangeFilter',
1733            data: '=data',
1734            urlSync: '@?uiUrlSync'
1735        },
1736        template: [
1737            '<div>',
1738            '   <div class="row">',
1739            '       <div class="col-xs-6"><div ui-field="fieldMin" data="data[settings.Id].range" ui-view-mode="edit"></div></div>',
1740            '       <div class="col-xs-6"><div ui-field="fieldMax" data="data[settings.Id].range" ui-view-mode="edit"></div></div>',
1741            '   </div>',
1742            '</div>'].join(''),
1743        link: function (scope, element, attrs) {
1744            //Create field definition
1745            
1746            scope.fieldMin = angular.copy(scope.settings);
1747            scope.fieldMin.Label = scope.settings.Label + ' ' + $translate.instant('algoapps.From');
1748            scope.fieldMin.Id = 'from';
1749            scope.fieldMin.IgnoreAditionalFields = true;
1750
1751            scope.fieldMax = angular.copy(scope.settings);
1752            scope.fieldMax.Label = '' + $translate.instant('algoapps.To');
1753            scope.fieldMax.Id = 'to';
1754            scope.fieldMax.IgnoreAditionalFields = true;
1755
1756            //Commented because field directive does nor support changes to field label (during init phase the field info is copyed and then it is never used again).
1757            //$translate('algoapps.From').then(function (t) { scope.fieldMin.Label = scope.settings.Label + ' ' + t.toString(); }, function (t) { scope.fieldMin.Label = scope.settings.Label + ' ' + t.toString(); });
1758            //$translate('algoapps.To').then(function (t) { scope.fieldMax.Label = t.toString(); }, function (t) { scope.fieldMax.Label = t.toString(); });
1759
1760            scope.$watch('data', function (newVal) {
1761                if (newVal) {
1762                    if (!scope.data[scope.settings.Id]) scope.data[scope.settings.Id] = {};
1763                    if (!scope.data[scope.settings.Id].range) scope.data[scope.settings.Id].range = {};
1764
1765                    if (scope.settings.FieldType == 'date' && scope.settings.IncludeLastDay) {
1766                        var range = scope.data[scope.settings.Id].range;
1767                        if (range.to) {
1768                            scope.data[scope.settings.Id].range.to = moment(range.to).endOf('Day').format('YYYY-MM-DDTHH:mm:ssZ');
1769                        }
1770                    }
1771                }
1772            }, true);
1773
1774            var getUrlVars = function () {
1775                var vars = [], hash;
1776                var hashes = $window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
1777                for (var i = 0; i < hashes.length; i++) {
1778                    hash = hashes[i].split('=');
1779                    vars[hash[0]] = hash[1];
1780                }
1781                return vars;
1782            }
1783
1784            var _locationChangedHandler = function (event) {
1785                if (!scope.data) return;
1786                var kMin = scope.settings.Id + '.' + scope.fieldMin.Id;
1787                var kMax = scope.settings.Id + '.' + scope.fieldMax.Id;
1788                var search = getUrlVars();
1789                var newValMin = search[kMin];
1790                var newValMax = search[kMax];
1791                var d = scope.data[scope.settings.Id] || {};
1792                if (!d.range) d.range = {};
1793                if (!_.isUndefined(search[kMin]) && newValMin != d.range[scope.fieldMin.Id]) {
1794                    if (!scope.data[scope.settings.Id]) scope.data[scope.settings.Id] = {};
1795                    if (!scope.data[scope.settings.Id].range) scope.data[scope.settings.Id].range = {};
1796                    scope.data[scope.settings.Id].range[scope.fieldMin.Id] = newValMin
1797                };
1798                if (!_.isUndefined(search[kMax]) && newValMax != d.range[scope.fieldMax.Id]) {
1799                    if (!scope.data[scope.settings.Id]) scope.data[scope.settings.Id] = {};
1800                    if (!scope.data[scope.settings.Id].range) scope.data[scope.settings.Id].range = {};
1801                    scope.data[scope.settings.Id].range[scope.fieldMax.Id] = newValMax
1802                };
1803            }
1804
1805            //Sync filter with URL
1806            if (!scope.urlSync || scope.urlSync != 'none') {
1807
1808                //First load
1809                _locationChangedHandler();
1810
1811                scope.$on('$locationChangeSuccess', _locationChangedHandler);
1812            }
1813            if (scope.urlSync == 'full') {
1814                scope.$watch(function () { return scope.data[scope.settings.Id] }, function (newVal) {
1815                    var d = scope.data[scope.settings.Id];
1816                    if (!d.range) d.range = {};
1817                    var newValMin = d.range[scope.fieldMin.Id];
1818                    var newValMax = d.range[scope.fieldMax.Id];
1819                    $location.search(scope.settings.Id + '.' + scope.fieldMin.Id, newValMin ? newValMin : null);
1820                    $location.search(scope.settings.Id + '.' + scope.fieldMax.Id, newValMax ? newValMax : null);
1821                }, true);
1822            }
1823
1824
1825        }
1826    };
1827}]);
1828/*Default filter attribute:*/
1829angular.module("DataUI").directive('uiDataFilters', [function () {
1830    return {
1831        restrict: 'A',
1832        priority: 1000,
1833        scope: false,
1834        link: {
1835            pre: function (scope, element, attrs) {
1836                attrs.$observe("uiDataFilters", function (newVal) {
1837                    if (!scope.queryParams) scope.queryParams = {};
1838                    if (!scope.queryParams.filter) scope.queryParams.filter = {};
1839                    angular.extend(scope.queryParams.filter, angular.fromJson(newVal));
1840
1841                });
1842            }
1843        }
1844    };
1845}]);
1846
1847
1848/*Select filter*/
1849angular.module("DataUI").directive('uiSelectFilter', ['CommonApi', function (CommonApi) {
1850    return {
1851        restrict: 'A',
1852        priority: 1000,
1853        scope: {
1854            ngModel: '=ngModel',
1855            options: '@uiOptions',
1856            apiOptions: '@uiApiOptions',
1857            defaultOption: '@uiDefaultOption',
1858            emptyLabel: '@uiEmptyOption'
1859        },
1860        template: [
1861            '<div class="btn-group">',
1862            ' <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">',
1863            '  {{selectedOption.Label}} <span class="caret"></span>',
1864            ' </button>',
1865            ' <ul class="dropdown-menu">',
1866            '  <li ng-repeat="option in optionsArray" ng-click="select(option)" ng-class="{\'active\':selectedOption.Id==option.Id}"><a href="#">{{option.Label}}</a></li>',
1867            ' </ul>',
1868            '</div>'
1869            ].join(''),
1870        link: function (scope, element, attrs) {
1871            scope.selectedOption = { Id: '', Label: '' };
1872            scope.emptyOption = { Id: '', Label: '' };
1873            scope.optionsArray = new Array();
1874
1875            /*Build filter*/
1876            var _buildFilter = function () {
1877                if (scope.options) {
1878                    scope.optionsArray = new Array();
1879                    _.forEach(scope.options.split(','), function (o) {
1880                        var p = o.split('|');
1881                        scope.optionsArray.push({ Id: p[0], Label: p[1] || p[0] });
1882                    }); 
1883                };
1884                //Load from API
1885                if (scope.apiOptions) {
1886                    CommonApi.all(scope.apiOptions).getList().then(function (data) {
1887                        scope.optionsArray = data;
1888                        scope.optionsArray.push(scope.emptyOption);
1889                    });
1890                };
1891
1892                if (!scope.selectedOption.Id && scope.defaultOption)
1893                {
1894                    var p = scope.defaultOption.split('|');
1895                    scope.selectedOption = { Id: p[0], Label: p[1] || p[0] };
1896                }
1897
1898                if (scope.emptyLabel) scope.emptyOption.Label = scope.emptyLabel;
1899            }
1900            var buildFilter = _.debounce(_buildFilter, 100);
1901            scope.$watch('uiOptions', buildFilter);
1902            scope.$watch('uiDefaultOption', buildFilter);
1903            scope.$watch('uiEmptyOption', buildFilter);
1904
1905            /*Change Filter*/
1906            scope.select = function (newVal) {
1907                scope.selectedOption = newVal;
1908            }
1909            scope.$watch('selectedOption.Id', function (newVal) {
1910                scope.ngModel = newVal;
1911            });
1912            scope.$watch('ngModel', function (newVal) {
1913                if (newVal == scope.selectedOption.Id) return;
1914                scope.selectedOption = _.find(scope.optionsArray, function (o) { return o.Id == newVal; });
1915            });
1916        }
1917    };
1918}]);
1919
1920/*Select filter*/
1921angular.module("DataUI").directive('uiCustomListFilter', ['CommonApi', '$compile', function (CommonApi, $compile) {
1922    return {
1923        restrict: 'A',
1924        priority: 100,
1925        scope: {
1926            ngModel: '=ngModel',
1927            _apiName: '@uiApiName', //<--Obsolete because it conflicts with the parent scope API
1928            apiName: '@uiListApiName'
1929        },
1930        template: [
1931            '<div></div>'
1932        ].join(''),
1933        transclude:true,
1934        link: function (scope, element, attrs, ctrl, transcludeFn) {
1935            scope.selectedOption = { Id: '', Label: '' };
1936            scope.$data = new Array();
1937            if (scope.ngModel) scope.selectedOption.Id = scope.ngModel;
1938
1939            scope.innerTemplate = '<div>{{$data | json}}</div>';
1940            transcludeFn(scope, function (content, innerScope) {
1941                element.append(content);
1942            }, null);
1943
1944            /*Build filter*/
1945            var _buildFilter = function () {
1946                //Load from API
1947                var _apiName = scope.apiName || scope._apiName;
1948                if (_apiName) {
1949                    CommonApi.all(_apiName).getList().then(function (data) {
1950                        scope.$data = data;
1951                    });
1952                };
1953            }
1954
1955            var buildFilter = _.debounce(_buildFilter, 100);
1956            scope.$watch('_apiName', buildFilter);
1957            scope.$watch('apiName', buildFilter);
1958
1959            /*Change Filter*/
1960            scope.select = function (newVal) {
1961                scope.selectedOption = newVal;
1962            }
1963            scope.toggle = function (newVal) {
1964                if (scope.isSelected(newVal)) {
1965                    scope.select(null);
1966                }
1967                else {
1968                    scope.select(newVal);
1969                }
1970            }
1971            scope.isSelected = function (v) {
1972                return (v && scope.selectedOption && v.Id == scope.selectedOption.Id);
1973            }
1974            scope.$watch('selectedOption.Id', function (newVal) {
1975                scope.ngModel = newVal;
1976            });
1977            scope.$watch('ngModel', function (newVal) {
1978                if (scope.selectedOption && newVal == scope.selectedOption.Id) return;
1979                scope.selectedOption = _.find(scope.$data, function (o) { return o.Id == newVal; });
1980            });
1981
1982        }
1983    };
1984}]);
1985
1986
1987/*Date range filters*/
1988angular.module("DataUI").directive('uiDateRangeFilter', [function () {
1989    return {
1990        restrict: 'A',
1991        priority: 1000,
1992        scope: {
1993            filter: '=?uiDateRangeFilter',
1994            from: '=?uiModelMin',
1995            to: '=?uiModelMax',
1996            options: '@uiOptions',
1997            defaultOption: '@uiDefaultOption'
1998        },
1999        template: '<div class="btn-group"><button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><span class="button-text">{{filterValue.selectedOption.label}}</span> <span class="caret"></span></button><ul class="dropdown-menu"><li  ng-repeat="option in optionsArray" ng-click="filterValue.selectedOption.id=option.id" ng-class="{\'active\':filterValue.selectedOption.id==option.id}"><a href="#">{{option.label}}</a></li></ul></div>',
2000        link: function (scope, element, attrs) {
2001            scope.filterValue = { selectedOption: {} };
2002            scope.optionsArray = new Array();
2003
2004
2005            /*Utility to parse text to date intervals
2006                To Do: Create text to date interval service
2007                To do: Add rules for Up To and From To enable setting only start or end dates or both.
2008            */
2009            var parseHumanDateString = function (newVal) {
2010                //Will retur this structure: with start, end and isValid
2011
2012                /*Regular expressions for formats*/
2013                var start, end, isValid = false;
2014                //Today
2015                if (/today/gi.test(newVal)) {
2016                    start = moment().startOf('day');
2017                    end = start.clone().endOf('day');
2018                    isValid = true;
2019                }
2020                //Yesterday
2021                else if (/yesterday/gi.test(newVal)) {
2022                    start = moment().subtract(1, 'day').startOf('day');
2023                    end = start.clone().endOf('day');
2024                    isValid = true;
2025                }
2026                //tomorow
2027                else if (/tomor/gi.test(newVal)) {
2028                    start = moment().add(1, 'day').startOf('day');
2029                    end = start.clone().endOf('day');
2030                    isValid = true;
2031                }
2032                //xx days ago
2033                else if (/(\d{1,3}) (year|month|week|day|hour|minute|second)(s)? ago/gi.test(newVal)) {
2034                    var r = /(\d{1,3}) (year|month|week|day|hour|minute|second)(s)? ago/gi;
2035                    var m = r.exec(newVal);
2036                    start = moment().subtract(m[1], m[2]).startOf(m[2]);
2037                    end = start.clone().endOf(m[2]);
2038                    isValid = true;
2039                }
2040                //Last xx Days, Months, ...
2041                else if (/last (\d{1,3})?.*(year|month|week|day|hour|minute|second)(s)?/gi.test(newVal)) {
2042                    var r = /last (\d{1,3})?.*(year|month|week|day|hour|minute|second)(s)?/gi;
2043                    var m = r.exec(newVal);
2044                    start = moment().subtract(m[1] || 1, m[2] || 'day').startOf(m[2] || 'day');
2045                    end = start.clone().add((m[1] || 1) , m[2] || 'day').endOf(m[2] || 'day');
2046                    isValid = true;
2047                }
2048                //Next xx Days, Months, ...
2049                else if (/next (\d{1,3})?.*(year|month|week|day|hour|minute|second)(s)?/gi.test(newVal)) {
2050                    var r = /next (\d{1,3})?.*(year|month|week|day|hour|minute|second)(s)?/gi;
2051                    var m = r.exec(newVal);
2052                    start = moment().add(0, m[2] || 'day').startOf(m[2] || 'day');
2053                    end = start.clone().add(m[1] || 0, m[2] || 'day').endOf(m[2] || 'day');
2054                    isValid = true;
2055                }
2056                //Day, Month, year before
2057                else if (/(year|month|week|day|hour|minute|second) before/gi.test(newVal)) {
2058                    var r = /(year|month|week|day|hour|minute|second) before/gi;
2059                    var m = r.exec(newVal);
2060                    start = moment().add(-1, m[1]).startOf(m[1] || 'day');
2061                    end = start.clone().endOf(m[1] || 'day');
2062                    isValid = true;
2063                }
2064                //This Day, Month, ...
2065                else if (/This (year|month|week|day|hour|minute|second)(s)?/gi.test(newVal)) {
2066                    var r = /This (year|month|week|day|hour|minute|second)(s)?/gi;
2067                    var m = r.exec(newVal);
2068                    start = moment().startOf(m[1] || 'day');
2069                    end = start.clone().endOf(m[1] || 'day');
2070                    isValid = true;
2071                }
2072                else {
2073                }
2074
2075                return { start: start, end: end, isValid: isValid };
2076            }
2077
2078
2079            /*Change Filter*/
2080            var setFilterFromText = function (newVal) {
2081
2082                if (!newVal || newVal == 'ALL') {
2083                    //Null clears filters
2084                    scope.filterValue.selectedLabel = scope.filterValue.selectedText;
2085                    scope.filterValue.from = null;
2086                    scope.filterValue.to = null;
2087                    if (attrs.uiDateRangeFilter) scope.filter = { range: { from: null, to: null } };
2088                    if (attrs.uiModelMin) scope.from = null;
2089                    if (attrs.uiModelMax) scope.to = null;
2090                    return;
2091                }
2092
2093                var _interval = parseHumanDateString(newVal);
2094
2095                if (_interval.isValid) {
2096                    scope.filterValue.selectedLabel = scope.filterValue.selectedText;
2097                    scope.filterValue.from = _interval.start;
2098                    scope.filterValue.to = _interval.end;
2099                    if (attrs.uiDateRangeFilter) scope.filter = { range: { from: _interval.start.toISOString(), to: _interval.end.toISOString() } };
2100                    if (attrs.uiModelMin) scope.from = _interval.start.toISOString();
2101                    if (attrs.uiModelMax) scope.to = _interval.end.toISOString();
2102                }
2103
2104            };
2105            scope.$watch('filterValue.selectedOption.id', setFilterFromText);
2106
2107            /*Build filter*/
2108            scope.buildFilter = function () {
2109                /*Options*/
2110                if (scope.options) {
2111                    scope.optionsArray = new Array();
2112                    _.forEach(scope.options.split(','), function (o) {
2113                        var p = o.split('|');
2114                        var id = p[0]; var label = p[1] || p[0];
2115                        scope.optionsArray.push({id:id,label:label});
2116                    });
2117                }
2118                /*Default selection*/
2119                if (!scope.filterValue.selectedOption.id && scope.defaultOption) {
2120                    var p = scope.defaultOption.split('|');
2121                    var id = p[0]; var label = p[1] || p[0];
2122                    scope.filterValue.selectedOption = { id: id, label: label };
2123                };
2124                setFilterFromText(scope.filterValue.selectedOption.id);
2125            }
2126
2127            scope.$watch('uiOptions', scope.buildFilter);
2128            scope.$watch('uiDefaultOption', scope.buildFilter);
2129            //init
2130            scope.buildFilter();
2131
2132            /*Filter changed => reflect changes in selected label*/
2133            var filterChanged = function () {
2134                var _start = moment.invalid();
2135                var _end = moment.invalid();
2136
2137                if (attrs.uiDateRangeFilter) _start = moment(scope.filter.range.from);
2138                if (attrs.uiDateRangeFilter) _end = moment(scope.filter.range.to);
2139                if (attrs.uiModelMin) _start = moment(scope.from);
2140                if (attrs.uiModelMax) _end = moment(scope.to);
2141
2142
2143                //TODO: Get range label from Options.
2144
2145                if (_start.isValid() && _end.isValid()) {
2146                    scope.filterValue.selectedOption.label = _start.format("MMM D") + " - " + _end.format("MMM D");
2147                }
2148                else if (_start.isValid())
2149                {
2150                    scope.filterValue.selectedOption.label = _start.format("MMM D");
2151                }
2152                else if (_end.isValid())
2153                {
2154                    scope.filterValue.selectedOption.label = _end.format("MMM D");
2155                }
2156                else
2157                {
2158                    scope.filterValue.selectedOption.label = "";
2159                }
2160            };
2161            scope.$watch('filter.range.from', filterChanged);
2162            scope.$watch('filter.range.to', filterChanged);
2163            scope.$watch('from', filterChanged);
2164            scope.$watch('to', filterChanged);
2165
2166
2167            
2168        }
2169    };
2170}]);
2171
2172
2173/*Date range slider*/
2174angular.module("DataUI").directive('uiDateRangeSlider', ['$timeout', function ($timeout) {
2175    return {
2176        restrict: 'A',
2177        priority: 1000,
2178        scope: {
2179            from: '=uiModelMin',
2180            to: '=uiModelMax',
2181            rangeMonths: '@uiRangeMonths'
2182        },
2183        template: '<rzslider class="ui-slider" rz-slider-model="slider.min" rz-slider-high="slider.max" rz-slider-options="slider.options"></rzslider>',
2184        link: function (scope, element, attrs) {
2185            scope.slider = {
2186                min: moment(scope.from).unix(),
2187                max: moment(scope.to).unix(),
2188                options: {
2189                    floor: moment().subtract(scope.rangeMonths || 3, 'month').startOf('day').unix(),/*To Do: Pass as attribute*/
2190                    ceil: moment().add(scope.rangeMonths || 3, 'month').startOf('day').unix(),/*To Do: Pass as attribute*/
2191                    step: 24 * 60 * 60,
2192                    draggableRange: true,
2193                    translate: function (value) {
2194                        return moment.unix(value).format('MMM D');
2195                    },
2196                    onEnd: function (sliderId, modelValue, highValue, pointerType) {
2197                        scope.from = moment.unix(modelValue).startOf('day').toISOString();
2198                        scope.to = moment.unix(highValue).endOf('day').toISOString();
2199                    }
2200                }
2201            };
2202
2203            /*Filter changed => reflect changes on scope To slider*/
2204            var filterChanged = function () {
2205                scope.slider.min = moment(scope.from).startOf('day').unix();
2206                scope.slider.max = moment(scope.to).startOf('day').unix();
2207
2208                if (scope.slider.min < scope.slider.options.floor) scope.slider.options.floor = scope.slider.min;
2209                if (scope.slider.max > scope.slider.options.ceil) scope.slider.options.ceil = scope.slider.max;
2210
2211                $timeout(function () { scope.$broadcast('rzSliderForceRender'); });
2212            };
2213            scope.$watch('from', filterChanged);
2214            scope.$watch('to', filterChanged);
2215
2216        }
2217    };
2218}]);
2219
2220/*Pagination*/
2221angular.module("DataUI").directive('uiPagination', [function () {
2222    return {
2223        restrict: 'A',
2224        priority: 1000,
2225        scope: {
2226            listSettings: '=uiPagination'
2227        },
2228        templateUrl: 'uiTablePagination.html',
2229        link: function (scope, element, attrs) {
2230            scope.pagination = {
2231                showPrev: false,
2232                showNext: false,
2233                firstPage: 0,
2234                middlePages: new Array(),
2235                middleSize: 3,
2236                lastPage: 0,
2237                pagesize: 20,
2238                total:0
2239            }
2240
2241            scope.buildPages = function () {
2242                scope.pagination.total = (scope.listSettings || {}).total || 0;
2243                scope.pagination.page = (scope.listSettings || {}).page || 0;
2244                scope.pagination.pagesize = (scope.listSettings || {}).pagesize || 20;
2245
2246                scope.pagination.showFirst = true;
2247                scope.pagination.showLast = true;
2248                scope.pagination.lastPage = Math.max(0, Math.floor((scope.pagination.total-1) / scope.pagination.pagesize));
2249                scope.pagination.showPrev = (scope.pagination.page > 0)
2250                scope.pagination.showNext = (scope.pagination.page < scope.pagination.lastPage)
2251
2252                var middleStart = Math.max(0, scope.pagination.page - Math.floor((scope.pagination.middleSize-1)/2));
2253                var middleEnd = Math.min(scope.pagination.lastPage ,middleStart + scope.pagination.middleSize);
2254                scope.pagination.middlePages = new Array();
2255                for (var p = middleStart; p <= middleEnd; p++)
2256                {
2257                    scope.pagination.middlePages.push(p);
2258                    if (p == scope.pagination.firstPage) scope.pagination.showFirst = false;
2259                    if (p == scope.pagination.lastPage) scope.pagination.showLast = false;
2260                }
2261                if (scope.pagination.lastPage < scope.pagination.page)
2262                {
2263                    scope.ChangePage(scope.pagination.lastPage);
2264                }
2265
2266                if (scope.pagination.total <= scope.pagination.pagesize) {
2267                    scope.pagination.showFirst = false;
2268                    scope.pagination.showLast = false;
2269                    scope.pagination.middlePages = new Array();
2270                }
2271            }
2272            scope.$watch('listSettings.total', scope.buildPages);
2273            scope.$watch('listSettings.page', scope.buildPages);
2274            scope.$watch('listSettings.pagesize', scope.buildPages);
2275
2276            scope.ChangePage = function (newPage) {
2277                scope.pagination.page = newPage;
2278                if (scope.pagination.page < 0) scope.pagination.page = 0;
2279                if (scope.pagination.page > scope.pagination.lastPage) scope.pagination.page = scope.pagination.lastPage;
2280                scope.listSettings.page = scope.pagination.page;
2281            }
2282        }
2283    };
2284}]);
2285
2286angular.module("DataUI").directive('uiPaginationSize', [function () {
2287    return {
2288        restrict: 'A',
2289        priority: 1000,
2290        scope: {
2291            listSettings: '=uiPaginationSize'
2292        },
2293        templateUrl: 'uiTablePaginationSize.html',
2294        link: function (scope, element, attrs) {
2295            scope.pagination = {
2296                pagesize: 20,
2297                pagesizes: new Array()
2298            }
2299
2300            scope.buildPages = function () {
2301                scope.pagination.pagesize = scope.listSettings.pagesize || 20;
2302                var pageSizes = scope.listSettings.pagesizes;
2303                if (!pageSizes || pageSizes == 0) pageSizes = [5, 10, 20, 50, 100, 200];
2304                /*Todo: Only allow page sizes that are smaller that the actual record count*/
2305                scope.pagination.pagesizes = pageSizes;
2306            }
2307            scope.$watch('listSettings.pagesize', scope.buildPages);
2308            scope.$watch('listSettings.total', scope.buildPages);
2309
2310            scope.Changepagesize = function (newpagesize) {
2311                scope.pagination.pagesize = newpagesize;
2312                scope.listSettings.pagesize = scope.pagination.pagesize;
2313            }
2314        }
2315    };
2316}]);
2317
2318angular.module("DataUI").directive('uiOrderBy', [function () {
2319    return {
2320        restrict: 'A',
2321        priority: 1000,
2322        scope: false, /*will depend on parent scope listSettings*/
2323        link: function (scope, element, attrs) {
2324            $(element).addClass('orderBy');
2325
2326            $(element).click(function () {
2327                scope.$applyAsync(function () {
2328                    var thisOrder = attrs.uiOrderBy;
2329                    if (!thisOrder) return;
2330                    var current = "";
2331                    if (scope.listSettings.order) current = scope.listSettings.order.join(',');
2332                    var newOrder = thisOrder;
2333                    if (newOrder == current || '+' + newOrder == current)
2334                    { newOrder = '-' + newOrder;}
2335                    scope.listSettings.order = [newOrder];
2336                });
2337            });
2338
2339            var setIcon = function () {
2340                $(element).removeClass('orderByAsc').removeClass('orderByDesc');
2341                var thisOrder = attrs.uiOrderBy;
2342                if (!thisOrder || !scope.listSettings) return;
2343                _.forEach(scope.listSettings.order, function (o) {
2344                    if (thisOrder == o || '+' + thisOrder == o) {
2345                        $(element).addClass('orderByAsc');
2346                    }
2347                    else if ('-' + thisOrder == o) {
2348                        $(element).addClass('orderByDesc');
2349                    }
2350                });
2351            }
2352
2353            scope.$watch('listSettings.order', setIcon, true);
2354            
2355        }
2356    };
2357}]);
2358
2359/*Preforms hierarchical parent child data transformation. Result will be stored in an array $data where each item has a Children array*/
2360angular.module("DataUI").directive('uiHierarchyData', ['$timeout', function ($timeout) {
2361    return {
2362        restrict: 'A',
2363        scope: {
2364            data: '=uiData',
2365            idField: '@?uiIdField',
2366            parrentIdField: '@?uiParentIdField'
2367        },
2368        transclude: true,
2369        link: function (scope, element, attrs, ctrl, transcludeFn) {
2370            scope.parentScope = scope.$parent;
2371            scope.$data = new Array();
2372            scope.root = {};
2373
2374            transcludeFn(scope, function (clone, scope) {
2375                element.append(clone);
2376            });
2377
2378            var _doTransform = function () {
2379                scope.root.children = [];
2380
2381                //Clear previous child calculations:
2382                _.forEach(scope.data, function (o) { if (o && o.children) o.children = []; });
2383                //Assign each child to its parent. When no parent found assign to root.
2384                _.forEach(scope.data, function (o) {
2385                    var pID = (o && (o[(scope.parrentIdField || 'ParentId')] || 0));
2386                    var p = _.find(scope.data, function (i) { return ((pID) == (i[(scope.idField || 'Id')] || 0)); });
2387                    if (!p) p = scope.root;
2388                    if (!p.children) p.children = [];
2389                    p.children.push(o);
2390                });
2391
2392                scope.$data = scope.root.children;
2393                scope.$applyAsync();
2394            }
2395
2396            var doTransform = _.debounce(_doTransform, 50);
2397            scope.$watchCollection('data', doTransform);
2398        }
2399    }
2400}]);
2401
2402
2403/*Converts list data into an object where each key and value will be mapped to list data fields. Result will be stored in an array $data as an object*/
2404angular.module("DataUI").directive('uiDataKeyBy', ['$timeout', function ($timeout) {
2405    return {
2406        restrict: 'A',
2407        scope: {
2408            data: '=uiDataKeyBy',
2409            aggBy: '@uiDataKeyField',
2410            //valuesBy: '@uiDataValuesField',
2411        },
2412        transclude: true,
2413        link: function (scope, element, attrs, ctrl, transcludeFn) {
2414            scope.$data = {};
2415
2416            transcludeFn(scope, function (clone, scope) {element.append(clone);});
2417
2418            var _doAgg = function () {
2419
2420                var aggFn = function (i) { return i[scope.aggBy]; };
2421                //var mapFn = function (i) { return (!scope.valuesBy ? i : i[scope.valuesBy]); };
2422
2423                scope.$data = _.chain(scope.data).keyBy(aggFn).value(
2423);
2424
2425                scope.$applyAsync();
2426            }
2427            var doAgg = _.debounce(_doAgg, 50);
2428            scope.$watchCollection('data', doAgg);
2429
2430        }
2431    }
2432}]);
2433
2434/*Preforms a agg function like group by and allows calculation of one member. Result will be stored in an array $data*/
2435angular.module("DataUI").directive('uiAggregateData', ['$timeout', function ($timeout) {
2436    return {
2437        restrict: 'A',
2438        scope: {
2439            data: '=uiAggregateData',
2440            aggBy: '@uiAggregateBy',
2441            aggFunc: '@uiAggregateFunction',
2442            aggMetric: '@uiAggregateMetric'
2443        },
2444        transclude: true,
2445        link: function (scope, element, attrs, ctrl, transcludeFn) {
2446            scope.$data = new Array();
2447
2448            transcludeFn(scope, function (clone, scope) {
2449                element.append(clone);
2450            });
2451
2452            var _doAgg = function () {
2453                var aggFn = function (i) { return i[scope.aggBy]; };
2454                var mapFn = function (i) { var r = {}; r[(scope.aggBy || 'Key')] = (i[0][scope.aggBy] || 'N.D.'); return r; };
2455                //Multimple?
2456                var nultipleAggBy = scope.aggBy.split(',');
2457                if (nultipleAggBy.length > 1) {
2458                    aggFn = function (i) { return (_.map(nultipleAggBy, function (a) { return i[a] || ''; }).join('-')); };
2459                    var mapFn = function (i) { var r = {}; _.forEach(nultipleAggBy, function (a) { r[a] = (i[0][a] || 'N.D.'); }); return r; };
2460                }
2461                //aggBy is the group by function.
2462                scope.$data = _(scope.data)
2463                    .groupBy(aggFn)
2464                    .map(function (objs, key) {
2465                        var r = mapFn(objs);
2466                        r[(scope.aggMetric || 'Value')] = (scope.aggFunc == 'sum' ? _.sumBy(objs, scope.aggMetric) : objs.length);
2467                        return r;
2468                    })
2469                    .value();
2470                scope.$applyAsync();
2471            }
2472            var doAgg = _.debounce(_doAgg, 50);
2473            scope.$watchCollection('data', doAgg);
2474
2475        }
2476    }
2477}]);
2478
2479/*Preforms a agg function like group by and allows calculation of one member. Result will be stored in an array $data*/
2480angular.module("DataUI").directive('uiDataAggregate', ['$timeout', function ($timeout) {
2481    return {
2482        restrict: 'A',
2483        scope: {
2484            data: '=uiData',
2485            destVar: '=?uiDataOutputTo',
2486            aggFn: '@?uiGroupByFn',
2487            aggBy: '@?uiGroupBy',
2488            calcFn: '@?uiAggFn',
2489            sortBy: '@?uiSortBy',
2490            alsoWatch:'@?uiWatch',
2491            filterFn: '@?uiFilterFn',
2492        },
2493        transclude: true,
2494        link: function (scope, element, attrs, ctrl, transcludeFn) {
2495            scope.$data = new Array();
2496
2497            transcludeFn(scope, function (clone, scope) {
2498                element.append(clone);
2499            });
2500
2501            var aggFn = function (i) { return i.Id; }; //Default to id
2502            if (scope.aggBy) aggFn = (new Function("with(this) { return function(i){ return _.get(i,'" + scope.aggBy + "');} }")).call();
2503            if (scope.aggFn) aggFn = (new Function("with(this) { return " + scope.aggFn + "}")).call();
2504            var calcFn = function (i) { return i.length; }; //Default to count
2505            if (scope.calcFn) calcFn = (new Function("with(this) { return " + scope.calcFn + "}")).call();
2506            var filterFn = function (i) { return true;};
2507            if (scope.filterFn) filterFn = (new Function("with(this) { return " + scope.filterFn + "}")).call(scope.$parent);
2508
2509            var _doAgg = function () {
2510                var d = _(scope.data)
2511                    .filter(filterFn)
2512                    .groupBy(aggFn)
2513                    .map(function (objs, key) {
2514                        var r = calcFn(objs);
2515                        _.set(r, (scope.aggBy || 'Key'), (key || 'N.D.'));
2516                        return r;
2517                    })
2518                    .sortBy(scope.aggBy || 'Key')
2519                    .value();
2520                if (scope.sortBy) {
2521                    var sortOrder = scope.sortBy, sortOrderDesc = false;
2522                    if (sortOrder.substring(0, 1) == '-') { sortOrderDesc = true; sortOrder = sortOrder.substring(1);}
2523                    d = _.sortBy(d, sortOrder);
2524                    if (sortOrderDesc) d = d.reverse();
2525                }
2526                scope.$data = d;
2527                if (attrs.uiDataOutputTo) scope.destVar = d;
2528                scope.$applyAsync();
2529            }
2530            var doAgg = _.debounce(_doAgg, 100);
2531            scope.$watch('data', doAgg);
2532            scope.$watchCollection('data', doAgg);
2533            scope.$watch('alsoWatch', doAgg);
2534
2535        }
2536    }
2537}]);
2538
2539/*Preforms a filter function. Result will be stored in an array $data*/
2540angular.module("DataUI").directive('uiDataFilter', ['$timeout', function ($timeout) {
2541    return {
2542        restrict: 'A',
2543        scope: {
2544            data: '=uiData',
2545            destVar: '=?uiDataOutputTo',
2546            filterFn: '@?uiFilterFn'
2547        },
2548        transclude: true,
2549        link: function (scope, element, attrs, ctrl, transcludeFn) {
2550            scope.$data = new Array();
2551
2552            transcludeFn(scope, function (clone, scope) {
2553                element.append(clone);
2554            });
2555
2556            var filterFn = function (i) { return true; };
2557            if (scope.filterFn) filterFn = (new Function("with(this) { return " + scope.filterFn + "}")).call(scope.$parent);
2558
2559            var _doAgg = function () {
2560                var d = _(scope.data)
2561                    .filter(filterFn)
2562                    .value();
2563                scope.$data = d;
2564                if (attrs.uiDataOutputTo) scope.destVar = d;
2565                scope.$applyAsync();
2566            }
2567            var doAgg = _.debounce(_doAgg, 50);
2568            scope.$watch('data', doAgg);
2569            scope.$watchCollection('data', doAgg);
2570            scope.$watch('alsoWatch', doAgg);
2571            scope.$watch('filterFn', doAgg);
2572
2573        }
2574    }
2575}]);
2576/*Stores ui-data attribute value in the root scope's variable specified by the attribute ui-set-in-root-scope*/
2577angular.module("DataUI").directive('uiSetInRootScope', ['$rootScope', function ($rootScope) {
2578    return {
2579        restrict: 'A',
2580        scope: {
2581            data: '=uiData',
2582            destVar: '@uiSetInRootScope'
2583        },
2584        link: function (scope, element, attrs) {
2585            scope.$watch('data', function (newVal) {
2586                if (scope.destVar) $rootScope[scope.destVar]= newVal;
2587            });
2588        }
2589    }
2590}]);
2591
2592angular.module("DataUI").directive('uiMockLoad', ['ParseContextUtils','$timeout', function (ParseContextUtils,$timeout) {
2593    return {
2594        restrict: 'A',
2595        transclude: {
2596            uiMockData: "?uiMockData"
2597        },
2598        scope: false,
2599        template: [
2600            '<div>',
2601            '    <div ng-transclude="uiMockData" style="display:none;"></div>',
2602            '</div>'
2603        ].join(''),
2604        link: function (scope, element, attrs, ctrl, transcludeFn) {
2605
2606            //Get the onclick js
2607            scope.mockData = "";
2608            transcludeFn(function (content) {
2609                scope.mockData = content.html();
2610            }, null, 'uiMockData');
2611
2612
2613            scope.setMockData = function () {
2614                scope.inProgress = false;
2615                scope.$data = scope.$eval(scope.mockData);
2616            }
2617            scope.loadData = function () {
2618                scope.inProgress = true;
2619                $timeout(scope.setMockData,2000);
2620            }
2621
2622            //init
2623            scope.loadData();
2624        }
2625    }
2626}]);
2627
2628angular.module("DataUI").controller('UISelectable', ["$scope", function ($scope) {
2629
2630    $scope.isAllSelected = false;
2631
2632    $scope.toggleAllSelection = function (filter) {
2633        if (!$scope.$data) return;
2634
2635        var data = $scope.$data;
2636        //Todo: change allSelected and apply to all rows.
2637        $scope.isAllSelected = !$scope.isAllSelected;
2638
2639        // Apply filtering in rows 
2640        if (filter) data = _.filter(data, filter);
2641
2642        _.forEach(data, function (i) { i.isSelected = $scope.isAllSelected; });
2643
2644        rebuildSelected();
2645    }
2646
2647    var _selectedRows = [];
2648    var _selectedRowsId = [];
2649    var _rebuildSelected = function () {
2650        _selectedRows.length = 0;
2651        _selectedRowsId.length = 0;
2652        if (!$scope.$data) return;
2653
2654        _($scope.$data)
2655            .filter(function (i) { return i.isSelected === true; })
2656            .forEach(function (i) { _selectedRows.push(i); _selectedRowsId.push(i.Id); });
2657
2658        $scope.$applyAsync();
2659    }
2660    var rebuildSelected = _.throttle(_rebuildSelected, 500, { 'trailing': true, 'leading': true });
2661
2662    $scope.getSelectedRows = function () {
2663        rebuildSelected();
2664        return _selectedRows;
2665    }
2666
2667    $scope.getSelectedRowsId = function () {
2668        rebuildSelected();
2669        return _selectedRowsId;
2670    }
2671
2672    $scope.getSelectedRowsCustomField = function (fieldName) {
2673        if (!$scope.$data) return [];
2674        return _($scope.$data)
2675            .filter(function (i) { return i.isSelected === true; })
2676            .map(function (i) { return i[fieldName]; })
2677            .value();
2678    }
2679
2680}]);
2681
2682
2683angular.module("DataUI").directive('uiDataMap', ['$timeout', function ($timeout) {
2684    return {
2685        restrict: 'A',
2686        scope: {
2687            data: '=uiData',
2688            calcFn: '@?uiMapFn',
2689            filterFn: '@?uiFilterFn'
2690        },
2691        transclude: true,
2692        link: function (scope, element, attrs, ctrl, transcludeFn) {
2693            scope.$data = new Array();
2694
2695            transcludeFn(scope, function (clone, scope) {
2696                element.append(clone);
2697            });
2698
2699            var calcFn = function (i) { return i.length; }; //Default to count
2700            var filterFn = function (i) { return true; };
2701            if (scope.calcFn) calcFn = (new Function("with(this) { return " + scope.calcFn + "}")).call();
2702            if (scope.filterFn) filterFn = (new Function("with(this) { return " + scope.filterFn + "}")).call();
2703
2704            var _doMap = function () {
2705
2706                var d = _(scope.data).map(calcFn).filter(filterFn).value();
2707                
2708                scope.$data = d;
2709
2710                scope.$applyAsync();
2711            }
2712
2713            var doMap = _.debounce(_doMap, 100);
2714            scope.$watch('data', doMap);
2715
2716        }
2717    }
2718}]);
2719
2720/*Simple directive to set $data isolated scope*/
2721angular.module("DataUI").directive('uiDataScope', ['$timeout', function ($timeout) {
2722    return {
2723        restrict: 'A',
2724        scope: {
2725            data: '=uiData',
2726            onChange: '&?uiOnChange'
2727        },
2728        transclude: true,
2729        link: function (scope, element, attrs, ctrl, transcludeFn) {
2730            scope.$data = new Array();
2731
2732            transcludeFn(scope, function (clone, scope) {
2733                element.append(clone);
2734            });
2735
2736            var _doMap = function () {
2737                scope.$data = scope.data;
2738                if (scope.onChange) scope.onChange();
2739                scope.$applyAsync();
2740            }
2741
2742            var doMap = _.debounce(_doMap, 100);
2743            scope.$watch('data', doMap);
2744        }
2745    }
2746}]);
2747
2748
2749/*Simple directive to parse items content from string to object*/
2750angular.module("DataUI").directive('uiParseItemsContent', ['$timeout', function ($timeout) {
2751    return {
2752        restrict: 'A',
2753        scope: false,
2754        link: function (scope, element, attrs, ctrl, transcludeFn) {
2755            var _doMap = function () {
2756                if (scope.$data) {
2757                    _.forEach(scope.$data, function (o) {
2758                        if (o.Content) {
2759                            try { o.Content = JSON.parse(o.Content); } catch(ex) { }
2760                        }
2761                    });
2762                    scope.$applyAsync();
2763                }
2764            }
2765            scope.$watch('$data', _doMap);
2766        }
2767    }
2768}]);
2769
2770/*Simple directive to join 2 arrays by one key field*/
2771angular.module("DataUI").directive('uiDataJoin', ['$timeout', function ($timeout) {
2772    return {
2773        restrict: 'A',
2774        scope: {
2775            data1: '=uiData1',
2776            data2: '=uiData2',
2777            dataKey: '@uiDataKey'
2778        },
2779        transclude: true,
2780        link: function (scope, element, attrs, ctrl, transcludeFn) {
2781            scope.$data = new Array();
2782
2783            transcludeFn(scope, function (clone, scope) {
2784                element.append(clone);
2785            });
2786
2787            var _doMap = function () {
2788                var merged = _.merge(_.keyBy(scope.data1, scope.dataKey), _.keyBy(scope.data2, scope.dataKey));
2789                scope.$data = _.values(merged);
2790                scope.$applyAsync();
2791            }
2792
2793            var doMap = _.debounce(_doMap, 100);
2794            scope.$watch('data1', doMap);
2795            scope.$watch('data2', doMap);
2796        }
2797    }
2798}]);
2799
2800

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.