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

  A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
nc 1
dl 0
loc 23
rs 9.0856
nop 0

1 Function

Rating   Name   Duplication   Size   Complexity  
A fallback-view.js ➔ ... ➔ this.$el.each 0 9 1
1
define(function(require) {
2
    'use strict';
3
4
    var FallbackView;
5
    var $ = require('jquery');
6
    var _ = require('underscore');
7
    var BaseView = require('oroui/js/app/views/base/view');
8
    var tinyMCE = require('tinymce/tinymce');
9
10
    /**
11
     * @export orolocale/js/app/views/fallback-view
12
     * @extends oroui.app.views.base.View
13
     * @class orolocale.app.views.FallbackView
14
     */
15
    FallbackView = BaseView.extend({
16
        autoRender: true,
17
18
        initSubviews: true,
19
20
        /**
21
         * @property {Object}
22
         */
23
        itemsByCode: {},
24
25
        /**
26
         * @property {Object}
27
         */
28
        itemToChildren: {},
29
30
        /**
31
         * @property {Object}
32
         */
33
        options: {
34
            expanded: false,
35
            hideDefaultLabel: true,
36
            fallbackWidth: 180,
37
            selectors: {
38
                status: '.fallback-status',
39
                item: '.fallback-item',
40
                defaultItem: '.fallback-item:first',
41
                childItem: '.fallback-item:not(:first)',
42
                itemLabel: '.fallback-item-label',
43
                itemValue: '.fallback-item-value',
44
                itemUseFallback: '.fallback-item-use-fallback',
45
                itemFallback: '.fallback-item-fallback'
46
            },
47
            icons: {
48
                'new': {
49
                    html: '<i class="fa-folder-o"></i>',
50
                    event: 'expandChildItems'
51
                },
52
                'edited': {
53
                    html: '<i class="fa-folder"></i>',
54
                    event: 'expandChildItems'
55
                },
56
                'save': {
57
                    html: '<i class="fa-folder-open"></i>',
58
                    event: 'collapseChildItems'
59
                }
60
            }
61
        },
62
63
        /**
64
         * @inheritDoc
65
         */
66
        initialize: function(options) {
67
            this.options = _.defaults(options || {}, this.options);
68
            FallbackView.__super__.initialize.call(this, options);
69
        },
70
71
        /**
72
         * @inheritDoc
73
         */
74
        render: function() {
75
            this.$(this.options.selectors.childItem).attr('data-layout', 'separate');
76
77
            this._deferredRender();
78
            this.initLayout().done(function() {
79
                this.handleLayoutInit();
80
                this._resolveDeferredRender();
81
            }.bind(this));
82
83
            return this;
84
        },
85
86
        renderSubviews: function() {
87
            this.initSubviews = false;
88
            this.$(this.options.selectors.childItem).removeAttr('data-layout');
89
90
            this.initLayout().done(function() {
91
                this.bindEvents();
92
            }.bind(this));
93
        },
94
95
        /**
96
         * Doing something after loading child components
97
         */
98
        handleLayoutInit: function() {
99
            var self = this;
100
101
            this.mapItemsByCode();
102
103
            this.getUseFallbackEl(this.$el).each(function() {
104
                self.switchUseFallback(self.getItemEl(this));
105
            });
106
107
            this.mapItemToChildren();
108
109
            this.getValueEl(this.$el).each(function() {
110
                // self.cloneValueToChildren(self.getItemEl(this)); uncomment on merging master
111
            });
112
113
            this.fixFallbackWidth();
114
            this.setStatusIcon();
115
        },
116
117
        /**
118
         * Bind events to controls
119
         */
120
        bindEvents: function() {
121
            var self = this;
122
123
            this.getValueEl(this.$el)
124
                .change(_.bind(this.cloneValueToChildrenEvent, this))
125
                .keyup(_.bind(this.cloneValueToChildrenEvent, this));
126
127
            this.$el.find(this.options.selectors.itemValue).find('.mce-tinymce').each(function() {
128
                tinyMCE.get(self.getValueEl(self.getItemEl(this)).attr('id'))
129
                    .on('change', function() {
130
                        $(this.targetElm).change();
131
                    })
132
                    .on('keyup', function() {
133
                        $(this.targetElm).change();
134
                    });
135
            });
136
137
            this.getUseFallbackEl(this.$el)
138
                .change(_.bind(this.switchUseFallbackEvent, this));
139
140
            this.getFallbackEl(this.$el)
141
                .change(_.bind(this.switchFallbackTypeEvent, this));
142
        },
143
144
        /**
145
         * Create item code to element mapping
146
         */
147
        mapItemsByCode: function() {
148
            var self = this;
149
150
            this.itemsByCode = {};
151
152
            this.$el.find(this.options.selectors.item).each(function() {
153
                var $item = $(this);
154
                var itemCode = self.getItemCode($item);
155
156
                if (!itemCode) {
157
                    return;
158
                }
159
160
                self.itemsByCode[itemCode] = $item;
161
            });
162
        },
163
164
        /**
165
         * Create item to children mapping
166
         */
167
        mapItemToChildren: function() {
168
            var self = this;
169
170
            this.itemToChildren = {};
171
172
            this.$el.find(this.options.selectors.item).each(function() {
173
                var $item = $(this);
174
                var parentItemCode = self.getParentItemCode($item);
175
176
                if (!parentItemCode) {
177
                    return;
178
                }
179
180
                if (self.itemToChildren[parentItemCode] === undefined) {
181
                    self.itemToChildren[parentItemCode] = [];
182
                }
183
                self.itemToChildren[parentItemCode].push($item);
184
            });
185
        },
186
187
        /**
188
         * Trigger on value change
189
         *
190
         * @param {Event} e
191
         */
192
        cloneValueToChildrenEvent: function(e) {
193
            this.cloneValueToChildren(this.getItemEl(e.currentTarget));
194
        },
195
196
        /**
197
         * Trigger on "use fallback" change
198
         *
199
         * @param {Event} e
200
         */
201
        switchUseFallbackEvent: function(e) {
202
            this.switchUseFallback(this.getItemEl(e.currentTarget));
203
        },
204
205
        /**
206
         * Trigger on fallback change
207
         *
208
         * @param {Event} e
209
         */
210
        switchFallbackTypeEvent: function(e) {
211
            var $item = this.getItemEl(e.currentTarget);
212
213
            this.mapItemToChildren();
214
215
            var parentItemCode = this.getParentItemCode($item);
216
            if (parentItemCode) {
217
                var $fromValue = this.getValueEl(this.itemsByCode[parentItemCode]);
218
                var $toValue = this.getValueEl($item);
219
                this.cloneValue($fromValue, $toValue);
220
            } else {
221
                this.cloneValueToChildrenEvent(e);
222
            }
223
        },
224
225
        /**
226
         * Show child items
227
         */
228
        expandChildItems: function() {
229
            if (this.initSubviews) {
230
                this.renderSubviews();
231
            }
232
233
            this.options.expanded = true;
234
            this.setStatusIcon();
235
        },
236
237
        /**
238
         * Hide child items
239
         */
240
        collapseChildItems: function() {
241
            this.options.expanded = false;
242
            this.setStatusIcon();
243
        },
244
245
        /**
246
         * Clone item value to children
247
         *
248
         * @param {jQuery} $item
249
         */
250
        cloneValueToChildren: function($item) {
251
            var $fromValue = this.getValueEl($item);
252
            var itemCode = this.getItemCode($item);
253
254
            var self = this;
255
            $.each(this.itemToChildren[itemCode] || [], function() {
256
                var $toValue = self.getValueEl(this);
257
                self.cloneValue($fromValue, $toValue);
258
            });
259
        },
260
261
        /**
262
         * Enable/disable controls depending on the "use fallback"
263
         *
264
         * @param {jQuery} $item
265
         */
266
        switchUseFallback: function($item) {
267
            var $useFallback = this.getUseFallbackEl($item);
268
            if ($useFallback.length === 0) {
269
                return;
270
            }
271
272
            var checked = $useFallback.get(0).checked;
273
274
            this.enableDisableValue(this.getValueEl($item), !checked);
275
            this.enableDisableFallback(this.getFallbackEl($item), checked);
276
        },
277
278
        /**
279
         * Enable/disable value
280
         *
281
         * @param {jQuery} $value
282
         * @param {Boolean} enable
283
         */
284
        enableDisableValue: function($value, enable) {
285
            var $valueContainer = $value.closest(this.options.selectors.itemValue);
286
287
            var editor;
288
            if ($valueContainer.find('.mce-tinymce').length > 0) {
289
                editor = tinyMCE.get($valueContainer.find('textarea').attr('id'));
290
            }
291
292
            if (enable) {
293
                $value.removeAttr('disabled');
294
295
                if (editor) {
296
                    editor.getBody().setAttribute('contenteditable', true);
297
                    $(editor.editorContainer).removeClass('disabled');
298
                    $(editor.editorContainer).children('.disabled-overlay').remove();
299
                }
300
            } else {
301
                $value.attr('disabled', 'disabled');
302
303
                if (editor) {
304
                    editor.getBody().setAttribute('contenteditable', false);
305
                    $(editor.editorContainer).addClass('disabled');
306
                    $(editor.editorContainer).append('<div class="disabled-overlay"></div>');
307
                }
308
            }
309
        },
310
311
        /**
312
         * Enable/disable fallback
313
         *
314
         * @param {jQuery} $fallback
315
         * @param {Boolean} enable
316
         */
317
        enableDisableFallback: function($fallback, enable) {
318
            var $fallbackContainer = $fallback.inputWidget('container');
319
320
            if (enable) {
321
                $fallback.removeAttr('disabled');
322
323
                if ($fallbackContainer) {
324
                    $fallbackContainer.removeClass('disabled');
325
                }
326
            } else {
327
                $fallback.attr('disabled', 'disabled');
328
329
                if ($fallbackContainer) {
330
                    $fallbackContainer.addClass('disabled');
331
                }
332
            }
333
334
            $fallback.change();
335
        },
336
337
        /**
338
         * Clone value to another value
339
         *
340
         * @param {jQuery} $fromValue
341
         * @param {jQuery} $toValue
342
         */
343
        cloneValue: function($fromValue, $toValue) {
344
            var isChanged = false;
345
            $fromValue.each(function(i) {
346
                var toValue = $toValue.get(i);
347
                if ($(this).is(':checkbox') || $(this).is(':radio')) {
348
                    if (toValue.checked !== this.checked) {
349
                        isChanged = true;
350
                        toValue.checked = this.checked;
351
                    }
352
                } else {
353
                    if ($(toValue).val() !== $(this).val()) {
354
                        isChanged = true;
355
                        $(toValue).val($(this).val());
356
                    }
357
                }
358
            });
359
            if (isChanged) {
360
                $toValue.filter(':first').change();
361
            }
362
        },
363
364
        /**
365
         * Get item element by children
366
         *
367
         * @param {*|jQuery|HTMLElement} el
368
         *
369
         * @returns {jQuery}
370
         */
371
        getItemEl: function(el) {
372
            var $item = $(el);
373
            if (!$item.is(this.options.selectors.item)) {
374
                $item = $item.closest(this.options.selectors.item);
375
            }
376
            return $item;
377
        },
378
379
        /**
380
         * Get value element
381
         *
382
         * @param {jQuery} $el
383
         *
384
         * @returns {jQuery}
385
         */
386
        getValueEl: function($el) {
387
            return $el.find(this.options.selectors.itemValue).find('input, textarea, select');
388
        },
389
390
        /**
391
         * Get "use fallback" element
392
         *
393
         * @param {jQuery} $el
394
         *
395
         * @returns {jQuery}
396
         */
397
        getUseFallbackEl: function($el) {
398
            return $el.find(this.options.selectors.itemUseFallback).find('input');
399
        },
400
401
        /**
402
         * Get fallback element
403
         *
404
         * @param {jQuery} $el
405
         *
406
         * @returns {jQuery}
407
         */
408
        getFallbackEl: function($el) {
409
            return $el.find(this.options.selectors.itemFallback).find('select');
410
        },
411
412
        /**
413
         * Get parent item code
414
         *
415
         * @param {jQuery} $item
416
         *
417
         * @returns {undefined|String}
418
         */
419
        getParentItemCode: function($item) {
420
            var $select = this.getFallbackEl($item);
421
            if ($select.length === 0 || $select.attr('disabled')) {
422
                return;
423
            }
424
425
            var parentItemCode = $select.attr('data-parent-localization');
426
427
            return parentItemCode && $select.val() !== 'system' ? parentItemCode : $select.val();
428
        },
429
430
        /**
431
         * Get item code
432
         *
433
         * @param {jQuery} $item
434
         *
435
         * @returns {String}
436
         */
437
        getItemCode: function($item) {
438
            var $select = this.getFallbackEl($item);
439
            var itemCode;
440
441
            if ($select.length === 0) {
442
                itemCode = 'system';
443
            } else {
444
                itemCode = $select.attr('data-localization');
445
            }
446
447
            return itemCode;
448
        },
449
450
        /**
451
         * Check is child has custom value
452
         */
453
        isChildEdited: function() {
454
            var isChildEdited = false;
455
            var $childItems = this.$el.find(this.options.selectors.childItem);
456
457
            this.getValueEl($childItems).each(function() {
458
                if (this.disabled) {
459
                    return;
460
                }
461
462
                if ($(this).is(':checkbox') || $(this).is(':radio')) {
463
                    isChildEdited = true;
464
                } else {
465
                    isChildEdited = $(this).val().length > 0;
466
                }
467
468
                if (isChildEdited) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if isChildEdited is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
469
                    return false;
470
                }
471
            });
472
473
            return isChildEdited;
474
        },
475
476
        /**
477
         * Set fallback selector width depending of their content
478
         */
479
        fixFallbackWidth: function() {
480
            var $fallback = this.$el.find(this.options.selectors.itemFallback).find('select');
481
            $fallback.inputWidget('width', this.options.fallbackWidth);
482
        },
483
484
        /**
485
         * Change status icon depending on expanded flag and child custom values
486
         */
487
        setStatusIcon: function() {
488
            var icon;
489
490
            if (this.options.expanded) {
491
                icon = 'save';
492
            } else if (this.isChildEdited()) {
493
                icon = 'edited';
494
            } else {
495
                icon = 'new';
496
            }
497
498
            icon = this.options.icons[icon];
499
500
            this.$el.find(this.options.selectors.status)
501
                .html(icon.html)
502
                .find('i').click(_.bind(this[icon.event], this));
503
504
            var $defaultLabel = this.$el.find(this.options.selectors.defaultItem)
505
                .find(this.options.selectors.itemLabel);
506
            var $childItems = this.$el.find(this.options.selectors.childItem);
507
508
            if (this.options.expanded) {
509
                if (this.options.hideDefaultLabel) {
510
                    $defaultLabel.show();
511
                }
512
                $childItems.show();
513
            } else {
514
                if (this.options.hideDefaultLabel) {
515
                    $defaultLabel.hide();
516
                }
517
                $childItems.hide();
518
            }
519
        }
520
    });
521
522
    return FallbackView;
523
});
524