summaryrefslogtreecommitdiff
path: root/assets/js/components
diff options
context:
space:
mode:
authorFrederic Guillot <fred@kanboard.net>2017-01-02 17:01:27 -0500
committerFrederic Guillot <fred@kanboard.net>2017-01-02 17:01:27 -0500
commit3833c12ccce59bcc49c4cfa892401973558f604d (patch)
treeb67b0e10cdc3d42e5626f728206138a444a40ed0 /assets/js/components
parentd49ce63e51f596ad3bf0d02b689aea673cf544f8 (diff)
Refactoring/rewrite of modal boxes handling
Diffstat (limited to 'assets/js/components')
-rw-r--r--assets/js/components/confirm-buttons.js (renamed from assets/js/components/submit-cancel.js)21
-rw-r--r--assets/js/components/file-upload.js192
-rw-r--r--assets/js/components/keyboard-shortcuts.js18
-rw-r--r--assets/js/components/modal.js29
-rw-r--r--assets/js/components/screenshot.js120
-rw-r--r--assets/js/components/select-dropdown-autocomplete.js45
-rw-r--r--assets/js/components/submit-buttons.js94
-rw-r--r--assets/js/components/suggest-menu.js2
-rw-r--r--assets/js/components/task-move-position.js3
9 files changed, 496 insertions, 28 deletions
diff --git a/assets/js/components/submit-cancel.js b/assets/js/components/confirm-buttons.js
index ccb3c1d9..81abe016 100644
--- a/assets/js/components/submit-cancel.js
+++ b/assets/js/components/confirm-buttons.js
@@ -1,28 +1,27 @@
-KB.component('submit-cancel', function (containerElement, options) {
+KB.component('confirm-buttons', function (containerElement, options) {
var isLoading = false;
function onSubmit() {
isLoading = true;
- KB.find('#modal-submit-button').replace(buildButton());
- KB.trigger('modal.submit');
+ KB.find('#modal-confirm-button').replace(buildButton());
+ window.location.href = options.url;
}
function onCancel() {
- KB.trigger('modal.cancel');
- _KB.get('Popover').close();
+ KB.trigger('modal.close');
}
function onStop() {
isLoading = false;
- KB.find('#modal-submit-button').replace(buildButton());
+ KB.find('#modal-confirm-button').replace(buildButton());
}
function buildButton() {
var button = KB.dom('button')
.click(onSubmit)
- .attr('id', 'modal-submit-button')
- .attr('type', 'submit')
- .attr('class', 'btn btn-blue');
+ .attr('id', 'modal-confirm-button')
+ .attr('type', 'button')
+ .attr('class', 'btn btn-red');
if (isLoading) {
button
@@ -32,6 +31,10 @@ KB.component('submit-cancel', function (containerElement, options) {
;
}
+ if (options.tabindex) {
+ button.attr('tabindex', options.tabindex);
+ }
+
return button
.text(options.submitLabel)
.build();
diff --git a/assets/js/components/file-upload.js b/assets/js/components/file-upload.js
new file mode 100644
index 00000000..762eb6e1
--- /dev/null
+++ b/assets/js/components/file-upload.js
@@ -0,0 +1,192 @@
+KB.component('file-upload', function (containerElement, options) {
+ var inputFileElement = null;
+ var dropzoneElement = null;
+ var files = [];
+ var currentFileIndex = null;
+
+ function onProgress(e) {
+ if (e.lengthComputable) {
+ var progress = e.loaded / e.total;
+ var percentage = Math.floor(progress * 100);
+
+ KB.find('#file-progress-' + currentFileIndex).attr('value', progress);
+ KB.find('#file-percentage-' + currentFileIndex).replaceText('(' + percentage + '%)');
+ }
+ }
+
+ function onError() {
+ var errorElement = KB.dom('div').addClass('file-error').text(options.labelUploadError).build();
+ KB.find('#file-item-' + currentFileIndex).add(errorElement);
+ }
+
+ function onComplete() {
+ currentFileIndex++;
+
+ if (currentFileIndex < files.length) {
+ KB.http.uploadFile(options.url, files[currentFileIndex], onProgress, onComplete, onError);
+ } else {
+ KB.trigger('modal.stop');
+ KB.trigger('modal.hide');
+
+ var alertElement = KB.dom('div')
+ .addClass('alert')
+ .addClass('alert-success')
+ .text(options.labelSuccess)
+ .build();
+
+ var buttonElement = KB.dom('button')
+ .attr('type', 'button')
+ .addClass('btn')
+ .addClass('btn-blue')
+ .click(onCloseWindow)
+ .text(options.labelCloseSuccess)
+ .build();
+
+ KB.dom(dropzoneElement).replace(KB.dom('div').add(alertElement).add(buttonElement).build());
+ }
+ }
+
+ function onCloseWindow() {
+ window.location.reload();
+ }
+
+ function onSubmit() {
+ currentFileIndex = 0;
+ uploadFiles();
+ }
+
+ function onFileChange() {
+ files = inputFileElement.files;
+ showFiles();
+ }
+
+ function onClickFileBrowser() {
+ files = [];
+ currentFileIndex = 0;
+ inputFileElement.click();
+ }
+
+ function onDragOver(e) {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+ function onDrop(e) {
+ e.stopPropagation();
+ e.preventDefault();
+
+ files = e.dataTransfer.files;
+ showFiles();
+ }
+
+ function uploadFiles() {
+ if (files.length > 0) {
+ KB.http.uploadFile(options.url, files[currentFileIndex], onProgress, onComplete, onError);
+ }
+ }
+
+ function showFiles() {
+ if (files.length > 0) {
+ KB.trigger('modal.enable');
+
+ KB.dom(dropzoneElement)
+ .empty()
+ .add(buildFileListElement());
+ } else {
+ KB.trigger('modal.disable');
+
+ KB.dom(dropzoneElement)
+ .empty()
+ .add(buildInnerDropzoneElement());
+ }
+ }
+
+ function buildFileInputElement() {
+ return KB.dom('input')
+ .attr('id', 'file-input-element')
+ .attr('type', 'file')
+ .attr('name', 'files[]')
+ .attr('multiple', true)
+ .on('change', onFileChange)
+ .hide()
+ .build();
+ }
+
+ function buildInnerDropzoneElement() {
+ var dropzoneLinkElement = KB.dom('a')
+ .attr('href', '#')
+ .text(options.labelChooseFiles)
+ .click(onClickFileBrowser)
+ .build();
+
+ return KB.dom('div')
+ .attr('id', 'file-dropzone-inner')
+ .text(options.labelDropzone + ' ' + options.labelOr + ' ')
+ .add(dropzoneLinkElement)
+ .build();
+ }
+
+ function buildDropzoneElement() {
+ var dropzoneElement = KB.dom('div')
+ .attr('id', 'file-dropzone')
+ .add(buildInnerDropzoneElement())
+ .build();
+
+ dropzoneElement.ondragover = onDragOver;
+ dropzoneElement.ondrop = onDrop;
+ dropzoneElement.ondragover = onDragOver;
+
+ return dropzoneElement;
+ }
+
+ function buildFileListItem(index) {
+ var isOversize = false;
+ var progressElement = KB.dom('progress')
+ .attr('id', 'file-progress-' + index)
+ .attr('value', 0)
+ .build();
+
+ var percentageElement = KB.dom('span')
+ .attr('id', 'file-percentage-' + index)
+ .text('(0%)')
+ .build();
+
+ var itemElement = KB.dom('li')
+ .attr('id', 'file-item-' + index)
+ .add(progressElement)
+ .text(' ' + files[index].name + ' ')
+ .add(percentageElement);
+
+ if (files[index].size > options.maxSize) {
+ itemElement.add(KB.dom('div').addClass('file-error').text(options.labelOversize).build());
+ isOversize = true;
+ }
+
+ if (isOversize) {
+ KB.trigger('modal.disable');
+ }
+
+ return itemElement.build();
+ }
+
+ function buildFileListElement() {
+ var fileListElement = KB.dom('ul')
+ .attr('id', 'file-list')
+ .build();
+
+ for (var i = 0; i < files.length; i++) {
+ fileListElement.appendChild(buildFileListItem(i));
+ }
+
+ return fileListElement;
+ }
+
+ this.render = function () {
+ KB.on('modal.submit', onSubmit);
+
+ inputFileElement = buildFileInputElement();
+ dropzoneElement = buildDropzoneElement();
+ containerElement.appendChild(inputFileElement);
+ containerElement.appendChild(dropzoneElement);
+ };
+});
diff --git a/assets/js/components/keyboard-shortcuts.js b/assets/js/components/keyboard-shortcuts.js
index 22859bd6..046e027c 100644
--- a/assets/js/components/keyboard-shortcuts.js
+++ b/assets/js/components/keyboard-shortcuts.js
@@ -15,19 +15,19 @@ KB.keyboardShortcuts = function () {
} else if (forms.length > 1) {
if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA') {
$(document.activeElement).parents("form").submit();
- } else if (_KB.get("Popover").isOpen()) {
- $("#popover-container form").submit();
+ } else if (KB.modal.isOpen()) {
+ KB.modal.getForm().submit();
}
}
}
KB.onKey('?', function () {
- _KB.get("Popover").open($("body").data("keyboard-shortcut-url"));
+ KB.modal.open(KB.find('body').data('keyboardShortcutUrl'));
});
KB.onKey('Escape', function () {
if (! KB.exists('#suggest-menu')) {
- _KB.get("Popover").close();
+ KB.trigger('modal.close');
_KB.get("Dropdown").close();
}
});
@@ -49,25 +49,25 @@ KB.keyboardShortcuts = function () {
});
KB.onKey('n', function () {
- _KB.get("Popover").open($("#board").data("task-creation-url"));
+ KB.modal.open(KB.find('#board').data('taskCreationUrl'), 'large', false);
});
}
if (KB.exists('#task-view')) {
KB.onKey('e', function () {
- _KB.get("Popover").open(KB.find('#task-view').data('editUrl'));
+ KB.modal.open(KB.find('#task-view').data('editUrl'), 'large');
});
KB.onKey('c', function () {
- _KB.get("Popover").open(KB.find('#task-view').data('commentUrl'));
+ KB.modal.open(KB.find('#task-view').data('commentUrl'));
});
KB.onKey('s', function () {
- _KB.get("Popover").open(KB.find('#task-view').data('subtaskUrl'));
+ KB.modal.open(KB.find('#task-view').data('subtaskUrl'));
});
KB.onKey('l', function () {
- _KB.get("Popover").open(KB.find('#task-view').data('internalLinkUrl'));
+ KB.modal.open(KB.find('#task-view').data('internalLinkUrl'));
});
}
diff --git a/assets/js/components/modal.js b/assets/js/components/modal.js
new file mode 100644
index 00000000..f0867289
--- /dev/null
+++ b/assets/js/components/modal.js
@@ -0,0 +1,29 @@
+(function () {
+ function getLink(e) {
+ if (e.target.tagName === 'I') {
+ return e.target.parentNode.getAttribute('href');
+ }
+
+ return e.target.getAttribute('href')
+ }
+
+ KB.onClick('.js-modal-large', function (e) {
+ KB.modal.open(getLink(e), 'large', false);
+ });
+
+ KB.onClick('.js-modal-medium', function (e) {
+ KB.modal.open(getLink(e), 'medium', false);
+ });
+
+ KB.onClick('.js-modal-small', function (e) {
+ KB.modal.open(getLink(e), 'small', false);
+ });
+
+ KB.onClick('.js-modal-confirm', function (e) {
+ KB.modal.open(getLink(e), 'small');
+ });
+
+ KB.onClick('.js-modal-close', function () {
+ KB.modal.close();
+ });
+}());
diff --git a/assets/js/components/screenshot.js b/assets/js/components/screenshot.js
new file mode 100644
index 00000000..a110f9b1
--- /dev/null
+++ b/assets/js/components/screenshot.js
@@ -0,0 +1,120 @@
+KB.component('screenshot', function (containerElement) {
+ var pasteCatcher = null;
+ var inputElement = null;
+
+ function onPaste(e) {
+ // Firefox doesn't have the property e.clipboardData.items (only Chrome)
+ if (e.clipboardData && e.clipboardData.items) {
+ var items = e.clipboardData.items;
+
+ if (items) {
+ for (var i = 0; i < items.length; i++) {
+ // Find an image in pasted elements
+ if (items[i].type.indexOf("image") !== -1) {
+
+ var blob = items[i].getAsFile();
+
+ // Get the image as base64 data
+ var reader = new FileReader();
+ reader.onload = function (event) {
+ createImage(event.target.result);
+ };
+
+ reader.readAsDataURL(blob);
+ }
+ }
+ }
+ } else {
+
+ // Handle Firefox
+ setTimeout(checkInput, 100);
+ }
+ }
+
+ function initialize() {
+ destroy();
+
+ if (! window.Clipboard) {
+ // Insert the content editable at the top to avoid scrolling down in the board view
+ pasteCatcher = document.createElement('div');
+ pasteCatcher.id = 'screenshot-pastezone';
+ pasteCatcher.contentEditable = true;
+ pasteCatcher.style.opacity = 0;
+ pasteCatcher.style.position = 'fixed';
+ pasteCatcher.style.top = 0;
+ pasteCatcher.style.right = 0;
+ pasteCatcher.style.width = 0;
+ document.body.insertBefore(pasteCatcher, document.body.firstChild);
+
+ pasteCatcher.focus();
+
+ // Set the focus when clicked anywhere in the document
+ document.addEventListener('click', setFocus);
+
+ // Set the focus when clicked in screenshot dropzone
+ document.getElementById('screenshot-zone').addEventListener('click', setFocus);
+ }
+
+ window.addEventListener('paste', onPaste, false);
+ }
+
+ function destroy() {
+ if (pasteCatcher !== null) {
+ document.body.removeChild(pasteCatcher);
+ } else if (document.getElementById('screenshot-pastezone')) {
+ document.body.removeChild(document.getElementById('screenshot-pastezone'));
+ }
+
+ document.removeEventListener('click', setFocus);
+ pasteCatcher = null;
+ }
+
+ function setFocus() {
+ if (pasteCatcher !== null) {
+ pasteCatcher.focus();
+ }
+ }
+
+ function checkInput() {
+ var child = pasteCatcher.childNodes[0];
+
+ if (child) {
+ // If the user pastes an image, the src attribute
+ // will represent the image as a base64 encoded string.
+ if (child.tagName === 'IMG') {
+ createImage(child.src);
+ }
+ }
+
+ pasteCatcher.innerHTML = '';
+ }
+
+ function createImage(blob) {
+ var pastedImage = new Image();
+ pastedImage.src = blob;
+
+ // Send the image content to the form variable
+ pastedImage.onload = function() {
+ var sourceSplit = blob.split('base64,');
+ inputElement.value = sourceSplit[1];
+ };
+
+ var zone = document.getElementById('screenshot-zone');
+ zone.innerHTML = '';
+ zone.className = 'screenshot-pasted';
+ zone.appendChild(pastedImage);
+
+ destroy();
+ initialize();
+ }
+
+ this.render = function () {
+ inputElement = KB.dom('input')
+ .attr('type', 'hidden')
+ .attr('name', 'screenshot')
+ .build();
+
+ containerElement.appendChild(inputElement);
+ initialize();
+ };
+});
diff --git a/assets/js/components/select-dropdown-autocomplete.js b/assets/js/components/select-dropdown-autocomplete.js
index 9a8df180..adf48470 100644
--- a/assets/js/components/select-dropdown-autocomplete.js
+++ b/assets/js/components/select-dropdown-autocomplete.js
@@ -1,5 +1,24 @@
KB.component('select-dropdown-autocomplete', function(containerElement, options) {
- var componentElement, inputElement, inputHiddenElement;
+ var componentElement, inputElement, inputHiddenElement, chevronIconElement, loadingIconElement;
+
+ function onLoadingStart() {
+ KB.dom(loadingIconElement).show();
+ KB.dom(chevronIconElement).hide();
+ }
+
+ function onLoadingStop() {
+ KB.dom(loadingIconElement).hide();
+ KB.dom(chevronIconElement).show();
+ }
+
+ function onScroll() {
+ var menuElement = KB.find('#select-dropdown-menu');
+
+ if (menuElement) {
+ var componentPosition = componentElement.getBoundingClientRect();
+ menuElement.style('top', (document.body.scrollTop + componentPosition.bottom) + 'px');
+ }
+ }
function onKeyDown(e) {
switch (KB.utils.getKey(e)) {
@@ -66,8 +85,10 @@ KB.component('select-dropdown-autocomplete', function(containerElement, options)
destroyDropdownMenu();
if (options.redirect) {
- var regex = new RegExp(options.redirect.regex, 'g');
- window.location = options.redirect.url.replace(regex, value);
+ window.location = options.redirect.url.replace(new RegExp(options.redirect.regex, 'g'), value);
+ } else if (options.replace) {
+ onLoadingStart();
+ KB.modal.replace(options.replace.url.replace(new RegExp(options.replace.regex, 'g'), value));
}
}
@@ -158,7 +179,7 @@ KB.component('select-dropdown-autocomplete', function(containerElement, options)
return KB.dom('ul')
.attr('id', 'select-dropdown-menu')
- .style('top', componentPosition.bottom + 'px')
+ .style('top', (document.body.scrollTop + componentPosition.bottom) + 'px')
.style('left', componentPosition.left + 'px')
.style('width', componentPosition.width + 'px')
.style('maxHeight', (window.innerHeight - componentPosition.bottom - 20) + 'px')
@@ -203,11 +224,20 @@ KB.component('select-dropdown-autocomplete', function(containerElement, options)
}
this.render = function () {
- var dropdownIconElement = KB.dom('i')
+ KB.on('select.dropdown.loading.start', onLoadingStart);
+ KB.on('select.dropdown.loading.stop', onLoadingStop);
+
+ chevronIconElement = KB.dom('i')
.attr('class', 'fa fa-chevron-down select-dropdown-chevron')
.click(toggleDropdownMenu)
.build();
+ loadingIconElement = KB.dom('span')
+ .hide()
+ .addClass('select-loading-icon')
+ .add(KB.dom('i').attr('class', 'fa fa-spinner fa-pulse').build())
+ .build();
+
inputHiddenElement = KB.dom('input')
.attr('type', 'hidden')
.attr('name', options.name)
@@ -227,7 +257,8 @@ KB.component('select-dropdown-autocomplete', function(containerElement, options)
.addClass('select-dropdown-input-container')
.add(inputHiddenElement)
.add(inputElement)
- .add(dropdownIconElement)
+ .add(chevronIconElement)
+ .add(loadingIconElement)
.build();
containerElement.appendChild(componentElement);
@@ -237,5 +268,7 @@ KB.component('select-dropdown-autocomplete', function(containerElement, options)
KB.on(eventName, function() { inputElement.focus(); });
});
}
+
+ window.addEventListener('scroll', onScroll, false);
};
});
diff --git a/assets/js/components/submit-buttons.js b/assets/js/components/submit-buttons.js
new file mode 100644
index 00000000..b6ad3769
--- /dev/null
+++ b/assets/js/components/submit-buttons.js
@@ -0,0 +1,94 @@
+KB.component('submit-buttons', function (containerElement, options) {
+ var isLoading = false;
+ var isDisabled = options.disabled || false;
+ var submitLabel = options.submitLabel;
+ var formActionElement = null;
+
+ function onSubmit() {
+ isLoading = true;
+ KB.find('#modal-submit-button').replace(buildButton());
+ KB.trigger('modal.submit');
+ }
+
+ function onCancel() {
+ KB.trigger('modal.close');
+ }
+
+ function onStop() {
+ isLoading = false;
+ KB.find('#modal-submit-button').replace(buildButton());
+ }
+
+ function onDisable() {
+ isLoading = false;
+ isDisabled = true;
+ KB.find('#modal-submit-button').replace(buildButton());
+ }
+
+ function onEnable() {
+ isLoading = false;
+ isDisabled = false;
+ KB.find('#modal-submit-button').replace(buildButton());
+ }
+
+ function onHide() {
+ KB.dom(formActionElement).hide();
+ }
+
+ function onUpdateSubmitLabel(eventData) {
+ submitLabel = eventData.submitLabel;
+ KB.find('#modal-submit-button').replace(buildButton());
+ }
+
+ function buildButton() {
+ var button = KB.dom('button')
+ .attr('id', 'modal-submit-button')
+ .attr('type', 'submit')
+ .attr('class', 'btn btn-' + (options.color || 'blue'));
+
+ if (KB.modal.isOpen()) {
+ button.click(onSubmit);
+ }
+
+ if (options.tabindex) {
+ button.attr('tabindex', options.tabindex);
+ }
+
+ if (isLoading) {
+ button
+ .disable()
+ .add(KB.dom('i').attr('class', 'fa fa-spinner fa-pulse').build())
+ .text(' ')
+ ;
+ }
+
+ if (isDisabled) {
+ button.disable();
+ }
+
+ return button
+ .text(submitLabel)
+ .build();
+ }
+
+ this.render = function () {
+ KB.on('modal.stop', onStop);
+ KB.on('modal.disable', onDisable);
+ KB.on('modal.enable', onEnable);
+ KB.on('modal.hide', onHide);
+ KB.on('modal.submit.label', onUpdateSubmitLabel);
+
+ var formActionElementBuilder = KB.dom('div')
+ .attr('class', 'form-actions')
+ .add(buildButton());
+
+ if (KB.modal.isOpen()) {
+ formActionElementBuilder
+ .text(' ' + options.orLabel + ' ')
+ .add(KB.dom('a').attr('href', '#').click(onCancel).text(options.cancelLabel).build())
+ }
+
+ formActionElement = formActionElementBuilder.build();
+ containerElement.appendChild(formActionElement);
+ };
+});
diff --git a/assets/js/components/suggest-menu.js b/assets/js/components/suggest-menu.js
index 238a7d77..7f3e6f62 100644
--- a/assets/js/components/suggest-menu.js
+++ b/assets/js/components/suggest-menu.js
@@ -62,7 +62,7 @@ KB.component('suggest-menu', function(containerElement, options) {
}
function getParentElement() {
- var selectors = ['.popover-form', '#popover-content', 'body'];
+ var selectors = ['#modal-content form', '#modal-content', 'body'];
for (var i = 0; i < selectors.length; i++) {
var element = document.querySelector(selectors[i]);
diff --git a/assets/js/components/task-move-position.js b/assets/js/components/task-move-position.js
index e54c1324..a1cd969e 100644
--- a/assets/js/components/task-move-position.js
+++ b/assets/js/components/task-move-position.js
@@ -74,8 +74,6 @@ KB.component('task-move-position', function (containerElement, options) {
"column_id": getColumnId(),
"swimlane_id": getSwimlaneId(),
"position": position
- }).success(function () {
- window.location.reload(true);
}).error(function (response) {
if (response) {
onError(response.message);
@@ -150,7 +148,6 @@ KB.component('task-move-position', function (containerElement, options) {
KB.on('modal.submit', onSubmit);
var form = KB.dom('div')
- .on('submit', onSubmit)
.add(KB.dom('div').attr('id', 'message-container').build())
.add(KB.html.label(options.swimlaneLabel, 'form-swimlanes'))
.add(buildSwimlaneSelect())