Completed
Push — master ( c3a965...f6b863 )
by Albert
03:29
created

web/app.js   B

Complexity

Total Complexity 40
Complexity/F 1.14

Size

Lines of Code 202
Function Count 35

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 40
c 3
b 0
f 0
dl 0
loc 202
rs 8.2608
cc 0
nc 1
mnd 1
bc 35
fnc 35
bpm 1
cpm 1.1428
noi 1

23 Functions

Rating   Name   Duplication   Size   Complexity  
A DragAndDrop.isFile 0 3 1
A DragAndDrop.createNewFieldsets 0 5 2
A DragAndDrop.isFieldsetEmpty 0 6 1
A DragAndDrop.getFileName 0 3 1
A NewFormButton.onClick 0 3 1
A Fieldset.getNextIndex 0 5 1
A NewFormButton.init 0 4 1
A DragAndDrop.fillFieldset 0 10 1
A Fieldset.addNew 0 8 1
A DragAndDrop.appendOverlay 0 6 1
A DragAndDrop.handleDragEnter 0 8 1
A DragAndDrop.handleDragOver 0 5 1
A Fieldset.getElement 0 17 1
A DragAndDrop.hideOverlay 0 3 1
A DragAndDrop.getEmptyFieldsets 0 5 1
A Fieldset.remove 0 11 2
A DragAndDrop.init 0 9 1
A DragAndDrop.getFileContent 0 10 1
A DragAndDrop.handleDrop 0 19 1
A DragAndDrop.showOverlay 0 3 1
A DragAndDrop.getOverlayElement 0 7 1
A DragAndDrop.handleDragLeave 0 9 1
A DragAndDrop.isReadable 0 4 1

How to fix   Complexity   

Complexity

Complex classes like web/app.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
const Fieldset = {
2
  SPECIFIED_FORM_QUERY: '.code-form--file[data-index="%d"]',
3
  LAST_FORM_QUERY: '.code-form--file:last-of-type',
4
5
  addNew() {
6
    const lastForm = document.querySelector(this.LAST_FORM_QUERY);
7
    const newForm = this.getElement();
8
    const formsWrapper = lastForm.parentNode;
9
10
    newForm.querySelector('.code-form--button__delete').addEventListener('click', this.remove.bind(this));
11
    formsWrapper.insertBefore(newForm, lastForm.nextSibling);
12
  },
13
14
  remove(event) {
15
      const index = parseInt(event.target.parentNode.parentNode.getAttribute('data-index'));
16
17
      if (index === 0) {
18
          return;
19
      }
20
21
      // Add 3 because: +1 because [data-index] is 0-indexed and +2 because we've got two elements before.
22
      const form = document.querySelector(this.SPECIFIED_FORM_QUERY.replace(/%d/, index));
23
      form.parentNode.removeChild(form);
24
  },
25
26
  getElement() {
27
    const index = this.getNextIndex();
28
    const fieldset = document.createElement('fieldset');
29
    fieldset.setAttribute('class', 'code-form--file');
30
    fieldset.setAttribute('data-index', index);
31
32
    fieldset.innerHTML =
33
      `<label class="code-form--label" for="name[${index}]">Nazwa pliku (opcjonalna)</label>
34
      <div class="code-form--control-group">
35
         <input type="text" id="name[${index}]" name="name[${index}]" class="code-form--control code-form--control__filename code-form--control-group-fill">
36
         <button type="button" class="code-form--button code-form--button__delete">Usuń plik</button>
37
       </div>
38
       <label for="content[${index}]" class="code-form--label">Treść pliku</label>
39
       <textarea name="content[${index}]" id="content[${index}]" rows="10" class="code-form--control code-form--control__textarea" required></textarea>`;
40
41
    return fieldset;
42
  },
43
44
  getNextIndex() {
45
    const currentIndex = parseInt(document.querySelector(this.LAST_FORM_QUERY).getAttribute('data-index'), 10);
46
47
    return currentIndex + 1;
48
  },
49
};
50
51
const NewFormButton = {
52
  BUTTON_QUERY: '#new-file',
53
54
  init() {
55
    const button = document.querySelector(this.BUTTON_QUERY);
56
    button.addEventListener('click', () => this.onClick());
57
  },
58
59
  onClick() {
60
    Fieldset.addNew();
61
  },
62
};
63
64
const DragAndDrop = {
65
  FILE_FORM_QUERY: '.code-form--file',
66
  OVERLAY_WRAPPER_QUERY: 'body',
67
68
  init() {
69
    this.lastTarget = null;
70
71
    this.appendOverlay();
72
    this.handleDragEnter();
73
    this.handleDragLeave();
74
    this.handleDragOver();
75
    this.handleDrop();
76
  },
77
78
  handleDragEnter() {
79
    window.addEventListener('dragenter', (event) => {
80
      if (this.isFile(event)) {
81
        this.lastTarget = event.target;
82
        this.showOverlay();
83
      }
84
    });
85
  },
86
87
  handleDragLeave() {
88
    window.addEventListener('dragleave', (event) => {
89
      event.preventDefault();
90
91
      if (event.target === this.lastTarget) {
92
        this.hideOverlay();
93
      }
94
    });
95
  },
96
97
  handleDragOver() {
98
    window.addEventListener('dragover', (event) => {
99
      event.preventDefault();
100
    });
101
  },
102
103
  handleDrop() {
104
    window.addEventListener('drop', (event) => {
105
      event.preventDefault();
106
      this.hideOverlay();
107
108
      const files = [...event.dataTransfer.files]
109
          .filter(file => this.isReadable(file));
110
111
      if (files.length === 0) {
112
        return;
113
      }
114
115
      const fieldsetsToCreateNumber = files.length - this.getEmptyFieldsets().length;
116
      this.createNewFieldsets(fieldsetsToCreateNumber);
117
118
      const emptyFieldsets = this.getEmptyFieldsets();
119
      emptyFieldsets.forEach((fieldset, index) => this.fillFieldset(fieldset, files[index]))
120
    });
121
  },
122
123
  createNewFieldsets(number) {
124
    for(let i = 0; i < number; i++) {
125
      Fieldset.addNew();
126
    }
127
  },
128
129
  fillFieldset(fieldset, file) {
130
    const input = fieldset.querySelector('input');
131
    const textarea = fieldset.querySelector('textarea');
132
133
    input.value = this.getFileName(file);
134
135
    this.getFileContent(file).then(content => {
136
      textarea.value = content;
137
    });
138
  },
139
140
  getEmptyFieldsets() {
141
    const fieldsets = [...document.querySelectorAll(this.FILE_FORM_QUERY)];
142
143
    return fieldsets.filter(fieldset => this.isFieldsetEmpty(fieldset));
144
  },
145
146
  isFieldsetEmpty(fieldset) {
147
    const inputValue = fieldset.querySelector('input').value;
148
    const textareaValue = fieldset.querySelector('textarea').value;
149
150
    return inputValue + textareaValue === '';
151
  },
152
153
  isFile(event) {
154
    return event.dataTransfer.types.some(type => type === 'Files');
155
  },
156
157
  isReadable(file) {
158
    // Is it empty string, application/* or text/*?
159
    return /(^$|application\/.+|text\/.+)/.exec(file.type);
160
  },
161
162
  getFileName(file) {
163
    return file.name;
164
  },
165
166
  getFileContent(file) {
167
    return new Promise(resolve => {
168
      const fileReader = new FileReader();
0 ignored issues
show
Bug introduced by
The variable FileReader seems to be never declared. If this is a global, consider adding a /** global: FileReader */ 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...
169
170
      fileReader.readAsText(file);
171
      fileReader.onload = (event) => {
172
        resolve(event.target.result);
173
      };
174
    });
175
  },
176
177
  getOverlayElement() {
178
    const overlay =  document.createElement('div');
179
    overlay.classList.add('drop-overlay');
180
    overlay.innerHTML = `<p class="drop-overlay--text">Upuść pliki tutaj</p>`;
181
182
    return overlay;
183
  },
184
185
  appendOverlay() {
186
    const overlayWrapper = document.querySelector(this.OVERLAY_WRAPPER_QUERY);
187
    this.overlay = this.getOverlayElement();
188
189
    overlayWrapper.appendChild(this.overlay);
190
  },
191
192
  showOverlay() {
193
    this.overlay.classList.add('drop-overlay__active');
194
  },
195
196
  hideOverlay() {
197
    this.overlay.classList.remove('drop-overlay__active');
198
  },
199
};
200
201
DragAndDrop.init();
202
NewFormButton.init();
203