diff options
author | Frederic Guillot <fred@kanboard.net> | 2017-01-02 17:01:27 -0500 |
---|---|---|
committer | Frederic Guillot <fred@kanboard.net> | 2017-01-02 17:01:27 -0500 |
commit | 3833c12ccce59bcc49c4cfa892401973558f604d (patch) | |
tree | b67b0e10cdc3d42e5626f728206138a444a40ed0 /assets/js/components | |
parent | d49ce63e51f596ad3bf0d02b689aea673cf544f8 (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.js | 192 | ||||
-rw-r--r-- | assets/js/components/keyboard-shortcuts.js | 18 | ||||
-rw-r--r-- | assets/js/components/modal.js | 29 | ||||
-rw-r--r-- | assets/js/components/screenshot.js | 120 | ||||
-rw-r--r-- | assets/js/components/select-dropdown-autocomplete.js | 45 | ||||
-rw-r--r-- | assets/js/components/submit-buttons.js | 94 | ||||
-rw-r--r-- | assets/js/components/suggest-menu.js | 2 | ||||
-rw-r--r-- | assets/js/components/task-move-position.js | 3 |
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()) |