frayerjj-frontend 0.5.6 → 0.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -3
- package/src/avatarCropper.js +97 -0
- package/src/ckeupload.js +95 -0
- package/src/fileUpload.js +230 -0
- package/src/hasMany.js +152 -0
- package/src/index.js +0 -1
- package/src/init.js +147 -0
- package/src/loading.js +181 -0
- package/src/modal.js +291 -0
- package/src/msg.js +13 -0
- package/src/phoneInput.js +34 -0
- package/src/scss/styles.scss +1 -0
- package/src/session.js +37 -0
- package/src/validate.js +61 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frayerjj-frontend",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.7",
|
|
4
4
|
"description": "My base frontend for various projects",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -10,8 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"style": "src/scss/styles.scss",
|
|
12
12
|
"files": [
|
|
13
|
-
"src/
|
|
14
|
-
"src/scss/"
|
|
13
|
+
"src/"
|
|
15
14
|
],
|
|
16
15
|
"scripts": {
|
|
17
16
|
"build": "echo 'No build step required'",
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import Cropper from 'cropperjs';
|
|
2
|
+
import { modal } from './modal';
|
|
3
|
+
|
|
4
|
+
export const avatarCropper = {
|
|
5
|
+
|
|
6
|
+
export: (selection, redirect) => {
|
|
7
|
+
selection.$toCanvas().then(canvas => {
|
|
8
|
+
canvas.toBlob(blob => {
|
|
9
|
+
let formData = new FormData();
|
|
10
|
+
formData.append('file', blob, 'avatar.png');
|
|
11
|
+
// Send the blob to the server
|
|
12
|
+
fetch(window.location.href, {
|
|
13
|
+
method: 'POST',
|
|
14
|
+
body: formData,
|
|
15
|
+
headers: {
|
|
16
|
+
'X-CSRFToken': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
|
17
|
+
}
|
|
18
|
+
}).then(response => {
|
|
19
|
+
if (response.ok) {
|
|
20
|
+
// Show success message
|
|
21
|
+
modal.alert('Avatar updated successfully.');
|
|
22
|
+
// Redirect to the specified URL
|
|
23
|
+
window.location.href = redirect;
|
|
24
|
+
} else
|
|
25
|
+
// Show error message
|
|
26
|
+
modal.alert('Error updating avatar. Please try again.');
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
loadImage: (file, cropperImg, selection, exportBtn) => {
|
|
33
|
+
// If valid image
|
|
34
|
+
if (file && file.type.match(/image.*/)) {
|
|
35
|
+
// Load image
|
|
36
|
+
let reader = new FileReader();
|
|
37
|
+
reader.onload = () => {
|
|
38
|
+
// Set image on Cropper
|
|
39
|
+
cropperImg.onload = () => {
|
|
40
|
+
// Reset selector
|
|
41
|
+
selection.$reset();
|
|
42
|
+
selection.$center();
|
|
43
|
+
// Show export button
|
|
44
|
+
exportBtn.style.display = 'block';
|
|
45
|
+
}
|
|
46
|
+
cropperImg.src = e.target.result;
|
|
47
|
+
};
|
|
48
|
+
reader.readAsDataURL(file);
|
|
49
|
+
} else
|
|
50
|
+
// Show error message
|
|
51
|
+
modal.alert('Invalid file type. Please select an image file.');
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
init:() => {
|
|
55
|
+
let img = document.getElementById('avatar-cropper');
|
|
56
|
+
if (img) {
|
|
57
|
+
|
|
58
|
+
// Set the image to a transparent 1x1 pixel
|
|
59
|
+
img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
|
|
60
|
+
|
|
61
|
+
// Initialize the cropper
|
|
62
|
+
new Cropper(img);
|
|
63
|
+
let canvas = document.querySelector('cropper-canvas');
|
|
64
|
+
canvas.classList.add('container-fixed');
|
|
65
|
+
window.dispatchEvent(new Event('resize'));
|
|
66
|
+
let selection = document.querySelector('cropper-selection');
|
|
67
|
+
selection.style.overflow = 'hidden';
|
|
68
|
+
selection.style.borderRadius = '50%';
|
|
69
|
+
selection.aspectRatio = 1;
|
|
70
|
+
let cropperImg = document.querySelector('cropper-image');
|
|
71
|
+
|
|
72
|
+
// Set export button
|
|
73
|
+
let exportBtn = document.querySelector(cropperImg.getAttribute('data-export-btn'));
|
|
74
|
+
let redirectUrl = img.getAttribute('data-redirect');
|
|
75
|
+
exportBtn.style.display = 'none';
|
|
76
|
+
exportBtn.addEventListener('click', () => {
|
|
77
|
+
avatarCropper.export(selection, redirectUrl);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Create a file input element
|
|
81
|
+
let fileInput = document.createElement('input');
|
|
82
|
+
fileInput.setAttribute('type', 'file');
|
|
83
|
+
fileInput.setAttribute('accept', 'image/*');
|
|
84
|
+
fileInput.style.display = 'none';
|
|
85
|
+
img.insertAdjacentElement('afterend', fileInput);
|
|
86
|
+
fileInput.addEventListener('change', ev => {
|
|
87
|
+
avatarCropper.loadImage(ev.target.files[0], cropperImg, selection, exportBtn);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Set upload button
|
|
91
|
+
let uploadBtn = document.querySelector(img.getAttribute('data-upload-btn'));
|
|
92
|
+
uploadBtn.addEventListener('click', () => {
|
|
93
|
+
fileInput.click();
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|
package/src/ckeupload.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";
|
|
2
|
+
import { msg } from "./msg";
|
|
3
|
+
|
|
4
|
+
export const ckeupload = {
|
|
5
|
+
adapter: editor => {
|
|
6
|
+
editor.plugins.get('FileRepository').createUploadAdapter = loader => {
|
|
7
|
+
return new CkeUploadAdapter(loader, editor.config._config.extraParams.uri, editor.config._config.extraParams.token);
|
|
8
|
+
};
|
|
9
|
+
},
|
|
10
|
+
init: () => {
|
|
11
|
+
class CkeUploadAdapter {
|
|
12
|
+
constructor(loader, uri, token) {
|
|
13
|
+
this.loader = loader;
|
|
14
|
+
this.uri = uri;
|
|
15
|
+
this.token = token;
|
|
16
|
+
}
|
|
17
|
+
upload() {
|
|
18
|
+
return this.loader.file.then(file => new Promise((resolve, reject) => {
|
|
19
|
+
this._initRequest();
|
|
20
|
+
this._initListeners(resolve, reject, file);
|
|
21
|
+
this._sendRequest(file);
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
abort() {
|
|
25
|
+
if (this.xhr) this.xhr.abort();
|
|
26
|
+
}
|
|
27
|
+
_initRequest() {
|
|
28
|
+
const xhr = this.xhr = new XMLHttpRequest();
|
|
29
|
+
xhr.open('POST', this.uri, true);
|
|
30
|
+
xhr.setRequestHeader('X-CSRFToken', this.token);
|
|
31
|
+
xhr.responseType = 'json';
|
|
32
|
+
}
|
|
33
|
+
_initListeners(resolve, reject, file) {
|
|
34
|
+
const xhr = this.xhr;
|
|
35
|
+
const loader = this.loader;
|
|
36
|
+
const genericErrorText = `Couldn't upload file: ${ file.name }.`;
|
|
37
|
+
xhr.addEventListener( 'error', () => reject(genericErrorText) );
|
|
38
|
+
xhr.addEventListener( 'abort', () => reject() );
|
|
39
|
+
xhr.addEventListener( 'load', () => {
|
|
40
|
+
const response = xhr.response;
|
|
41
|
+
if (!response || response.error)
|
|
42
|
+
return reject(response && response.error ? response.error.message : genericErrorText);
|
|
43
|
+
resolve({ default: response.url });
|
|
44
|
+
});
|
|
45
|
+
if (xhr.upload) {
|
|
46
|
+
xhr.upload.addEventListener('progress', evt => {
|
|
47
|
+
if (evt.lengthComputable) {
|
|
48
|
+
loader.uploadTotal = evt.total;
|
|
49
|
+
loader.uploaded = evt.loaded;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
_sendRequest(file) {
|
|
55
|
+
const data = new FormData();
|
|
56
|
+
data.append('file', file);
|
|
57
|
+
this.xhr.send(data);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
window.editors = [];
|
|
61
|
+
document.querySelectorAll('.wysiwyg').forEach(el => {
|
|
62
|
+
if (el.getAttribute('data-bs-toggle') === 'tooltip') {
|
|
63
|
+
let placement = el.getAttribute('data-bs-placement'),
|
|
64
|
+
title = el.getAttribute('title');
|
|
65
|
+
el.parentElement.setAttribute('data-bs-toggle', 'tooltip');
|
|
66
|
+
el.parentElement.setAttribute('data-bs-placement', placement);
|
|
67
|
+
el.parentElement.setAttribute('title', title);
|
|
68
|
+
el.removeAttribute('data-bs-toggle');
|
|
69
|
+
el.removeAttribute('data-bs-placement');
|
|
70
|
+
el.removeAttribute('title');
|
|
71
|
+
}
|
|
72
|
+
ClassicEditor.create(el, {
|
|
73
|
+
licenseKey: 'GPL',
|
|
74
|
+
htmlSupport: {
|
|
75
|
+
allow: [{
|
|
76
|
+
name: /.*/,
|
|
77
|
+
attributes: true,
|
|
78
|
+
classes: true,
|
|
79
|
+
styles: true
|
|
80
|
+
}]
|
|
81
|
+
},
|
|
82
|
+
extraParams: {
|
|
83
|
+
uri: document.querySelector('meta[name="asset-upload"]').getAttribute('content'),
|
|
84
|
+
token: document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
|
85
|
+
},
|
|
86
|
+
extraPlugins: [ CkeUploadAdapter ]
|
|
87
|
+
}).then(editor => {
|
|
88
|
+
window.editors[el.id] = editor;
|
|
89
|
+
msg.verbose('CKEditor Initialized');
|
|
90
|
+
}).catch( error => {
|
|
91
|
+
console.error( 'There was a problem initializing the editor.', error );
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { loading } from "./loading";
|
|
2
|
+
import { msg } from "./msg";
|
|
3
|
+
import { modal } from "./modal";
|
|
4
|
+
|
|
5
|
+
export const fileUpload = {
|
|
6
|
+
|
|
7
|
+
imgFormats: ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml', 'image/webp'],
|
|
8
|
+
|
|
9
|
+
icons: {
|
|
10
|
+
'application/msword': 'fa-file-word',
|
|
11
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'fa-file-word',
|
|
12
|
+
'application/vnd.ms-excel': 'fa-file-excel',
|
|
13
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'fa-file-excel',
|
|
14
|
+
'application/vnd.ms-powerpoint': 'fa-file-powerpoint',
|
|
15
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'fa-file-powerpoint',
|
|
16
|
+
'application/pdf': 'fa-file-pdf',
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
sampleArgs: {
|
|
20
|
+
name: 'files', // Variable name
|
|
21
|
+
mimes: ['image/jpeg', 'image/png'], // Allowed MIME types
|
|
22
|
+
iconSize: 128, // Icon size
|
|
23
|
+
files: [ // Existing files
|
|
24
|
+
{ id: 1, name: 'file1.jpg', mime: 'image/jpeg', uri: '/path/to/file1.jpg' },
|
|
25
|
+
],
|
|
26
|
+
messages: { // Messages
|
|
27
|
+
instructions: 'Drag and drop files here or click the button below.',
|
|
28
|
+
browse: 'Browse',
|
|
29
|
+
close: 'Close',
|
|
30
|
+
view: 'View',
|
|
31
|
+
delete: 'Delete',
|
|
32
|
+
errorTitle: 'Error',
|
|
33
|
+
uploadError: 'Error uploading file.',
|
|
34
|
+
deleteError: 'Error deleting file.'
|
|
35
|
+
},
|
|
36
|
+
uris: { // URIs
|
|
37
|
+
upload: '/path/to/upload',
|
|
38
|
+
delete: '/path/to/delete'
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
build: {
|
|
43
|
+
|
|
44
|
+
core: args => {
|
|
45
|
+
let container = document.createElement('div');
|
|
46
|
+
let list = document.createElement('ul');
|
|
47
|
+
list.classList.add('upload-files');
|
|
48
|
+
Objects.values(args.files).forEach(file => {
|
|
49
|
+
list.insertAdjacentHTML('beforeend', fileUpload.build.item(args, file));
|
|
50
|
+
});
|
|
51
|
+
let input = document.createElement('input');
|
|
52
|
+
input.type = 'file';
|
|
53
|
+
input.style.display = 'none';
|
|
54
|
+
let instructions = document.createElement('div');
|
|
55
|
+
instructions.classList.add('upload-instructions');
|
|
56
|
+
let icon = document.createElement('span');
|
|
57
|
+
icon.classList.add('fas', 'fa-arrow-alt-circle-u');
|
|
58
|
+
instructions.appendChild(icon);
|
|
59
|
+
let text = document.createElement('p');
|
|
60
|
+
text.textContent = args.messages.instructions ?? fileUpload.sampleArgs.messages.instructions;
|
|
61
|
+
instructions.appendChild(text);
|
|
62
|
+
let button = document.createElement('button');
|
|
63
|
+
button.classList.add('btn', 'btn-outline-secondary', 'btn-browse');
|
|
64
|
+
button.textContent = args.messages.browse ?? fileUpload.sampleArgs.messages.browse;
|
|
65
|
+
instructions.appendChild(button);
|
|
66
|
+
container.appendChild(list);
|
|
67
|
+
container.appendChild(input);
|
|
68
|
+
container.appendChild(instructions);
|
|
69
|
+
return container.outerHTML;
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
item: (args, file) => {
|
|
73
|
+
let item = document.createElement('li');
|
|
74
|
+
item.classList.add('upload-file');
|
|
75
|
+
item.title = file.name;
|
|
76
|
+
item.setAttribute('data-uri', file.uri);
|
|
77
|
+
if (fileUpload.imgFormats.includes(file.mime))
|
|
78
|
+
item.classList.add('upload-type-image');
|
|
79
|
+
else
|
|
80
|
+
item.classList.add('upload-type-file');
|
|
81
|
+
let input = document.createElement('input');
|
|
82
|
+
input.type = 'hidden';
|
|
83
|
+
input.name = args.name + '[]';
|
|
84
|
+
input.value = file.id;
|
|
85
|
+
item.appendChild(input);
|
|
86
|
+
let icon = fileUpload.imgFormats.includes(file.mime) ? fileUpload.build.thumb(file) : fileUpload.build.icon(file);
|
|
87
|
+
item.appendChild(icon);
|
|
88
|
+
let heading = document.createElement('h6');
|
|
89
|
+
heading.textContent = file.name;
|
|
90
|
+
item.appendChild(heading);
|
|
91
|
+
let buttons = document.createElement('div');
|
|
92
|
+
buttons.classList.add('btn-group');
|
|
93
|
+
let view = document.createElement('button');
|
|
94
|
+
view.classList.add('btn', 'btn-outline-secondary', 'btn-view');
|
|
95
|
+
view.textContent = args.messages.view ?? fileUpload.sampleArgs.messages.view;
|
|
96
|
+
buttons.appendChild(view);
|
|
97
|
+
let del = document.createElement('button');
|
|
98
|
+
del.classList.add('btn', 'btn-outline-danger', 'btn-delete');
|
|
99
|
+
del.textContent = args.messages.delete ?? fileUpload.sampleArgs.messages.delete;
|
|
100
|
+
buttons.appendChild(del);
|
|
101
|
+
item.appendChild(buttons);
|
|
102
|
+
return item.outerHTML;
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
icon: file => {
|
|
106
|
+
let icon = document.createElement('span');
|
|
107
|
+
icon.classList.add('border', 'fas');
|
|
108
|
+
if (fileUpload.icons[file.mime])
|
|
109
|
+
icon.classList.add(fileUpload.icons[file.mime]);
|
|
110
|
+
else
|
|
111
|
+
icon.classList.add('fa-file');
|
|
112
|
+
icon.style.width = fileUpload.iconSize + 'px';
|
|
113
|
+
icon.style.height = fileUpload.iconSize + 'px';
|
|
114
|
+
return icon;
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
thumb: file => {
|
|
118
|
+
let thumb = document.createElement('img');
|
|
119
|
+
thumb.src = file.uri;
|
|
120
|
+
thumb.style.width = fileUpload.iconSize + 'px';
|
|
121
|
+
thumb.style.height = fileUpload.iconSize + 'px';
|
|
122
|
+
return thumb;
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
delete: (el, item) => {
|
|
127
|
+
loading.start(1, el);
|
|
128
|
+
let data = new FormData();
|
|
129
|
+
data.append('id', item.querySelector('input[type="hidden"]').value);
|
|
130
|
+
fetch(el.getAttribute('data-delete-uri'), {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
body: data,
|
|
133
|
+
headers: {
|
|
134
|
+
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
|
|
135
|
+
}
|
|
136
|
+
}).then(response => response.json()).then(result => {
|
|
137
|
+
if (result.success)
|
|
138
|
+
item.remove();
|
|
139
|
+
else
|
|
140
|
+
modal.alert(args.messages.errorTitle ?? fileUpload.sampleArgs.messages.errorTitle, args.messages.deleteError ?? fileUpload.sampleArgs.messages.deleteError);
|
|
141
|
+
loading.stop();
|
|
142
|
+
}).catch(error => {
|
|
143
|
+
modal.alert(args.messages.errorTitle ?? fileUpload.sampleArgs.messages.errorTitle, args.messages.deleteError ?? fileUpload.sampleArgs.messages.deleteError);
|
|
144
|
+
msg.error(error);
|
|
145
|
+
loading.stop();
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
init: () => {
|
|
150
|
+
document.querySelectorAll('.file-upload').forEach(el => {
|
|
151
|
+
// Get arguments
|
|
152
|
+
let args = JSON.parse(el.innerHTML);
|
|
153
|
+
// Build has many input
|
|
154
|
+
el.innerHTML = fileUpload.build.core(args);
|
|
155
|
+
// Add event listeners
|
|
156
|
+
el.querySelector('.btn-browse').addEventListener('click', () => {
|
|
157
|
+
el.querySelector('input[type="file"]').click();
|
|
158
|
+
});
|
|
159
|
+
el.querySelector('input[type="file"]').addEventListener('change', ev => {
|
|
160
|
+
fileUpload.upload(el, args, ev.target.files);
|
|
161
|
+
});
|
|
162
|
+
el.addEventListener('dragover', ev => {
|
|
163
|
+
ev.preventDefault();
|
|
164
|
+
el.classList.add('dragover');
|
|
165
|
+
});
|
|
166
|
+
el.addEventListener('dragleave', ev => {
|
|
167
|
+
el.classList.remove('dragover');
|
|
168
|
+
});
|
|
169
|
+
el.addEventListener('drop', ev => {
|
|
170
|
+
ev.preventDefault();
|
|
171
|
+
el.classList.remove('dragover');
|
|
172
|
+
fileUpload.upload(el, args, ev.dataTransfer.files);
|
|
173
|
+
});
|
|
174
|
+
el.querySelectorAll('.btn-delete').forEach(btn => {
|
|
175
|
+
btn.addEventListener('click', () => {
|
|
176
|
+
fileUpload.delete(el, btn.closest('.upload-file'));
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
el.querySelectorAll('.btn-view').forEach(btn => {
|
|
180
|
+
btn.addEventListener('click', () => {
|
|
181
|
+
fileUpload.view(args, btn.closest('.upload-file'));
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
upload: (el, file, args) => {
|
|
188
|
+
loading.start(1, el);
|
|
189
|
+
let data = new FormData();
|
|
190
|
+
data.append('owner', args.name);
|
|
191
|
+
data.append('file', file[0]);
|
|
192
|
+
fetch(args.uris.upload, {
|
|
193
|
+
method: 'POST',
|
|
194
|
+
body: data,
|
|
195
|
+
headers: {
|
|
196
|
+
'multipart': 'form-data',
|
|
197
|
+
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
|
|
198
|
+
}
|
|
199
|
+
}).then(response => response.json()).then(result => {
|
|
200
|
+
if (result.success)
|
|
201
|
+
el.querySelector('.upload-files').insertAdjacentHTML('beforeend', fileUpload.build.item(args, result.file));
|
|
202
|
+
else
|
|
203
|
+
modal.alert(args.messages.errorTitle ?? fileUpload.sampleArgs.messages.errorTitle, args.messages.uploadError ?? fileUpload.sampleArgs.messages.uploadError);
|
|
204
|
+
loading.stop();
|
|
205
|
+
}).catch(error => {
|
|
206
|
+
modal.alert(args.messages.errorTitle ?? fileUpload.sampleArgs.messages.errorTitle, args.messages.uploadError ?? fileUpload.sampleArgs.messages.uploadError);
|
|
207
|
+
msg.error(error);
|
|
208
|
+
loading.stop();
|
|
209
|
+
});
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
view: (args, item) => {
|
|
213
|
+
if (item.classList.contains('upload-type-image')) {
|
|
214
|
+
let body = document.createElement('img');
|
|
215
|
+
body.src = item.querySelector('img').src;
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
let body = document.createElement('iframe');
|
|
219
|
+
body.src = `https://docs.google.com/gview?url=${window.location.protocol}//${window.location.host}${item.getAttribute('data-uri')}&embedded=true`;
|
|
220
|
+
}
|
|
221
|
+
let randomId = modal.randomId('fileview');
|
|
222
|
+
document.querySelector('body').insertAdjacentHTML('beforeend', modal.build.modal({
|
|
223
|
+
id: randomId,
|
|
224
|
+
className: 'modal-lg',
|
|
225
|
+
title: item.title,
|
|
226
|
+
body: body.outerHTML,
|
|
227
|
+
buttons: [{ text: args.messages.close ?? fileUpload.sampleArgs.messages.close }]
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
230
|
+
};
|
package/src/hasMany.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { msg } from './msg.js';
|
|
2
|
+
import { modal } from './modal.js';
|
|
3
|
+
|
|
4
|
+
export const hasMany = {
|
|
5
|
+
|
|
6
|
+
sampleArgs: {
|
|
7
|
+
name: 'name', // Variable name
|
|
8
|
+
title: 'Title', // Verbose name
|
|
9
|
+
notb: false, // Use None of the Below
|
|
10
|
+
messages: {
|
|
11
|
+
notbLabel: 'None', // Label for None of the Below
|
|
12
|
+
close: 'Close', // Close button text
|
|
13
|
+
button: 'Select Objects' // OK button text
|
|
14
|
+
},
|
|
15
|
+
options: [
|
|
16
|
+
{ id: 1, label: 'Option 1', checked: false },
|
|
17
|
+
{ id: 2, label: 'Option 2', checked: true }
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
build: {
|
|
22
|
+
|
|
23
|
+
core: args => {
|
|
24
|
+
msg.verbose('Building Has Many Inupt');
|
|
25
|
+
// Create container
|
|
26
|
+
let container = document.createElement('div');
|
|
27
|
+
// Create list of selected items
|
|
28
|
+
let list = document.createElement('ul');
|
|
29
|
+
list.id = 'id_' + args.name;
|
|
30
|
+
list.className = 'list-group list-group-flush';
|
|
31
|
+
// Create list of all options for modal
|
|
32
|
+
let modalList = document.createElement('ul');
|
|
33
|
+
modalList.className = 'list-group list-group-flush';
|
|
34
|
+
// Create hidden input for when there is no selected items
|
|
35
|
+
let input = document.createElement('input');
|
|
36
|
+
input.type = 'hidden';
|
|
37
|
+
input.name = args.name + '[]';
|
|
38
|
+
// If using None of the Below
|
|
39
|
+
if (args.notb)
|
|
40
|
+
modalList.insertAdjacentHTML('beforeend', hasMany.build.checkItem(args.name, {
|
|
41
|
+
id: 'notb',
|
|
42
|
+
label: (args.notbLabel ?? hasMany.sampleArgs.messages.notbLabel),
|
|
43
|
+
notb: true
|
|
44
|
+
}));
|
|
45
|
+
// Cycle through all options
|
|
46
|
+
Objects.values(args.options).forEach(option => {
|
|
47
|
+
modalList.insertAdjacentHTML('beforeend', hasMany.build.checkItem(args.name, option));
|
|
48
|
+
// If option is selected, add to list
|
|
49
|
+
if (option.checked)
|
|
50
|
+
list.insertAdjacentHTML('beforeend', hasMany.build.item(args.name, option));
|
|
51
|
+
});
|
|
52
|
+
// Create modal
|
|
53
|
+
let randomId = modal.randomId('hasmany');
|
|
54
|
+
document.querySelector('body').insertAdjacentHTML('beforeend', modal.build.modal({
|
|
55
|
+
id: randomId,
|
|
56
|
+
title: args.title,
|
|
57
|
+
body: modalList.outerHTML,
|
|
58
|
+
buttons: [ { text: args.messages.close ?? hasMany.sampleArgs.messages.close } ]
|
|
59
|
+
}));
|
|
60
|
+
// Create button to open modal
|
|
61
|
+
let button = document.createElement('button');
|
|
62
|
+
button.type = 'button';
|
|
63
|
+
button.className = 'btn btn-outline-secondary btn-block';
|
|
64
|
+
button.setAttribute('data-bs-toggle', 'modal');
|
|
65
|
+
button.setAttribute('data-bs-target', '#' + randomId);
|
|
66
|
+
button.textContent = args.messages.button ?? hasMany.sampleArgs.messages.button;
|
|
67
|
+
// Add elements to container
|
|
68
|
+
container.appendChild(input);
|
|
69
|
+
container.appendChild(list);
|
|
70
|
+
container.appendChild(button);
|
|
71
|
+
// Return container
|
|
72
|
+
return container.outerHTML;
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
checkItem: (name, option) => {
|
|
76
|
+
// Create list item
|
|
77
|
+
let item = document.createElement('li');
|
|
78
|
+
item.className = 'list-group-item';
|
|
79
|
+
// Create checkbox
|
|
80
|
+
let input = document.createElement('input');
|
|
81
|
+
input.classList.add('form-check-input');
|
|
82
|
+
if (option.notb)
|
|
83
|
+
input.classList.add('hasmany-' + name + 'notb');
|
|
84
|
+
else
|
|
85
|
+
input.classList.add('hasmany-' + name);
|
|
86
|
+
input.type = 'checkbox';
|
|
87
|
+
if (option.checked)
|
|
88
|
+
input.checked = true
|
|
89
|
+
input.id = name + option.id;
|
|
90
|
+
input.value = id;
|
|
91
|
+
item.appendChild(input);
|
|
92
|
+
// Create label
|
|
93
|
+
let label = document.createElement('label');
|
|
94
|
+
label.setAttribute('for', name + option.id);
|
|
95
|
+
label.textContent = option.label;
|
|
96
|
+
item.appendChild(label);
|
|
97
|
+
return item.outerHTML;
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
listItem: (name, option) => {
|
|
101
|
+
// Create list item
|
|
102
|
+
let item = document.createElement('li');
|
|
103
|
+
item.id = 'id_' + name + '_' + option.id;
|
|
104
|
+
item.textContent = option.label;
|
|
105
|
+
item.className = 'list-group-item';
|
|
106
|
+
// Create hidden input
|
|
107
|
+
let input = document.createElement('input');
|
|
108
|
+
input.type = 'hidden';
|
|
109
|
+
input.name = name + '[]';
|
|
110
|
+
input.value = option.id;
|
|
111
|
+
item.appendChild(input);
|
|
112
|
+
return item.outerHTML;
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
init: () => {
|
|
117
|
+
document.querySelectorAll('.has-many').forEach(el => {
|
|
118
|
+
// Get arguments
|
|
119
|
+
let args = JSON.parse(el.innerHTML);
|
|
120
|
+
// Build has many input
|
|
121
|
+
el.innerHTML = hasMany.build.core(args);
|
|
122
|
+
// Add event listeners
|
|
123
|
+
document.querySelectorAll('.hasmany-' + args.name).forEach(input => {
|
|
124
|
+
input.addEventListener('change', ev => {
|
|
125
|
+
if (ev.target.checked) {
|
|
126
|
+
// Add item to list
|
|
127
|
+
document.querySelector('#id_' + args.name).insertAdjacentHTML('beforeend', hasMany.build.item(args.name, {
|
|
128
|
+
id: ev.target.value,
|
|
129
|
+
label: ev.target.nextElementSibling.textContent
|
|
130
|
+
}));
|
|
131
|
+
// If using None of the Below, make sure it is unchecked
|
|
132
|
+
if (args.notb)
|
|
133
|
+
document.querySelector('.hasmany-' + args.name + 'notb').checked = false;
|
|
134
|
+
} else
|
|
135
|
+
// Remove item from list
|
|
136
|
+
document.querySelector('#id_' + args.name + '_' + ev.target.value).remove();
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
// Add event listener for None of the Below
|
|
140
|
+
if (args.notb)
|
|
141
|
+
document.querySelector('.hasmany-' + args.name + 'notb').addEventListener('change', ev => {
|
|
142
|
+
if (ev.target.checked)
|
|
143
|
+
// Uncheck all other items
|
|
144
|
+
document.querySelectorAll('hasmany-' + args.name).forEach(input => {
|
|
145
|
+
input.checked = false;
|
|
146
|
+
});
|
|
147
|
+
// Remove all items from list
|
|
148
|
+
document.querySelector('#id_' + args.name).innerHTML = '';
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
package/src/index.js
CHANGED