Passed
Push — master ( d7d61e...da7150 )
by Paul
04:39
created

+/scripts/admin/shortcode.js   B

Complexity

Total Complexity 53
Complexity/F 2.12

Size

Lines of Code 229
Function Count 25

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 0
c 2
b 0
f 0
nc 16
dl 0
loc 229
rs 7.4757
wmc 53
mnd 2
bc 38
fnc 25
bpm 1.52
cpm 2.12
noi 1

20 Functions

Rating   Name   Duplication   Size   Complexity  
A Shortcode.destroy_ 0 7 2
A Shortcode.normalize_ 0 11 3
A Shortcode.initTinymceEditor_ 0 3 1
A Shortcode.initQuicktagsEditor_ 0 15 2
A Shortcode.close_ 0 4 1
A Shortcode.init_ 0 7 1
A shortcode.js ➔ Shortcode 0 19 4
A Shortcode.normalizeHide_ 0 9 4
A Shortcode.normalizeCount_ 0 4 3
A Shortcode.onClose_ 0 4 2
A Shortcode.normalizeId_ 0 4 2
A Shortcode.onTrigger_ 0 14 3
A Shortcode.response_ 0 16 4
A Shortcode.open_ 0 4 1
A Shortcode.onToggle_ 0 8 2
A Shortcode.responsePopup_ 0 11 1
A Shortcode.sendToEditor_ 0 10 4
A Shortcode.submitShortcode_ 0 5 2
A Shortcode.responseButtons_ 0 10 1
A Shortcode.validateAttributes_ 0 14 4

How to fix   Complexity   

Complexity

Complex classes like +/scripts/admin/shortcode.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
/** global: GLSR, jQuery */
2
;(function( editor, tinymce, x ) {
3
4
	'use strict';
5
6
	var Shortcode = function( selector ) {
7
		this.el = document.querySelector( selector );
8
		if( !this.el )return;
9
		this.current = null; // this.current is used by scForm to trigger the correct popup
10
		this.editor = null;
11
		this.button = this.el.querySelector( 'button' );
12
		this.menuItems = this.el.querySelectorAll( '.mce-menu-item' );
13
		if( !this.button || !this.menuItems.length )return;
14
		this.create = function( editor_id ) {
15
			this.editor = tinymce.get( editor_id );
16
			if( !this.editor )return;
17
			var request = {
18
				action: 'mce-shortcode',
19
				shortcode: this.current,
20
			};
21
			(new GLSR.Ajax( request )).post( this.response_.bind( this ));
22
		};
23
		this.init_();
24
	};
25
26
	Shortcode.prototype = {
27
		attributes_: {},
28
		hiddenKeys_: [],
29
30
		/** @return void */
31
		init_: function() {
32
			document.addEventListener( 'click', this.onClose_.bind( this ));
33
			this.button.addEventListener( 'click', this.onToggle_.bind( this ));
34
			this.menuItems.forEach( function( item ) {
35
				item.addEventListener( 'click', this.onTrigger_.bind( this ));
36
			}.bind( this ));
37
		},
38
39
		/** @return void */
40
		initTinymceEditor_: function() {
41
			tinymce.execCommand( 'GLSR_Shortcode' );
42
		},
43
44
		/** @return void */
45
		initQuicktagsEditor_: function() {
46
			if( x( '#scTemp' ).length ) {
47
				this.initTinymceEditor_();
48
				return;
49
			}
50
			x( 'body' ).append( '<textarea id="scTemp" style="display:none!important;"/>' );
51
			tinymce.init({
52
				elements: 'scTemp',
53
				mode: 'exact',
54
				plugins: ['glsr_shortcode', 'wplink'],
55
			});
56
			setTimeout( function() {
57
				this.initTinymceEditor_();
58
			}, 200 );
59
		},
60
61
		/** @return void */
62
		close_: function() {
63
			x( this.button ).removeClass( 'active' );
64
			x( this.el ).find( '.glsr-mce-menu' ).hide();
65
		},
66
67
		/** @return void */
68
		destroy_: function() {
69
			var tmp = x( '#scTemp' );
70
			if( tmp.length ) {
71
				tinymce.get( 'scTemp' ).remove();
72
				tmp.remove();
73
			}
74
		},
75
76
		/** @return void */
77
		normalize_: function( attributes ) {
78
			this.attributes_ = attributes;
79
			this.hiddenKeys_ = [];
80
			for( var key in attributes ) {
81
				if( !attributes.hasOwnProperty( key ))continue;
82
				this.normalizeCount_( key );
83
				this.normalizeHide_( key );
84
				this.normalizeId_( key );
85
			}
86
			this.attributes_.hide = this.hiddenKeys_.join( ',' );
87
		},
88
89
		/** @return void */
90
		normalizeCount_: function( key ) {
91
			if( key !== 'count' || x.isNumeric( this.attributes_[key] ))return;
92
			this.attributes_[key] = '';
93
		},
94
95
		/** @return void */
96
		normalizeHide_: function( key ) {
97
			if( !GLSR.hiddenkeys.hasOwnProperty( this.current ))return;
98
			var value = key.substring('hide_'.length);
99
			if( GLSR.hiddenkeys[this.current].indexOf( value ) === -1 )return;
100
			if( this.attributes_[key] ) {
101
				this.hiddenKeys_.push( value );
102
			}
103
			delete this.attributes_[key];
104
		},
105
106
		/** @return void */
107
		normalizeId_: function( key ) {
108
			if( key !== 'id' )return;
109
			this.attributes_[key] = (+new Date()).toString(36);
110
		},
111
112
		/** @return void */
113
		onClose_: function( ev ) {
114
			if( x( ev.target ).closest( x( this.el )).length )return;
115
			this.close_();
116
		},
117
118
		/** @return void */
119
		onToggle_: function( ev ) {
120
			ev.preventDefault();
121
			if( ev.target.classList.contains( 'active' )) {
122
				this.close_();
123
				return;
124
			}
125
			this.open_();
126
		},
127
128
		/** @return void */
129
		onTrigger_: function( ev ) {
130
			ev.preventDefault();
131
			this.current = ev.target.dataset.shortcode;
132
			if( !this.current )return;
133
			if( tinymce.get( window.wpActiveEditor )) {
134
				this.initTinymceEditor_();
135
			}
136
			else {
137
				this.initQuicktagsEditor_();
138
			}
139
			setTimeout( function() {
140
				this.close_();
141
			}.bind( this ), 100 );
142
		},
143
144
		/** @return void */
145
		open_: function() {
146
			x( this.button ).addClass( 'active' );
147
			x( this.el ).find( '.glsr-mce-menu' ).show();
148
		},
149
150
		/** @return void */
151
		response_: function( response ) {
152
			if( !response )return;
153
			if( response.body.length === 0 ) {
154
				window.send_to_editor( '[' + response.shortcode + ']' );
155
				this.destroy_();
156
				return;
157
			}
158
			var popup = this.responsePopup_( response );
159
			// Change the buttons if server-side validation failed
160
			if( response.ok.constructor === Array ) {
161
				popup.buttons[0].text = response.ok[0];
162
				popup.buttons[0].onclick = 'close';
163
				delete popup.buttons[1];
164
			}
165
			this.editor.windowManager.open( popup );
166
		},
167
168
		/** @return array */
169
		responseButtons_: function( response ) {
170
			return [{
171
				classes: 'btn glsr-btn primary',
172
				onclick: this.submitShortcode_.bind( this ),
173
				text: response.ok,
174
			},{
175
				onclick: 'close',
176
				text: response.close,
177
			}];
178
		},
179
180
		/** @return object */
181
		responsePopup_: function( response ) {
182
			return {
183
				title: response.title,
184
				body: response.body,
185
				classes: 'glsr-mce-popup',
186
				minWidth: 320,
187
				buttons: this.responseButtons_( response ),
188
				onsubmit: this.sendToEditor_.bind( this, response ),
189
				onclose: this.destroy_.bind( this ),
190
			};
191
		},
192
193
		/** @return void */
194
		sendToEditor_: function( response, ev ) {
195
			var attributes = '';
196
			this.normalize_( ev.data );
197
			for( var key in this.attributes_ ) {
198
				if( this.attributes_.hasOwnProperty( key ) && this.attributes_[key] !== '' ) {
199
					attributes += ' ' + key + '="' + this.attributes_[key] + '"';
200
				}
201
			}
202
			window.send_to_editor( '[' + response.shortcode + attributes + ']' );
203
		},
204
205
		/** @return void */
206
		submitShortcode_: function() {
207
			var currentWindow = this.editor.windowManager.getWindows()[0];
208
			if( !this.validateAttributes_( currentWindow ))return;
209
			currentWindow.submit();
210
		},
211
212
		/** @return bool */
213
		validateAttributes_: function( currentWindow ) {
214
			var field;
215
			var is_valid = true;
216
			var requiredAttributes = GLSR.shortcodes[this.current];
217
			for( var id in requiredAttributes ) {
1 ignored issue
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...
218
				field = currentWindow.find( '#' + id )[0];
219
				if( typeof field !== 'undefined' && field.state.data.value === '' ) {
220
					is_valid = false;
221
					alert( requiredAttributes[id] );
222
					break;
223
				}
224
			}
225
			return is_valid;
226
		},
227
	};
228
229
	GLSR.Shortcode = Shortcode;
230
})( window.editor, window.tinymce, jQuery );
231