Test Setup Failed
Push — master ( 11cd9d...8453e3 )
by
unknown
03:59
created

activity-list-view.js ➔ ... ➔ unescapeHTML   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
nc 1
dl 0
loc 9
rs 9.6666
nop 1
1
define(function(require) {
2
    'use strict';
3
4
    var ActivityListView;
5
    var $ = require('jquery');
6
    var _ = require('underscore');
7
    var __ = require('orotranslation/js/translator');
8
    var routing = require('routing');
9
    var mediator = require('oroui/js/mediator');
10
    var DialogWidget = require('oro/dialog-widget');
11
    var DeleteConfirmation = require('oroui/js/delete-confirmation');
12
    var BaseCollectionView = require('oroui/js/app/views/base/collection-view');
13
14
    ActivityListView = BaseCollectionView.extend({
15
        options: {
16
            configuration: {},
17
            template: null,
18
            itemTemplate: null,
19
            itemViewIdPrefix: 'activity-',
20
            listSelector: '.items.list-box',
21
            fallbackSelector: '.no-data',
22
            loadingSelector: '.loading-mask',
23
            listWidgetSelector: '.activities-container .activity-list-widget',
24
            activityListSelector: '.activity-list',
25
            collection: null,
26
            urls: {
27
                viewItem: null,
28
                updateItem: null,
29
                deleteItem: null
30
            },
31
            messages: {},
32
            ignoreHead: false,
33
            doNotFetch: false,
34
            reloadOnAdd: true,
35
            reloadOnUpdate: true,
36
            triggerRefreshEvent: true
37
        },
38
39
        listen: {
40
            'toView collection': '_viewItem',
41
            'toViewGroup collection': '_viewGroup',
42
            'toEdit collection': '_editItem',
43
            'toDelete collection': '_deleteItem'
44
        },
45
46
        EDIT_DIALOG_CONFIGURATION_DEFAULTS: {
47
            regionEnabled: false,
48
            incrementalPosition: false,
49
            alias: 'activity_list:item:update',
50
            dialogOptions: {
51
                modal: true,
52
                resizable: true,
53
                width: 515,
54
                autoResize: true
55
            }
56
        },
57
58
        initialize: function(options) {
59
            this.options = _.defaults(options || {}, this.options);
60
61
            _.defaults(this.options.messages, {
62
                editDialogTitle: __('oro.activitylist.edit_title'),
63
                itemSaved: __('oro.activitylist.item_saved'),
64
                itemRemoved: __('oro.activitylist.item_removed'),
65
66
                deleteConfirmation: __('oro.activitylist.delete_confirmation'),
67
                deleteItemError: __('oro.activitylist.delete_error'),
68
69
                loadItemsError: __('oro.activitylist.load_error'),
70
                forbiddenError: __('oro.activitylist.forbidden_error'),
71
                forbiddenActivityDataError: __('oro.activitylist.forbidden_activity_data_view_error')
72
            });
73
74
            this.template = _.template($(this.options.template).html());
75
            this.isFiltersEmpty = true;
76
            this.gridToolbar = $(
77
                this.options.listWidgetSelector + ' ' + this.options.activityListSelector + ' .grid-toolbar'
78
            );
79
80
            if (this.options.reloadOnAdd) {
81
                /**
82
                 * on adding activity item listen to "widget:doRefresh:activity-list-widget"
83
                 */
84
                mediator.on('widget:doRefresh:activity-list-widget', this._reloadOnAdd, this);
85
            }
86
87
            if (this.options.reloadOnUpdate) {
88
                /**
89
                 * on editing activity item listen to "widget_success:activity_list:item:update"
90
                 */
91
                mediator.on('widget_success:activity_list:item:update', this._reload, this);
92
            }
93
94
            ActivityListView.__super__.initialize.call(this, options);
95
96
            if (!this.doNotFetch) {
97
                this._initPager();
98
            }
99
        },
100
101
        /**
102
         * @inheritDoc
103
         */
104
        dispose: function() {
105
            if (this.disposed) {
106
                return;
107
            }
108
109
            delete this.itemEditDialog;
110
111
            mediator.off('widget:doRefresh:activity-list-widget', this._reloadOnAdd, this);
112
            mediator.off('widget_success:activity_list:item:update', this._reload, this);
113
114
            ActivityListView.__super__.dispose.call(this);
115
        },
116
117
        initItemView: function(model) {
118
            var className = model.getRelatedActivityClass();
119
            var configuration = this.options.configuration[className];
120
            if (this.itemView) {
121
                return new this.itemView({
122
                    autoRender: false,
123
                    model: model,
124
                    configuration: configuration,
125
                    ignoreHead: this.options.ignoreHead
126
                });
127
            } else {
128
                ActivityListView.__super__.render.apply(this, arguments);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
129
            }
130
        },
131
132
        refresh: function() {
133
            this.collection.setPage(1);
134
            this.collection.resetPageFilter();
135
136
            this._reload();
137
138
            if (this.options.triggerRefreshEvent) {
139
                mediator.trigger('widget_success:activity_list:refresh');
140
            }
141
        },
142
143
        _initPager: function() {
144
            if (this.collection.getCount() && this.collection.getPage() === 1) {
145
                this._toggleNext(true);
146
            }
147
148
            if (this.collection.getPage() === 1) {
149
                this._togglePrevious();
150
            } else {
151
                this._togglePrevious(true);
152
            }
153
154
            if (this.collection.getCount() < this.collection.getPageSize()) {
155
                this._toggleNext();
156
            }
157
158
            if (this.collection.getCount() === 0 &&
159
                this.isFiltersEmpty &&
160
                this.collection.getPage() === 1 &&
161
                !this.collection.models.length
162
            ) {
163
                this.gridToolbar.hide();
164
            } else {
165
                this.gridToolbar.show();
166
            }
167
        },
168
169
        /**
170
         * Fetches loading container element
171
         *
172
         *  - returns loading container passed over options,
173
         *    or the view element as default loading container
174
         *
175
         * @returns {HTMLElement|undefined}
176
         * @protected
177
         * @override
178
         */
179
        _getLoadingContainer: function() {
180
            var loadingContainer = this.options.loadingContainer;
181
            if (loadingContainer instanceof $) {
182
                // fetches loading container from options
183
                loadingContainer = loadingContainer.get(0);
184
            }
185
            if (!loadingContainer) {
186
                // uses the element as default loading container
187
                loadingContainer = this.$el.get(0);
188
            }
189
            return loadingContainer;
190
        },
191
192
        goto_previous: function() {
193
            var currentPage = this.collection.getPage();
194
            if (currentPage === 1) {
195
                return;
196
            }
197
198
            if (currentPage === 2) {
199
                this.collection.setPage(1);
200
                this.collection.resetPageFilter();
201
202
                this._reload();
203
            } else {
204
                var nextPage = currentPage - 1;
205
                this.collection.setPage(nextPage);
206
207
                this._setupPageFilterForPrevAction();
208
209
                this._reload();
210
            }
211
212
            this._toggleNext(true);
213
        },
214
        goto_next: function() {
215
            if (this.collection.getCount() < this.collection.getPageSize()) {
216
                return;
217
            }
218
            var currentPage = this.collection.getPage();
219
220
            this.collection.setPage(currentPage + 1);
221
            this.collection.setPageTotal(this.collection.getPageTotal() + 1);
222
223
            this._setupPageFilterForNextAction();
224
225
            this._reload();
226
        },
227
228
        _setupPageFilterForPrevAction: function() {
229
            var model = this.collection.first();
230
            var sameModelIds = this._findSameModelsBySortingField(model);
231
232
            this.collection.setPageFilterDate(model.attributes[this.collection.pager.sortingField]);
233
            this.collection.setPageFilterIds(sameModelIds.length ? sameModelIds : [model.id]);
234
            this.collection.setPageFilterAction('prev');
235
        },
236
        _setupPageFilterForNextAction: function() {
237
            var model = this.collection.last();
238
            var sameModelIds = this._findSameModelsBySortingField(model);
239
240
            this.collection.setPageFilterDate(model.attributes[this.collection.pager.sortingField]);
241
            this.collection.setPageFilterIds(sameModelIds.length ? sameModelIds : [model.id]);
242
            this.collection.setPageFilterAction('next');
243
        },
244
        /**
245
         * Finds the same models in collection by sorting field
246
         * @param model ActivityModel to be used for comparison
247
         */
248
        _findSameModelsBySortingField: function(model) {
249
            var modelIds = [];
250
            var sortingField = this.collection.pager.sortingField;
251
            var sameModels = _.filter(this.collection.models, function(collectionModel) {
252
                return collectionModel.attributes[sortingField] === model.attributes[sortingField];
253
            }, this);
254
            if (sameModels.length) {
255
                modelIds = _.map(sameModels, function(collectionModel) {
256
                    return collectionModel.id;
257
                });
258
            }
259
260
            return modelIds;
261
        },
262
263
        _togglePrevious: function(enable) {
264
            if (_.isUndefined(enable)) {
265
                $(this.options.listWidgetSelector + ' .pagination-previous').addClass('disabled');
266
            } else {
267
                $(this.options.listWidgetSelector + ' .pagination-previous').removeClass('disabled');
268
            }
269
        },
270
271
        _toggleNext: function(enable) {
272
            if (_.isUndefined(enable)) {
273
                $(this.options.listWidgetSelector + ' .pagination-next').addClass('disabled');
274
            } else {
275
                $(this.options.listWidgetSelector + ' .pagination-next').removeClass('disabled');
276
            }
277
        },
278
279
        _reloadOnAdd: function() {
280
            if (this.collection.getPage() === 1) {
281
                this.collection.resetPageFilter();
282
                this._reload();
283
            }
284
        },
285
286
        _reload: function() {
287
            var itemViews;
288
            // please note that _hideLoading will be called in renderAllItems() function
289
            this._showLoading();
290
            if (this.options.doNotFetch) {
291
                this._hideLoading();
292
                return;
293
            }
294
            try {
295
                // store views state
296
                this.oldViewStates = {};
297
                itemViews = this.getItemViews();
298
                this.oldViewStates = _.map(itemViews, function(view) {
299
                    return {
300
                        attrs: view.model.toJSON(),
301
                        collapsed: view.isCollapsed(),
302
                        height: view.$el.height()
303
                    };
304
                });
305
306
                this.collection.fetch({
307
                    reset: true,
308
                    success: _.bind(function() {
309
                        this._initPager();
310
                        this._hideLoading();
311
                    }, this),
312
                    error: _.bind(function(collection, response) {
313
                        this._showLoadItemsError(response.responseJSON || {});
314
                    }, this)
315
                });
316
            } catch (err) {
317
                this._showLoadItemsError(err);
318
            }
319
        },
320
321
        renderAllItems: function() {
322
            var result;
323
            var i;
324
            var view;
325
            var model;
326
            var oldViewState;
327
            var contentLoadedPromises;
328
            var deferredContentLoading;
329
330
            result = ActivityListView.__super__.renderAllItems.apply(this, arguments);
331
332
            contentLoadedPromises = [];
333
334
            if (this.oldViewStates) {
335
                // restore state
336
                for (i = 0; i < this.oldViewStates.length; i++) {
337
                    oldViewState = this.oldViewStates[i];
338
                    model = this.collection.findSameActivity(oldViewState.attrs);
339
                    if (model) {
340
                        view = this.getItemView(model);
341
                        model.set('is_loaded', false);
342
                        if (view && !oldViewState.collapsed) {
343
                            view.toggle();
344
                            view.getAccorditionBody().addClass('in');
345
                            view.getAccorditionToggle().removeClass('collapsed');
346
                            if (view.model.get('isContentLoading')) {
347
                                // if model is loading - need to wait until content will be loaded before _hideLoading()
348
                                // also preserve height during loading
349
                                view.$el.height(oldViewState.height);
350
                                deferredContentLoading = $.Deferred();
351
                                contentLoadedPromises.push(deferredContentLoading);
352
                                view.model.once(
353
                                    'change:isContentLoading',
354
                                    _.bind(function(view, deferredContentLoading) {
355
                                        // reset height
356
                                        view.$el.height('');
357
                                        deferredContentLoading.resolve();
358
                                    }, this, view, deferredContentLoading)
359
                                );
360
                            }
361
                        }
362
                    }
363
                }
364
                delete this.oldViewStates;
365
            }
366
367
            $.when.apply($, contentLoadedPromises).done(_.bind(function() {
368
                this._hideLoading();
369
            }, this));
370
371
            return result;
372
        },
373
374
        _viewItem: function(model) {
375
            this._loadModelContentHTML(model, 'itemView');
376
        },
377
378
        _viewGroup: function(model) {
379
            this._loadModelContentHTML(model, 'groupView');
380
        },
381
382
        _loadModelContentHTML: function(model, actionKey) {
383
            var url = this._getUrl(actionKey, model);
384
            if (model.get('is_loaded') === true) {
385
                return;
386
            }
387
            model.loadContentHTML(url)
388
                .fail(_.bind(function(response) {
389
                    if (response.status === 403) {
390
                        this._showForbiddenActivityDataError(response.responseJSON || {});
391
                    } else {
392
                        this._showLoadItemsError(response.responseJSON || {});
393
                    }
394
                }, this));
395
        },
396
397
        _editItem: function(model, extraOptions) {
398
            if (!this.itemEditDialog) {
399
                var unescapeHTML = function unescapeHTML(unsafe) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable unescapeHTML already seems to be declared on line 399. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
400
                    return unsafe
401
                        .replace(/&nbsp;/g, ' ')
402
                        .replace(/&amp;/g, '&')
403
                        .replace(/&lt;/g, '<')
404
                        .replace(/&gt;/g, '>')
405
                        .replace(/&quot;/g, '\"')
406
                        .replace(/&#039;/g, '\'');
407
                };
408
409
                var dialogConfiguration = $.extend(true, {}, this.EDIT_DIALOG_CONFIGURATION_DEFAULTS, extraOptions, {
410
                    url: this._getUrl('itemEdit', model),
411
                    title: unescapeHTML(model.get('subject')),
412
                    dialogOptions: {
413
                        close: _.bind(function() {
414
                            delete this.itemEditDialog;
415
                        }, this)
416
                    }
417
                });
418
                this.itemEditDialog = new DialogWidget(dialogConfiguration);
419
420
                this.itemEditDialog.render();
421
            }
422
        },
423
424
        _deleteItem: function(model) {
425
            var confirm = new DeleteConfirmation({
426
                content: this._getMessage('deleteConfirmation')
427
            });
428
            confirm.on('ok', _.bind(function() {
429
                this._onItemDelete(model);
430
            }, this));
431
            confirm.open();
432
        },
433
434
        _onItemDelete: function(model) {
435
            this._showLoading();
436
            try {
437
                // in case deleting the last item on page - will show the previous one
438
                if (this.collection.getCount() === 1) {
439
                    // the first page never has pageFilters
440
                    // in case 2nd page and last item deletion - just reset pageFilter, this will give the 1st page
441
                    // in all other cases simulate `Prev` action
442
                    if (this.collection.getPage() <= 2) {
443
                        this.collection.resetPageFilter();
444
                    } else {
445
                        this.collection.setPage(this.collection.getPage() - 1);
446
                        this._setupPageFilterForPrevAction();
447
                    }
448
                }
449
450
                model.destroy({
451
                    wait: true,
452
                    url: this._getUrl('itemDelete', model),
453
                    success: _.bind(function() {
454
                        mediator.execute('showFlashMessage', 'success', this._getMessage('itemRemoved'));
455
                        mediator.trigger('widget_success:activity_list:item:delete');
456
457
                        this._reload();
458
                    }, this),
459
                    error: _.bind(function(model, response) {
460
                        if (!_.isUndefined(response.status) && response.status === 403) {
461
                            this._showForbiddenError(response.responseJSON || {});
462
                        } else {
463
                            this._showDeleteItemError(response.responseJSON || {});
464
                        }
465
                        this._hideLoading();
466
                    }, this)
467
                });
468
            } catch (err) {
469
                this._showDeleteItemError(err);
470
                this._hideLoading();
471
            }
472
        },
473
474
        /**
475
         * Fetches url for certain action
476
         *
477
         * @param {string} actionKey
478
         * @param {Backbone.Model=}model
479
         * @returns {string}
480
         * @protected
481
         */
482
        _getUrl: function(actionKey, model) {
483
            var className = model.getRelatedActivityClass();
484
            var route = this.options.configuration[className].routes[actionKey];
485
            return routing.generate(route, {id: model.get('relatedActivityId')});
486
        },
487
488
        _getMessage: function(labelKey) {
489
            return this.options.messages[labelKey];
490
        },
491
492
        _showLoading: function() {
493
            this.subview('loading').show();
494
        },
495
496
        _hideLoading: function() {
497
            if (this.subview('loading')) {
498
                this.subview('loading').hide();
499
            }
500
        },
501
502
        _showLoadItemsError: function(err) {
503
            this._showError(this.options.messages.loadItemsError, err);
504
        },
505
506
        _showDeleteItemError: function(err) {
507
            this._showError(this.options.messages.deleteItemError, err);
508
        },
509
510
        _showForbiddenActivityDataError: function(err) {
511
            this._showError(this.options.messages.forbiddenActivityDataError, err);
512
        },
513
514
        _showForbiddenError: function(err) {
515
            this._showError(this.options.messages.forbiddenError, err);
516
        },
517
518
        _showError: function(message, err) {
519
            this._hideLoading();
520
            mediator.execute('showErrorMessage', message, err);
521
        }
522
    });
523
524
    return ActivityListView;
525
});
526