src/Oro/Bundle/ShoppingListBundle/Resources/public/js/app/views/product-add-to-shopping-list-view.js   F
last analyzed

Complexity

Total Complexity 97
Complexity/F 2.26

Size

Lines of Code 486
Function Count 43

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 0
nc 196608
dl 0
loc 486
rs 3.12
c 0
b 0
f 0
wmc 97
mnd 3
bc 89
fnc 43
bpm 2.0697
cpm 2.2557
noi 10

How to fix   Complexity   

Complexity

Complex classes like src/Oro/Bundle/ShoppingListBundle/Resources/public/js/app/views/product-add-to-shopping-list-view.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
define(function(require) {
2
    'use strict';
3
4
    var ProductAddToShoppingListView;
5
    var BaseView = require('oroui/js/app/views/base/view');
6
    var ElementsHelper = require('orofrontend/js/app/elements-helper');
7
    var ShoppingListCreateWidget = require('oro/shopping-list-create-widget');
8
    var ApiAccessor = require('oroui/js/tools/api-accessor');
9
    var routing = require('routing');
10
    var mediator = require('oroui/js/mediator');
11
    var $ = require('jquery');
12
    var _ = require('underscore');
13
    var ShoppingListCollectionService = require('oroshoppinglist/js/shoppinglist-collection-service');
14
15
    ProductAddToShoppingListView = BaseView.extend(_.extend({}, ElementsHelper, {
16
        options: {
17
            emptyMatrixAllowed: false,
18
            buttonTemplate: '',
19
            createNewButtonTemplate: '',
20
            removeButtonTemplate: '',
21
            shoppingListCreateEnabled: true,
22
            buttonsSelector: '.add-to-shopping-list-button',
23
            quantityField: '[data-name="field__quantity"]',
24
            messages: {
25
                success: 'oro.form.inlineEditing.successMessage'
26
            },
27
            save_api_accessor: {
28
                http_method: 'PUT',
29
                route: 'oro_api_shopping_list_frontend_put_line_item',
30
                form_name: 'oro_product_frontend_line_item'
31
            }
32
        },
33
34
        dropdownWidget: null,
35
36
        shoppingListCollection: null,
37
38
        modelAttr: {
39
            shopping_lists: []
40
        },
41
42
        rendered: false,
43
44
        /**
45
         * @inheritDoc
46
         */
47
        constructor: function ProductAddToShoppingListView() {
48
            ProductAddToShoppingListView.__super__.constructor.apply(this, arguments);
49
        },
50
51
        /**
52
         * @inheritDoc
53
         */
54
        initialize: function(options) {
55
            ProductAddToShoppingListView.__super__.initialize.apply(this, arguments);
56
            this.deferredInitializeCheck(options, ['productModel', 'dropdownWidget']);
57
        },
58
59
        deferredInitialize: function(options) {
60
            this.options = $.extend(true, {}, this.options, _.pick(options, _.keys(this.options)));
61
62
            this.dropdownWidget = options.dropdownWidget;
63
            this.$form = this.dropdownWidget.element.closest('form');
64
65
            this.initModel(options);
66
67
            if (this.options.buttonTemplate) {
68
                this.options.buttonTemplate = _.template(this.options.buttonTemplate);
69
            }
70
            if (this.options.removeButtonTemplate) {
71
                this.options.removeButtonTemplate = _.template(this.options.removeButtonTemplate);
72
            }
73
            if (this.options.createNewButtonTemplate) {
74
                this.options.createNewButtonTemplate = _.template(this.options.createNewButtonTemplate);
75
            }
76
77
            this.saveApiAccessor = new ApiAccessor(this.options.save_api_accessor);
78
79
            if (this.model) {
80
                this.model.on('change:unit', this._onUnitChanged, this);
81
                this.model.on('editLineItem', this._onEditLineItem, this);
82
            }
83
84
            this.$form.find(this.options.quantityField).on('keydown', _.bind(this._onQuantityEnter, this));
85
86
            ShoppingListCollectionService.shoppingListCollection.done(_.bind(function(collection) {
87
                this.shoppingListCollection = collection;
88
                this.listenTo(collection, 'change', this._onCollectionChange);
89
                this.render();
90
            }, this));
91
        },
92
93
        initModel: function(options) {
94
            var modelAttr = _.each(options.modelAttr, function(value, attribute) {
95
                options.modelAttr[attribute] = value === 'undefined' ? undefined : value;
96
            }) || {};
97
            this.modelAttr = $.extend(true, {}, this.modelAttr, modelAttr);
98
            if (options.productModel) {
99
                this.model = options.productModel;
100
            }
101
102
            if (!this.model) {
103
                return;
104
            }
105
106
            _.each(this.modelAttr, function(value, attribute) {
107
                if (!this.model.has(attribute) || modelAttr[attribute] !== undefined) {
108
                    this.model.set(attribute, value);
109
                }
110
            }, this);
111
112
            if (this.model.get('shopping_lists') === undefined) {
113
                this.model.set('shopping_lists', []);
114
            }
115
        },
116
117
        render: function() {
118
            this._setEditLineItem(null, true);
119
120
            var buttons = this._collectAllButtons();
121
122
            this._getContainer().html(buttons);
123
        },
124
125
        _onCollectionChange: function() {
126
            if (arguments.length > 0 && arguments[0].shoppingListCreateEnabled !== undefined) {
127
                this.options.shoppingListCreateEnabled = arguments[0].shoppingListCreateEnabled;
128
            }
129
            this._setEditLineItem();
130
131
            var buttons = this._collectAllButtons();
132
133
            this._clearButtons();
134
            this._getContainer().prepend(buttons);
135
            this.dropdownWidget._renderButtons();
136
        },
137
138
        _clearButtons: function() {
139
            this._getContainer().find(this.options.buttonsSelector).remove();
140
        },
141
142
        _getContainer: function() {
143
            return this.dropdownWidget.element.find('.btn-group:first');
144
        },
145
146
        _collectAllButtons: function() {
147
            var buttons = [];
148
149
            if (!this.shoppingListCollection.length) {
150
                var $addNewButton = $(this.options.buttonTemplate({
151
                    id: null,
152
                    label: _.__('oro.shoppinglist.entity_label')
153
                })).find(this.options.buttonsSelector);
154
155
                return [$addNewButton];
156
            }
157
158
            var currentShoppingList = this.findCurrentShoppingList();
159
            this._addShippingListButtons(buttons, currentShoppingList);
160
161
            this.shoppingListCollection.sort();
162
            this.shoppingListCollection.each(function(model) {
163
                var shoppingList = model.toJSON();
164
                if (shoppingList.id === currentShoppingList.id) {
165
                    return;
166
                }
167
168
                this._addShippingListButtons(buttons, shoppingList);
169
            }, this);
170
171
            if (this.options.shoppingListCreateEnabled) {
172
                var $createNewButton = $(this.options.createNewButtonTemplate({id: null, label: ''}));
173
                $createNewButton = this.updateLabel($createNewButton, null);
174
                buttons.push($createNewButton);
175
            }
176
177
            if (buttons.length === 1) {
178
                var decoreClass = this.dropdownWidget.options.decoreClass || '';
179
                buttons = _.first(buttons).find(this.options.buttonsSelector).addClass(decoreClass);
180
            }
181
182
            return buttons;
183
        },
184
185
        _addShippingListButtons: function(buttons, shoppingList) {
186
            var $button = $(this.options.buttonTemplate(shoppingList));
187
            if (!this.model) {
188
                buttons.push($button);
189
                return;
190
            }
191
            var hasLineItems = this.findShoppingListByIdAndUnit(shoppingList, this.model.get('unit'));
192
            if (hasLineItems) {
193
                $button = this.updateLabel($button, shoppingList, hasLineItems);
194
            }
195
            buttons.push($button);
196
197
            if (hasLineItems) {
198
                var $removeButton = $(this.options.removeButtonTemplate(shoppingList));
199
                $removeButton.find('a').attr('data-intention', 'remove');
200
                buttons.push($removeButton);
201
            }
202
        },
203
204
        _afterRenderButtons: function() {
205
            this.initButtons();
206
            this.rendered = true;
207
        },
208
209
        initButtons: function() {
210
            this.findAllButtons()
211
                .off('click' + this.eventNamespace())
212
                .on('click' + this.eventNamespace(), _.bind(this.onClick, this));
213
        },
214
215
        findDropdownButtons: function(filter) {
216
            var $el = this.dropdownWidget.element || this.dropdownWidget.dropdown;
217
            var $buttons = $el.find(this.options.buttonsSelector);
218
            if (filter) {
219
                $buttons = $buttons.filter(filter);
220
            }
221
            return $buttons;
222
        },
223
224
        findMainButton: function() {
225
            if (this.dropdownWidget.main && this.dropdownWidget.main.is(this.options.buttonsSelector)) {
226
                return this.dropdownWidget.main;
227
            }
228
            return $([]);
229
        },
230
231
        findAllButtons: function(filter) {
232
            var $buttons = this.findMainButton().add(this.findDropdownButtons());
233
            if (filter) {
234
                $buttons = $buttons.filter(filter);
235
            }
236
            return $buttons;
237
        },
238
239
        findShoppingListById: function(shoppingList) {
240
            if (!this.model) {
241
                return null;
242
            }
243
            return _.find(this.model.get('shopping_lists'), {id: shoppingList.id}) || null;
244
        },
245
246
        findShoppingListByIdAndUnit: function(shoppingList, unit) {
247
            var foundShoppingList = this.findShoppingListById(shoppingList);
248
            if (!foundShoppingList) {
249
                return null;
250
            }
251
            var hasUnits = _.find(foundShoppingList.line_items, {unit: unit});
252
            return hasUnits ? foundShoppingList : null;
253
        },
254
255
        findShoppingListLineItemByUnit: function(shoppingList, unit) {
256
            var foundShoppingList = this.findShoppingListById(shoppingList);
257
            if (!foundShoppingList) {
258
                return null;
259
            }
260
            return _.find(foundShoppingList.line_items, {unit: unit});
261
        },
262
263
        _onQuantityEnter: function(e) {
264
            var ENTER_KEY_CODE = 13;
265
266
            if (e.keyCode === ENTER_KEY_CODE) {
267
                this.model.set({
268
                    quantity: parseInt($(e.target).val(), 10)
269
                });
270
271
                var mainButton = this.findMainButton();
272
273
                if (!mainButton.length) {
274
                    mainButton = this.findAllButtons();
275
                }
276
277
                mainButton.click();
278
            }
279
        },
280
281
        findCurrentShoppingList: function() {
282
            return this.shoppingListCollection.find({is_current: true}).toJSON() || null;
283
        },
284
285
        findShoppingList: function(id) {
286
            id = parseInt(id, 10);
287
            return this.shoppingListCollection.find({id: id}).toJSON() || null;
288
        },
289
290
        validate: function(intention, url, urlOptions, formData) {
0 ignored issues
show
Unused Code introduced by
The parameter intention is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter formData is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter urlOptions is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter url is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
291
            return this.dropdownWidget.validateForm();
292
        },
293
294
        onClick: function(e) {
295
            var $button = $(e.currentTarget);
296
            if ($button.data('disabled')) {
297
                return false;
298
            }
299
            var url = $button.data('url');
300
            var intention = $button.data('intention');
301
            var formData = this.$form.serialize();
302
303
            var urlOptions = {
304
                shoppingListId: $button.data('shoppinglist').id
305
            };
306
            if (this.model) {
307
                urlOptions.productId = this.model.get('id');
308
                if (this.model.has('parentProduct')) {
309
                    urlOptions.parentProductId = this.model.get('parentProduct');
310
                }
311
            }
312
313
            if (!this.validate(intention, url, urlOptions, formData)) {
314
                return;
315
            }
316
317
            if (intention === 'new') {
318
                this._createNewShoppingList(url, urlOptions, formData);
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...
319
            } else if (intention === 'update') {
320
                this._saveLineItem(url, urlOptions, formData);
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...
321
            } else if (intention === 'remove') {
322
                this._removeLineItem(url, urlOptions, formData);
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...
323
            } else {
324
                this._addLineItem(url, urlOptions, formData);
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...
325
            }
326
        },
327
328
        updateLabel: function($button, shoppingList, hasLineItems) {
329
            var label;
330
            var intention;
331
332
            if (shoppingList && hasLineItems) {
333
                label = _.__('oro.shoppinglist.actions.update_shopping_list', {
334
                    shoppingList: shoppingList.label
335
                });
336
                intention = 'update';
337
            } else if (!shoppingList) {
338
                label = _.__('oro.shoppinglist.widget.add_to_new_shopping_list');
339
                intention = 'new';
340
            } else {
341
                label = _.__('oro.shoppinglist.actions.add_to_shopping_list', {
342
                    shoppingList: shoppingList.label
343
                });
344
                intention = 'add';
345
            }
346
347
            $button.find('a')
348
                .text(label)
349
                .attr('title', label)
350
                .attr('data-intention', intention);
351
352
            return $button;
353
        },
354
355
        _setEditLineItem: function(lineItemId, setFirstLineItem) {
356
            this.editLineItem = null;
357
358
            if (!this.model || !this.shoppingListCollection.length) {
359
                return;
360
            }
361
362
            var currentShoppingListInCollection = this.findCurrentShoppingList();
363
            var currentShoppingListInModel = this.findShoppingListById(currentShoppingListInCollection);
364
365
            if (!currentShoppingListInModel) {
366
                return;
367
            }
368
369
            if (lineItemId) {
370
                this.editLineItem = _.findWhere(currentShoppingListInModel.line_items, {id: lineItemId});
371
            } else if (setFirstLineItem) {
372
                this.editLineItem = currentShoppingListInModel.line_items[0] || null;
373
            } else if (!this.model.get('quantity_changed_manually')) {
374
                this.editLineItem = _.findWhere(
375
                    currentShoppingListInModel.line_items, {unit: this.model.get('unit')}
376
                ) || null;
377
            }
378
379
            if (this.editLineItem) {
380
                // quantity precision depend on unit, set unit first
381
                this.model.set('unit', this.editLineItem.unit);
382
                this.model.set('quantity', this.editLineItem.quantity);
383
                this.model.set('quantity_changed_manually', true);// prevent quantity change in other components
384
            }
385
        },
386
387
        _createNewShoppingList: function(url, urlOptions, formData) {
388
            if (this.model && !this.model.get('line_item_form_enable')) {
389
                return;
390
            }
391
            var dialog = new ShoppingListCreateWidget({});
392
            dialog.on('formSave', _.bind(function(response) {
393
                urlOptions.shoppingListId = response.savedId;
394
                this._addLineItem(url, urlOptions, formData);
395
            }, this));
396
            dialog.render();
397
        },
398
399
        _addProductToShoppingList: function(url, urlOptions, formData) {
400
            if (this.model && !this.model.get('line_item_form_enable')) {
401
                return;
402
            }
403
            var self = this;
404
405
            mediator.execute('showLoading');
406
            $.ajax({
407
                type: 'POST',
408
                url: routing.generate(url, urlOptions),
409
                data: formData,
410
                success: function(response) {
411
                    mediator.trigger('shopping-list:line-items:update-response', self.model, response);
412
                },
413
                complete: function() {
414
                    mediator.execute('hideLoading');
415
                }
416
            });
417
        },
418
419
        _onUnitChanged: function() {
420
            this._setEditLineItem();
421
            if (this.rendered) {
422
                this._onCollectionChange();
423
            }
424
        },
425
426
        _onEditLineItem: function(lineItemId) {
427
            this._setEditLineItem(lineItemId);
428
            this.model.trigger('focus:quantity');
429
        },
430
431
        _addLineItem: function(url, urlOptions, formData) {
432
            this._addProductToShoppingList(url, urlOptions, formData);
433
        },
434
435
        _removeLineItem: function(url, urlOptions, formData) {
436
            this._addProductToShoppingList(url, urlOptions, formData);
437
        },
438
439
        _saveLineItem: function(url, urlOptions, formData) {
0 ignored issues
show
Unused Code introduced by
The parameter formData is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
440
            if (this.model && !this.model.get('line_item_form_enable')) {
441
                return;
442
            }
443
            mediator.execute('showLoading');
444
445
            var shoppingList = this.findShoppingList(urlOptions.shoppingListId);
446
            var lineItem = this.findShoppingListLineItemByUnit(shoppingList, this.model.get('unit'));
447
448
            var savePromise = this.saveApiAccessor.send({
449
                id: lineItem.id
450
            }, {
451
                quantity: this.model.get('quantity'),
452
                unit: this.model.get('unit')
453
            });
454
455
            savePromise
456
                .done(_.bind(function(response) {
457
                    lineItem.quantity = response.quantity;
458
                    lineItem.unit = response.unit;
459
                    this.shoppingListCollection.trigger('change', {
460
                        refresh: true
461
                    });
462
                    mediator.execute('showFlashMessage', 'success', _.__(this.options.messages.success));
463
                }, this))
464
                .always(function() {
465
                    mediator.execute('hideLoading');
466
                });
467
        },
468
469
        dispose: function(options) {
0 ignored issues
show
Unused Code introduced by
The parameter options is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
470
            delete this.dropdownWidget;
471
            delete this.modelAttr;
472
            delete this.shoppingListCollection;
473
            delete this.editLineItem;
474
            delete this.$form;
475
476
            mediator.off(null, null, this);
477
            if (this.model) {
478
                this.model.off(null, null, this);
479
            }
480
481
            ProductAddToShoppingListView.__super__.dispose.apply(this, arguments);
482
        }
483
    }));
484
485
    return ProductAddToShoppingListView;
486
});
487