Completed
Pull Request — master (#40)
by
unknown
02:53
created

src/server/public/scripts/modules/UserList.js   C

Complexity

Total Complexity 54
Complexity/F 2.16

Size

Lines of Code 346
Function Count 25

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 0
wmc 54
nc 1
mnd 3
bc 46
fnc 25
dl 0
loc 346
rs 6.8539
bpm 1.84
cpm 2.16
noi 3
c 1
b 0
f 0

11 Functions

Rating   Name   Duplication   Size   Complexity  
A usersList._update 0 58 2
A usersList._edit 0 9 1
A usersList._remove 0 20 2
A usersList._validateEmail 0 4 1
D usersList._add 0 116 14
B usersList._deactivate 0 24 1
B usersList._activate 0 24 1
A usersList._closeUpdate 0 3 1
A usersList._closeFormUpdate 0 7 1
B usersList._bindEvents 0 37 3
B usersList.init 0 37 3

How to fix   Complexity   

Complexity

Complex classes like src/server/public/scripts/modules/UserList.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 document, confirm, $ */
2
3
import Nanoajax from 'nanoajax'
4
import qs from 'qs'
5
6
var usersList = {
7
  init: function () {
8
    var scope = document.querySelector('.user-list')
9
    if (scope != null) {
10
      this._ajax = Nanoajax.ajax
11
12
      this._scope = scope
13
      this._table = this._scope.querySelector('#filtered-list tbody')
14
      this._alert = document.querySelector('.alert')
15
      this._handleActivate = this._activate.bind(this)
16
      this._handleDeactivate = this._deactivate.bind(this)
17
      this._handleRemove = this._remove.bind(this)
18
      this._handleEdit = this._edit.bind(this)
19
      this._handleUpdate = this._update.bind(this)
20
      this._handleCloseUpdate = this._closeUpdate.bind(this)
21
      this._handleAdd = this._add.bind(this)
22
23
      this._bindEvents()
24
    }
25
26
    if ($('#filtered-list').size() > 0) {
27
      var orderables = document.querySelectorAll('#filtered-list thead th')
28
      var columns = []
29
      Array.prototype.forEach.call(orderables, (orderable) => {
30
        var order = orderable.getAttribute('data-orderable')
31
        if (order != null) {
32
          columns.push({ 'orderable': (order == 'true') ? true : false })
33
        }else {
34
          columns.push(null)
35
        }
36
      })
37
      this.table = $('#filtered-list').DataTable({
38
        paging: false,
39
        'info': false,
40
        'columns': columns
41
      })
42
    }
43
  },
44
  _bindEvents: function () {
45
    this._activateBtn = this._scope.querySelectorAll('[data-activate]')
46
    this._deactivateBtn = this._scope.querySelectorAll('[data-deactivate]')
47
    this._removeBtn = this._scope.querySelectorAll('[data-remove]')
48
    this._editBtn = this._scope.querySelectorAll('[data-edit]')
49
    this._updateBtn = this._scope.querySelectorAll('[data-update]')
50
    this._addBtn = this._scope.querySelector('[data-add-user]')
51
52
    Array.prototype.forEach.call(this._activateBtn, (btn) => {
53
      btn.removeEventListener('click', this._handleActivate)
54
      btn.addEventListener('click', this._handleActivate)
55
    })
56
57
    Array.prototype.forEach.call(this._deactivateBtn, (btn) => {
58
      btn.removeEventListener('click', this._handleDeactivate)
59
      btn.addEventListener('click', this._handleDeactivate)
60
    })
61
62
    Array.prototype.forEach.call(this._removeBtn, (btn) => {
63
      btn.removeEventListener('click', this._handleRemove)
64
      btn.addEventListener('click', this._handleRemove)
65
    })
66
67
    Array.prototype.forEach.call(this._editBtn, (btn) => {
68
      btn.removeEventListener('click', this._handleEdit, true)
69
      btn.addEventListener('click', this._handleEdit, true)
70
    })
71
72
    Array.prototype.forEach.call(this._updateBtn, (btn) => {
73
      btn.removeEventListener('click', this._handleUpdate, true)
74
      btn.addEventListener('click', this._handleUpdate, true)
75
    })
76
77
    if(typeof this._addBtn !== 'undefined' && this._addBtn !== null) {
78
      this._addBtn.addEventListener('click', this._handleAdd)
79
    }
80
  },
81
  _activate: function (e) {
82
    var target = e.currentTarget
83
    var id = target.getAttribute('data-user-id')
84
85
    var toSave = qs.stringify({
86
      id: id
87
    })
88
89
    this._ajax(
90
      {
91
        url: '/abe/users/activate',
92
        body: toSave,
93
        method: 'post'
94
      },
95
      () => {
96
        var childGlyph = target.querySelector('.glyphicon')
97
        childGlyph.classList.remove('glyphicon-eye-open', 'text-info')
98
        childGlyph.classList.add('glyphicon-eye-close', 'text-danger')
99
        target.classList.remove('glyphicon-eye-close', 'text-danger')
100
        target.classList.add('glyphicon-eye-open', 'text-info')
101
        target.removeEventListener('click', this._handleActivate)
102
        target.addEventListener('click', this._handleDeactivate)
103
      })
104
  },
105
  _deactivate: function (e) {
106
    var target = e.currentTarget
107
    var id = target.getAttribute('data-user-id')
108
109
    var toSave = qs.stringify({
110
      id: id
111
    })
112
    
113
    this._ajax(
114
      {
115
        url: '/abe/users/deactivate',
116
        body: toSave,
117
        method: 'post'
118
      },
119
      () => {
120
        var childGlyph = target.querySelector('.glyphicon')
121
        childGlyph.classList.remove('glyphicon-eye-close', 'text-danger')
122
        childGlyph.classList.add('glyphicon-eye-open', 'text-info')
123
        target.classList.remove('glyphicon-eye-open', 'text-info')
124
        target.classList.add('glyphicon-eye-close', 'text-danger')
125
        target.removeEventListener('click', this._handleDeactivate)
126
        target.addEventListener('click', this._handleActivate)
127
      })
128
  },
129
  _edit: function (e) {
130
    var parent = e.currentTarget.parentNode.parentNode
131
    e.currentTarget.removeEventListener('click', this._handleEdit, true)
132
133
    parent.classList.add('editing')
134
    var closeUpdateBtn = parent.querySelector('[data-close-update]')
135
    closeUpdateBtn.removeEventListener('click', this._handleCloseUpdate)
136
    closeUpdateBtn.addEventListener('click', this._handleCloseUpdate)
137
  },
138
  _closeFormUpdate(target) {
139
    var parent = target.parentNode.parentNode.parentNode
140
    var edit = parent.querySelector('[data-edit]')
141
    parent.classList.remove('editing')
142
    edit.addEventListener('click', this._handleEdit, true)
143
    target.removeEventListener('click', this._handleCloseUpdate)
144
  },
145
  _closeUpdate: function (e) {
146
    this._closeFormUpdate(e.currentTarget)
147
  },
148
  _update: function (e) {
149
    var parent = e.currentTarget.parentNode.parentNode.parentNode
150
    var target = e.currentTarget
151
    var data = {
152
      id: target.getAttribute('data-user-id')
153
    }
154
155
    var inputs = parent.querySelectorAll('.form-control')
156
    var msg = ''
157
    var hasError = false
158
    Array.prototype.forEach.call(inputs, function(input) {
159
      data[input.name] = input.value
160
161
      if(input.name === 'email' && !this._validateEmail(input.value)) {
162
        hasError = true
163
        input.parentNode.classList.add('has-error')
164
        this._alert.classList.remove('hidden')
165
        msg += 'email is invalid<br />'
166
        return
0 ignored issues
show
Unused Code introduced by
This return has no effect and can be removed.
Loading history...
167
      }else if (input.value.trim() === '') {
168
        hasError = true
169
        input.parentNode.classList.add('has-error')
170
        this._alert.classList.remove('hidden')
171
        msg += input.name + ' is invalid<br />'
172
        return
0 ignored issues
show
Unused Code introduced by
This return has no effect and can be removed.
Loading history...
173
      }else {
174
        input.parentNode.classList.remove('has-error')
175
      }
176
    }.bind(this))
177
178
    if (hasError) {
179
      this._alert.innerHTML = msg
180
      return
181
    }else {
182
      this._alert.classList.add('hidden')
183
      this._alert.innerHTML = ''
184
    }
185
    var toSave = qs.stringify(data)
186
    
187
    this._ajax(
188
      {
189
        url: '/abe/users/update',
190
        body: toSave,
191
        method: 'post'
192
      },
193
      (code, responseText) => {
194
        var response = JSON.parse(responseText)
195
        if (response.success === 1) {
196
          Array.prototype.forEach.call(inputs, function(input) {
197
            input.parentNode.parentNode.querySelector('.value').innerHTML = input.value
198
          })
199
          this._closeFormUpdate(target)
200
        }else {
201
          this._alert.classList.remove('hidden')
202
          this._alert.innerHTML = response.message
203
        }
204
      })
205
  },
206
  _remove: function (e) {
207
    var confirmDelete = confirm(e.currentTarget.getAttribute('data-text'))
208
    if (!confirmDelete) return
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
209
210
    var target = e.currentTarget
211
    var id = target.getAttribute('data-user-id')
212
    var toSave = qs.stringify({
213
      id: id
214
    })
215
    
216
    this._ajax(
217
      {
218
        url: '/abe/users/remove',
219
        body: toSave,
220
        method: 'post'
221
      },
222
      () => {
223
        target.parentNode.parentNode.remove()
224
      })
225
  },
226
  _validateEmail: function(email) {
227
    var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
228
    return re.test(email)
229
  },
230
  _add: function () {
231
    this._alert.classList.add('hidden')
232
    var username = document.querySelector('[data-add-user-username]')
233
    if(typeof username.value === 'undefined' || username.value === null || username.value === '') {
234
      username.parentNode.classList.add('has-error')
235
      return 
236
    }
237
    username.parentNode.classList.remove('has-error')
238
239
    var name = document.querySelector('[data-add-user-name]')
240
    if(typeof name.value === 'undefined' || name.value === null || name.value === '') {
241
      name.parentNode.classList.add('has-error')
242
      return 
243
    }
244
    name.parentNode.classList.remove('has-error')
245
246
    var email = document.querySelector('[data-add-user-email]')
247
    if(typeof email.value === 'undefined' || email.value === null || email.value === '') {
248
      email.parentNode.classList.add('has-error')
249
      return 
250
    }
251
    if(!this._validateEmail(email.value)) {
252
      email.parentNode.classList.add('has-error')
253
      this._alert.classList.remove('hidden')
254
      this._alert.innerHTML = 'email is invalid'
255
      return
256
    }
257
    email.parentNode.classList.remove('has-error')
258
259
    var password = document.querySelector('[data-add-user-password]')
260
    if(typeof password.value === 'undefined' || password.value === null || password.value === '') {
261
      password.parentNode.classList.add('has-error')
262
      return 
263
    }
264
    
265
    password.parentNode.classList.remove('has-error')
266
267
    var role = document.querySelector('[data-add-user-role]')
268
    var toSave = qs.stringify({
269
      username: username.value,
270
      name: name.value,
271
      email: email.value,
272
      password: password.value,
273
      role: role.value
274
    })
275
    
276
    this._ajax(
277
      {
278
        url: '/abe/users/add',
279
        body: toSave,
280
        method: 'post'
281
      },
282
      (code, responseText) => {
283
        var data = JSON.parse(responseText)
284
        if(data.success === 1) {
285
          var tr = document.createElement('tr')
286
          var oldTr = document.querySelector('[data-user-base]')
287
          if(typeof oldTr !== 'undefined' && oldTr !== null) {
288
            tr.innerHTML = oldTr.innerHTML
289
290
            var tdUsername = tr.querySelector('.td-username')
291
            tdUsername.querySelector('.value').innerHTML = data.user.username
292
            tdUsername.querySelector('input').value = data.user.username
293
294
            var tdName = tr.querySelector('.td-name')
295
            tdName.querySelector('.value').innerHTML = data.user.name
296
            tdName.querySelector('input').value = data.user.name
297
298
            var tdEmail = tr.querySelector('.td-email')
299
            tdEmail.querySelector('.value').innerHTML = data.user.email
300
            tdEmail.querySelector('input').value = data.user.email
301
302
            var tdRole = tr.querySelector('.td-role')
303
            tdRole.querySelector('.value').innerHTML = data.user.role.name
304
            var tdRoleOptions = tdRole.querySelectorAll('select option')
305
            Array.prototype.forEach.call(tdRoleOptions, function(option) {
306
              if(option.value === data.user.role.name) {
307
                option.selected = 'selected'
308
              }
309
            })
310
311
            var tdActive = tr.querySelector('.td-active')
312
            var glypEyeClose = tdActive.querySelector('.glyphicon-eye-close')
313
            glypEyeClose.addEventListener('click', this._handleActivate, true)
314
            glypEyeClose.setAttribute('data-user-id', data.user.id)
315
316
            var tdActions = tr.querySelector('.td-actions')
317
            var glypEdit = tdActions.querySelector('.glyphicon-pencil')
318
            glypEdit.addEventListener('click', this._handleEdit, true)
319
            glypEdit.setAttribute('data-user-id', data.user.id)
320
321
            var glypOk = tdActions.querySelector('.glyphicon-ok')
322
            glypOk.addEventListener('click', this._handleUpdate, true)
323
            glypOk.setAttribute('data-user-id', data.user.id)
324
325
            var glypCloseUpdate = tdActions.querySelector('.glyphicon-remove')
326
            glypCloseUpdate.setAttribute('data-user-id', data.user.id)
327
            // glypCloseUpdate.addEventListener('click', this._handleCloseUpdate, true)
328
329
            var glypRemove = tdActions.querySelector('.glyphicon-trash')
330
            glypRemove.setAttribute('data-user-id', data.user.id)
331
            glypRemove.addEventListener('click', this._handleRemove, true)
332
          }
333
334
          this._table.appendChild(tr)
335
      
336
          username.value = ''
337
          name.value = ''
338
          email.value = ''
339
          password.value = ''
340
        }else {
341
          this._alert.classList.remove('hidden')
342
          this._alert.innerHTML = data.message
343
        }
344
      })
345
  }
346
}
347
348
export default usersList
349
350
// var userListEle = document.querySelector('.user-list');
351
// if(typeof userListEle !== 'undefined' && userListEle !== null) {
352
//   usersList.init(userListEle)
353
// }