view/js/bootstrap-wysihtml5/bootstrap3-wysihtml5.js   B
last analyzed

Complexity

Total Complexity 44
Complexity/F 1.57

Size

Lines of Code 286
Function Count 28

Duplication

Duplicated Lines 278
Ratio 97.2 %

Importance

Changes 0
Metric Value
wmc 44
eloc 191
c 0
b 0
f 0
dl 278
loc 286
rs 8.8798
mnd 16
bc 16
fnc 28
bpm 0.5714
cpm 1.5713
noi 17

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like view/js/bootstrap-wysihtml5/bootstrap3-wysihtml5.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
/* jshint expr: true */
2
(function (factory) {
3
    if (typeof define === 'function' && define.amd) {
0 ignored issues
show
Bug introduced by
The variable define seems to be never declared. If this is a global, consider adding a /** global: define */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
4
        // AMD. Register as an anonymous module.
5
        define('bootstrap.wysihtml5', ['jquery', 'wysihtml5', 'bootstrap', 'bootstrap.wysihtml5.templates', 'bootstrap.wysihtml5.commands'], factory);
6
    } else {
7
        // Browser globals
8
        factory(jQuery, wysihtml5);
0 ignored issues
show
Bug introduced by
The variable wysihtml5 seems to be never declared. If this is a global, consider adding a /** global: wysihtml5 */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
9
    }
10 View Code Duplication
}(function ($, wysihtml5) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
11
12
var bsWysihtml5 = function($, wysihtml5) {
13
  'use strict';
14
15
  var templates = function(key, locale, options) {
16
    if(wysihtml5.tpl[key]) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if wysihtml5.tpl.key 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...
17
      return wysihtml5.tpl[key]({locale: locale, options: options});
18
    }
19
  };
20
21
  var Wysihtml5 = function(el, options) {
22
    this.el = el;
23
    var toolbarOpts = $.extend(true, {}, defaultOptions, options);
24
    for(var t in toolbarOpts.customTemplates) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
25
      wysihtml5.tpl[t] = toolbarOpts.customTemplates[t];
26
    }
27
    this.toolbar = this.createToolbar(el, toolbarOpts);
28
    this.editor =  this.createEditor(toolbarOpts);
29
  };
30
31
  Wysihtml5.prototype = {
32
33
    constructor: Wysihtml5,
34
35
    createEditor: function(options) {
36
      options = options || {};
37
38
      // Add the toolbar to a clone of the options object so multiple instances
39
      // of the WYISYWG don't break because 'toolbar' is already defined
40
      options = $.extend(true, {}, options);
41
      options.toolbar = this.toolbar[0];
42
43
      var editor = new wysihtml5.Editor(this.el[0], options);
44
45
      // #30 - body is in IE 10 not created by default, which leads to nullpointer
46
      // 2014/02/13 - adapted to wysihtml5-0.4, does not work in IE
47
      if(editor.composer.editableArea.contentDocument) {
48
        this.addMoreShortcuts(editor, editor.composer.editableArea.contentDocument.body || editor.composer.editableArea.contentDocument, options.shortcuts);
49
      } else {
50
        this.addMoreShortcuts(editor, editor.composer.editableArea, options.shortcuts);    
51
      }
52
      
53
54
      if(options && options.events) {
55
        for(var eventName in options.events) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
56
          editor.on(eventName, options.events[eventName]);
57
        }
58
      }
59
      
60
      editor.on('beforeload', this.syncBootstrapDialogEvents);
61
      //syncBootstrapDialogEvents();
62
      return editor;
63
    },
64
65
    //sync wysihtml5 events for dialogs with bootstrap events
66
    syncBootstrapDialogEvents: function() {
67
      var editor = this;
68
      $.map(this.toolbar.commandMapping, function(value, index) {
0 ignored issues
show
Unused Code introduced by
The parameter index 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...
69
        return [value];
70
      }).filter(function(commandObj, idx, arr) {
0 ignored issues
show
Unused Code introduced by
The parameter arr 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 idx 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...
71
        return commandObj.dialog;
72
      }).map(function(commandObj, idx, arr) {
0 ignored issues
show
Unused Code introduced by
The parameter arr 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 idx 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...
73
        return commandObj.dialog;
74
      }).forEach(function(dialog, idx, arr) {
0 ignored issues
show
Unused Code introduced by
The parameter idx 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 arr 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...
75
        dialog.on('show', function(event) {
0 ignored issues
show
Unused Code introduced by
The parameter event 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...
76
          $(this.container).modal('show');
77
        });
78
        dialog.on('hide', function(event) {
0 ignored issues
show
Unused Code introduced by
The parameter event 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...
79
          $(this.container).modal('hide');
80
          editor.composer.focus();
81
        });
82
        $(dialog.container).on('shown.bs.modal', function () {
83
          $(this).find('input, select, textarea').first().focus();
84
        });
85
      });
86
    },
87
88
    createToolbar: function(el, options) {
89
      var self = this;
90
      var toolbar = $('<ul/>', {
91
        'class' : 'wysihtml5-toolbar',
92
        'style': 'display:none'
93
      });
94
      var culture = options.locale || defaultOptions.locale || 'en';
95
      if(!locale.hasOwnProperty(culture)) {
96
        console.debug('Locale \'' + culture + '\' not found. Available locales are: ' + Object.keys(locale) + '. Falling back to \'en\'.');
97
        culture = 'en';
98
      }
99
      var localeObject = $.extend(true, {}, locale.en, locale[culture]);
100
      for(var key in options.toolbar) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
101
        if(options.toolbar[key]) {
102
          toolbar.append(templates(key, localeObject, options));
103
104
          if(key === 'html') {
105
            this.initHtml(toolbar);
106
          }
107
108
        }
109
      }
110
111
      toolbar.find('a[data-wysihtml5-command="formatBlock"]').click(function(e) {
112
        var target = e.delegateTarget || e.target || e.srcElement,
113
            el = $(target),
114
            showformat = el.data('wysihtml5-display-format-name'),
115
            formatname = el.data('wysihtml5-format-name') || el.html();
116
        if(showformat === undefined || showformat === 'true') {
117
          self.toolbar.find('.current-font').text(formatname);
118
        }
119
      });
120
121
      toolbar.find('a[data-wysihtml5-command="foreColor"]').click(function(e) {
122
        var target = e.target || e.srcElement;
123
        var el = $(target);
124
        self.toolbar.find('.current-color').text(el.html());
125
      });
126
127
      this.el.before(toolbar);
128
129
      return toolbar;
130
    },
131
132
    initHtml: function(toolbar) {
133
      var changeViewSelector = 'a[data-wysihtml5-action="change_view"]';
134
      toolbar.find(changeViewSelector).click(function(e) {
0 ignored issues
show
Unused Code introduced by
The parameter e 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...
135
        toolbar.find('a.btn').not(changeViewSelector).toggleClass('disabled');
136
      });
137
    },
138
139
    addMoreShortcuts: function(editor, el, shortcuts) {
140
      /* some additional shortcuts */
141
      wysihtml5.dom.observe(el, 'keydown', function(event) {
142
        var keyCode  = event.keyCode,
143
            command  = shortcuts[keyCode];
144
        if ((event.ctrlKey || event.metaKey || event.altKey) && command && wysihtml5.commands[command]) {
145
146
          var commandObj = editor.toolbar.commandMapping[command + ':null'];
147
          if (commandObj && commandObj.dialog && !commandObj.state) {
148
            commandObj.dialog.show();
149
          } else {
150
            wysihtml5.commands[command].exec(editor.composer, command);
151
          }
152
          event.preventDefault();
153
        }
154
      });
155
    }
156
  };
157
158
  // these define our public api
159
  var methods = {
160
    resetDefaults: function() {
161
      $.fn.wysihtml5.defaultOptions = $.extend(true, {}, $.fn.wysihtml5.defaultOptionsCache);
162
    },
163
    bypassDefaults: function(options) {
164
      return this.each(function () {
165
        var $this = $(this);
166
        $this.data('wysihtml5', new Wysihtml5($this, options));
167
      });
168
    },
169
    shallowExtend: function (options) {
170
      var settings = $.extend({}, $.fn.wysihtml5.defaultOptions, options || {}, $(this).data());
171
      var that = this;
172
      return methods.bypassDefaults.apply(that, [settings]);
173
    },
174
    deepExtend: function(options) {
175
      var settings = $.extend(true, {}, $.fn.wysihtml5.defaultOptions, options || {});
176
      var that = this;
177
      return methods.bypassDefaults.apply(that, [settings]);
178
    },
179
    init: function(options) {
180
      var that = this;
181
      return methods.shallowExtend.apply(that, [options]);
182
    }
183
  };
184
185
  $.fn.wysihtml5 = function ( method ) {
186
    if ( methods[method] ) {
187
      return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
188
    } else if ( typeof method === 'object' || ! method ) {
189
      return methods.init.apply( this, arguments );
190
    } else {
191
      $.error( 'Method ' +  method + ' does not exist on jQuery.wysihtml5' );
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...
192
    }    
193
  };
194
195
  $.fn.wysihtml5.Constructor = Wysihtml5;
196
197
  var defaultOptions = $.fn.wysihtml5.defaultOptions = {
198
    toolbar: {
199
      'font-styles': true,
200
      'color': false,
201
      'emphasis': {
202
        'small': true
203
      },
204
      'blockquote': true,
205
      'lists': true,
206
      'html': false,
207
      'link': true,
208
      'image': true,
209
      'smallmodals': false
210
    },
211
    parserRules: {
212
      classes: {
213
        'wysiwyg-color-silver' : 1,
214
        'wysiwyg-color-gray' : 1,
215
        'wysiwyg-color-white' : 1,
216
        'wysiwyg-color-maroon' : 1,
217
        'wysiwyg-color-red' : 1,
218
        'wysiwyg-color-purple' : 1,
219
        'wysiwyg-color-fuchsia' : 1,
220
        'wysiwyg-color-green' : 1,
221
        'wysiwyg-color-lime' : 1,
222
        'wysiwyg-color-olive' : 1,
223
        'wysiwyg-color-yellow' : 1,
224
        'wysiwyg-color-navy' : 1,
225
        'wysiwyg-color-blue' : 1,
226
        'wysiwyg-color-teal' : 1,
227
        'wysiwyg-color-aqua' : 1,
228
        'wysiwyg-color-orange' : 1
229
      },
230
      tags: {
231
        'b':  {},
232
        'i':  {},
233
        'strong': {},
234
        'em': {},
235
        'p': {},
236
        'br': {},
237
        'ol': {},
238
        'ul': {},
239
        'li': {},
240
        'h1': {},
241
        'h2': {},
242
        'h3': {},
243
        'h4': {},
244
        'h5': {},
245
        'h6': {},
246
        'blockquote': {},
247
        'u': 1,
248
        'img': {
249
          'check_attributes': {
250
            'width': 'numbers',
251
            'alt': 'alt',
252
            'src': 'url',
253
            'height': 'numbers'
254
          }
255
        },
256
        'a':  {
257
          check_attributes: {
258
            'href': 'url' // important to avoid XSS
259
          },
260
          'set_attributes': {
261
            'target': '_blank',
262
            'rel': 'nofollow'
263
          }
264
        },
265
        'span': 1,
266
        'div': 1,
267
        'small': 1,
268
        // to allow save and edit files with code tag hacks
269
        'code': 1,
270
        'pre': 1
271
      }
272
    },
273
    locale: 'en',
274
    shortcuts: {
275
      '83': 'small'     // S
276
    }
277
    
278
  };
279
280
  if (typeof $.fn.wysihtml5.defaultOptionsCache === 'undefined') {
281
    $.fn.wysihtml5.defaultOptionsCache = $.extend(true, {}, $.fn.wysihtml5.defaultOptions);
282
  }
283
284
  var locale = $.fn.wysihtml5.locale = {};
285
};
286
bsWysihtml5($, wysihtml5);
287
}));
288