diff options
Diffstat (limited to 'uploader/static/js')
| -rw-r--r-- | uploader/static/js/datatables.js | 132 | ||||
| -rw-r--r-- | uploader/static/js/debug.js | 40 | ||||
| -rw-r--r-- | uploader/static/js/files.js | 259 | ||||
| -rw-r--r-- | uploader/static/js/misc.js | 6 | ||||
| -rw-r--r-- | uploader/static/js/populations.js | 47 | ||||
| -rw-r--r-- | uploader/static/js/pubmed.js | 113 | ||||
| -rw-r--r-- | uploader/static/js/species.js | 45 | ||||
| -rw-r--r-- | uploader/static/js/upload_samples.js | 24 | ||||
| -rw-r--r-- | uploader/static/js/urls.js | 26 | ||||
| -rw-r--r-- | uploader/static/js/utils.js | 28 |
10 files changed, 604 insertions, 116 deletions
diff --git a/uploader/static/js/datatables.js b/uploader/static/js/datatables.js index 8d54a84..bfcda2a 100644 --- a/uploader/static/js/datatables.js +++ b/uploader/static/js/datatables.js @@ -1,57 +1,97 @@ /** Handlers for events in datatables **/ -var dtAddRowSelectionHandler = (tableId) => { - $(tableId).on("draw.dt", (event) => { - $(".chk-row-select").on("change", (event) => { - var checkboxOrRadio = event.target; - var tablerow = checkboxOrRadio.parentElement.parentElement; - var tableclass = tablerow.getAttribute("class"); - if(checkboxOrRadio.checked) { - if (checkboxOrRadio.type == "radio") { - $(tableId + " tr").each((index, row) => { - var rowKlass = $(row).attr("class") || ""; - row.setAttribute( - "class", rowKlass.replaceAll("selected", "").trim()); - }); - } - tablerow.setAttribute("class", `${tableclass} selected`); - } - else { - tablerow.setAttribute( - "class", tableclass.replaceAll("selected", "").trim()); - } - }); - }); +var addTableLength = (menuList, lengthToAdd, dataLength) => { + if(dataLength >= lengthToAdd) { + newList = structuredClone(menuList);//menuList.slice(0, menuList.length); // shallow copy + newList.push(lengthToAdd); + return newList; + } + return menuList; }; +var defaultLengthMenu = (data) => { + menuList = [] + var lengths = [10, 25, 50, 100, 1000]; + if(data.length > 1000) { + lengths.push(data.length) + } + lengths.forEach((len) => { + menuList = addTableLength(menuList, len, data.length); + }); + return menuList; +}; -var toggleCheck = (checkboxOrRadio) => { - if (checkboxOrRadio.length > 0) { - var currentState = checkboxOrRadio.prop("checked"); - var newState = !currentState; - if (currentState == true && checkboxOrRadio.attr("type").toLowerCase() == "radio") { - // We don't want to toggle from true to false by clicking on the row - // if it is a radio button. - newState = currentState; - } - checkboxOrRadio.prop("checked", newState); - checkboxOrRadio.trigger("change"); +var setRowCheckableProperty = (node, state) => { + /** + * Set a row's (`node`) checkbox's or radio button's checked state to the + * boolean value `state`. + **/ + if(typeof(state) == "boolean") { + var pseudoclass = state == false ? ":checked" : ":not(:checked)"; + var checkable = ( + $(node).find(`input[type="checkbox"]${pseudoclass}`)[0] + || + $(node).find(`input[type="radio"]${pseudoclass}`)[0]); + $(checkable).prop("checked", state); + } else { + throw new Error("`state` *MUST* be a boolean value.") } }; +var setRowChecked = (node) => {setRowCheckableProperty(node, true);}; +var setRowUnchecked = (node) => {setRowCheckableProperty(node, false);}; -var dtAddRowClickHandler = (tableId) => { - $(tableId).on("draw.dt", (event) => { - $(tableId + " tbody tr").on("click", (event) => { - var row = event.target.closest("tr"); - var checkboxOrRadio = $(row).find(".chk-row-select"); - toggleCheck(checkboxOrRadio); - }); +var buildDataTable = (tableId, data = [], columns = [], userSettings = {}) => { + var defaultSettings = { + responsive: true, + layout: { + topStart: null, + topEnd: null, + bottomStart: null, + bottomEnd: null, + }, + select: true, + lengthMenu: defaultLengthMenu(data), + language: { + processing: "Processing… Please wait.", + loadingRecords: "Loading table data… Please wait.", + lengthMenu: "", + info: "" + }, + drawCallback: function (settings) { + var api = this.api(); + api.rows({selected: true}).nodes().each((node, index) => { + setRowChecked(node); + }); + api.rows({selected: false}).nodes().each((node, index) => { + setRowUnchecked(node); + }); + } + } + var theDataTable = $(tableId).DataTable({ + ...defaultSettings, + ...userSettings, + ...(data.length == 0 ? {} : {data: data}), + ...(columns.length == 0 ? {} : {columns: columns}) + }); + theDataTable.on("select", (event, datatable, type, indexes) => { + datatable + .rows(indexes) + .nodes() + .each((node, index) => { + setRowChecked(node); + }); + }); + theDataTable.on("deselect", (event, datatable, type, indexes) => { + datatable + .rows(indexes) + .nodes() + .each(function(node, index) { + setRowUnchecked(node); + }); }); -}; - -var dtAddCommonHandlers = (tableId) => { - dtAddRowSelectionHandler(tableId); - dtAddRowClickHandler(tableId); -} + theDataTable.selectAll = () => {theDataTable.rows().select()}; + theDataTable.deselectAll = () => {theDataTable.rows().deselect()}; + return theDataTable; +}; diff --git a/uploader/static/js/debug.js b/uploader/static/js/debug.js new file mode 100644 index 0000000..eb01209 --- /dev/null +++ b/uploader/static/js/debug.js @@ -0,0 +1,40 @@ +/** + * The entire purpose of this function is for use to debug values inline + * without changing the flow of the code too much. + * + * This **MUST** be a non-arrow function to allow access to the `arguments` + * object. + * + * This function expects at least one argument. + * + * If more than one argument is provided, then: + * a) the last argument is considered the value, and will be returned + * b) all other arguments will be converted to string and output + * + * If only one argument is provided, it is considered the value, and will be + * returned. + * + * Zero arguments is an error condition. + **/ +function __pk__(val) { + /* Handle zero arguments */ + if (arguments.length < 1) { + throw new Error("Invalid arguments: Expected at least one argument."); + } + + msg = "/********** DEBUG **********/"; + if (arguments.length > 1) { + msg = Array.from( + arguments + ).slice( + 0, + arguments.length - 1 + ).map((val) => { + return String(val); + }).join("; ") + } + + value = arguments[arguments.length - 1]; + console.debug("/********** " + msg + " **********/", value); + return value; +} diff --git a/uploader/static/js/files.js b/uploader/static/js/files.js index 9d6bca1..7532df3 100644 --- a/uploader/static/js/files.js +++ b/uploader/static/js/files.js @@ -84,8 +84,9 @@ var errorHandler = makeResumableHandler("error"); var markResumableDragAndDropElement = (resumable, fileinput, droparea, browsebutton) => { if(resumable.support) { //Hide file input element and display drag&drop UI - add_class(fileinput, "hidden"); - remove_class(droparea, "hidden"); + add_class( + fileinput.closest(".non-resumable-elements"), "visually-hidden"); + remove_class(droparea, "visually-hidden"); // Define UI elements for browse and drag&drop resumable.assignDrop(droparea); @@ -96,7 +97,7 @@ var markResumableDragAndDropElement = (resumable, fileinput, droparea, browsebut }; -var makeResumableElement = (targeturi, fileinput, droparea, uploadbutton, filetype) => { +var makeResumableElement = (targeturi, droparea, filetype) => { var resumable = Resumable({ target: targeturi, fileType: filetype, @@ -116,3 +117,255 @@ var makeResumableElement = (targeturi, fileinput, droparea, uploadbutton, filety return resumable; }; + + +var CSVFilesMetadata = () => { + return { + "separator": $("#txt-file-separator").val(), + "comment_char": $( + "#txt-file-comment-character").val(), + "na_strings": $("#txt-file-na").val() + } +}; + + +var updatePreview = (table, filedata, formdata, numrows) => { + table.find("thead tr").remove() + table.find(".data-row").remove(); + var linenum = 0; + var tableheader = table.find("thead"); + var tablebody = table.find("tbody"); + var numheadings = 0; + var comment_chars = formdata + .comment_char + .split(" ") + .map((v) => {return v.trim();}) + .filter((v) => {return Boolean(v);}); + var navalues = formdata + .na_strings + .split(" ") + .map((v) => {return v.trim();}) + .filter((v) => {return Boolean(v);}); + filedata.forEach((line) => { + if(comment_chars.includes(line[0]) || linenum >= numrows) { + return false; + } + var row = $("<tr></tr>"); + line.split(formdata.separator) + .map((field) => { + var value = field.trim(); + if(navalues.includes(value)) { + return "[NO-VALUE]"; + } + return value; + }) + .filter((field) => { + return (field !== "" && field != undefined && field != null); + }) + .forEach((field) => { + if(linenum == 0) { + numheadings += 1; + var tablefield = $("<th></th>"); + tablefield.text(field); + row.append(tablefield); + } else { + add_class(row, "data-row"); + var tablefield = $("<td></td>"); + tablefield.text(field); + row.append(tablefield); + } + }); + + if(linenum == 0) { + tableheader.append(row); + } else { + tablebody.append(row); + } + linenum += 1; + }); + + if(table.find("tbody tr.data-row").length > 0) { + add_class(table.find(".data-row-template"), "visually-hidden"); + } else { + remove_class(table.find(".data-row-template"), "visually-hidden"); + } +}; + + +var makePreviewUpdater = (preview_table, preview_rows) => { + return (data) => { + updatePreview( + preview_table, + data, + CSVFilesMetadata(), + preview_rows); + }; +}; + + +var resumableDisplayFiles = (display_area, files) => { + files.forEach((file) => { + display_area.find(".file-display").remove(); + var display_element = display_area + .find(".file-display-template") + .clone(); + remove_class(display_element, "visually-hidden"); + remove_class(display_element, "file-display-template"); + add_class(display_element, "file-display"); + display_element.find(".filename").text(file.name + || file.fileName + || file.relativePath + || file.webkitRelativePath); + display_element.find(".filesize").text( + (file.size / (1024*1024)).toFixed(2) + "MB"); + display_element.find(".fileuniqueid").text(file.uniqueIdentifier); + display_element.find(".filemimetype").text(file.file.type); + display_area.append(display_element); + }); +}; + + +var indicateProgress = (resumable, progress_bar) => { + return () => {/*Has no event!*/ + var progress = (resumable.progress() * 100).toFixed(2); + var pbar = progress_bar.find(".progress-bar"); + remove_class(progress_bar, "visually-hidden"); + pbar.css("width", progress+"%"); + pbar.attr("aria-valuenow", progress); + pbar.text("Uploading: " + progress + "%"); + }; +}; + + +var retryUpload = (retry_button, cancel_button) => { + retry_button.on("click", (event) => { + resumable.files.forEach((file) => {file.retry();}); + add_class(retry_button, "visually-hidden"); + remove_class(cancel_button, "visually-hidden"); + add_class(browse_button, "visually-hidden"); + }); +}; + + +var cancelUpload = (cancel_button, retry_button) => { + cancel_button.on("click", (event) => { + resumable.files.forEach((file) => { + if(file.isUploading()) { + file.abort(); + } + }); + add_class(cancel_button, "visually-hidden"); + remove_class(retry_button, "visually-hidden"); + remove_class(browse_button, "visually-hidden"); + }); +}; + + +var startUpload = (browse_button, retry_button, cancel_button) => { + return (event) => { + remove_class(cancel_button, "visually-hidden"); + add_class(retry_button, "visually-hidden"); + add_class(browse_button, "visually-hidden"); + }; +}; + + +var makeFormSubmitter = function (form, processForm, num_files) { + var uploaded_files = new Set(); + + return function (new_file) { + uploaded_files.add(new_file); + if(uploaded_files.size === num_files) { + if(form.length !== 1) { + // TODO: Handle error somehow? + alert("The form was not provided. Bailing!"); + return false; + } + + $.ajax({ + "url": form.attr("action"), + "type": "POST", + "data": processForm(form[0], uploaded_files),// `uploaded_files` changes with each file added -- check this and fix. + "beforeSend": function(xhr) { + xhr.setRequestHeader("Accept", "application/json"); + }, + "processData": false, + "contentType": false, + "success": (data, textstatus, jqxhr) => { + // TODO: Redirect to endpoint that should come as part of the + // success/error message. + console.log("SUCCESS DATA: ", data); + console.log("SUCCESS STATUS: ", textstatus); + console.log("SUCCESS jqXHR: ", jqxhr); + window.location.assign(window.location.origin + data["redirect-to"]); + }, + }); + return false; + } + return false; + }; +}; + + +var uploadSuccess = (file_input_name, submitForm) => { + return (file, message) => { + submitForm({...JSON.parse(message), "file-input-name": file_input_name}); + }; +}; + + +var uploadError = (submitButton) => { + return (message, file) => { + submitButton.removeAttr("disabled"); + console.log("THE FILE:", file); + console.log("THE ERROR MESSAGE:", message); + }; +}; + +var makeResumableObject = ( + form_id, file_input_id, resumable_element_id, preview_table_id, submitForm, filetypes=["csv", "tsv", "txt"], preview_rows=5 +) => { + var the_form = $("#" + form_id); + var file_input = $("#" + file_input_id); + var submit_button = the_form.find("input[type=submit]"); + if(file_input.length != 1) { + return false; + } + var r = errorHandler( + fileSuccessHandler( + uploadStartHandler( + filesAddedHandler( + markResumableDragAndDropElement( + makeResumableElement( + the_form.attr("data-resumable-target"), + $("#" + resumable_element_id), + filetypes), + file_input, + $("#" + resumable_element_id), + $("#" + resumable_element_id + "-browse-button")), + (files) => { + // TODO: Also trigger preview! + resumableDisplayFiles( + $("#" + resumable_element_id + "-selected-files"), files); + files.forEach((file) => { + readFirstNLines( + file.file, + 100, + [makePreviewUpdater( + $("#" + preview_table_id), + preview_rows)]) + }); + }), + startUpload($("#" + resumable_element_id + "-browse-button"), + $("#" + resumable_element_id + "-retry-button"), + $("#" + resumable_element_id + "-cancel-button"))), + uploadSuccess(file_input.attr("name"), submitForm)), + uploadError(submit_button)); + + /** Setup progress indicator **/ + progressHandler( + r, + indicateProgress(r, $("#" + resumable_element_id + "-progress-bar"))); + + return r; +}; diff --git a/uploader/static/js/misc.js b/uploader/static/js/misc.js deleted file mode 100644 index cf7b39e..0000000 --- a/uploader/static/js/misc.js +++ /dev/null @@ -1,6 +0,0 @@ -"Miscellaneous functions and event-handlers" - -$(".not-implemented").click((event) => { - event.preventDefault(); - alert("This feature is not implemented yet. Please bear with us."); -}); diff --git a/uploader/static/js/populations.js b/uploader/static/js/populations.js index 5c1f848..111ebb7 100644 --- a/uploader/static/js/populations.js +++ b/uploader/static/js/populations.js @@ -1,19 +1,9 @@ -var populationDataTable = (populationdata) => { - var lengthMenu = [10, 25, 50, 100, 1000]; - if(populationdata.length > 1000) { - lengthMenu.push(populationdata.length) - } - $("#tbl-select-population").DataTable({ - responsive: true, - lengthMenu: lengthMenu, - language: { - processing: "Processing… Please wait.", - loadingRecords: "Loading population — Please wait.", - lengthMenu: "Show _MENU_ populations", - info: "Showing _START_ to _END_ of _TOTAL_ populations" - }, - data: populationdata, - columns: [ +$(() => { + var populationsDataTable = buildDataTable( + "#tbl-select-population", + JSON.parse( + $("#tbl-select-population").attr("data-populations-list")), + [ { data: (apopulation) => { return `<input type="radio" name="population_id"` @@ -23,17 +13,24 @@ var populationDataTable = (populationdata) => { } }, { + searchable: true, data: (apopulation) => { return `${apopulation.FullName} (${apopulation.InbredSetName})`; } } - ] - }); -}; - - -$(() => { - dtAddCommonHandlers("#tbl-select-population"); - populationDataTable(JSON.parse( - $("#tbl-select-population").attr("data-populations-list"))); + ], + { + select: "single", + paging: true, + scrollY: 500, + deferRender: true, + scroller: true, + scrollCollapse: true, + layout: { + topStart: "info", + topEnd: "search", + bottomStart: "pageLength", + bottomEnd: false + } + }); }); diff --git a/uploader/static/js/pubmed.js b/uploader/static/js/pubmed.js new file mode 100644 index 0000000..f425f49 --- /dev/null +++ b/uploader/static/js/pubmed.js @@ -0,0 +1,113 @@ +var extract_details = (pubmed_id, details) => { + var months = { + "jan": "January", + "feb": "February", + "mar": "March", + "apr": "April", + "may": "May", + "jun": "June", + "jul": "July", + "aug": "August", + "sep": "September", + "oct": "October", + "nov": "November", + "dec": "December" + }; + var _date = details[pubmed_id].pubdate.split(" "); + return { + "authors": details[pubmed_id].authors.map((authobj) => { + return authobj.name; + }), + "title": details[pubmed_id].title, + "journal": details[pubmed_id].fulljournalname, + "volume": details[pubmed_id].volume, + "pages": details[pubmed_id].pages, + "month": _date.length > 1 ? (months[_date[1].toLowerCase()] || "January") : "January", + "year": _date[0], + }; +}; + +var update_publication_details = (details) => { + Object.entries(details).forEach((entry) => {; + switch(entry[0]) { + case "authors": + $("#txt-publication-authors").val(entry[1].join(", ")); + break; + case "month": + $("#select-publication-month") + .children("option") + .each((index, child) => { + console.debug(entry[1].toLowerCase()); + child.selected = child.value == entry[1].toLowerCase(); + }); + default: + $("#txt-publication-" + entry[0]).val(entry[1]); + break; + } + }); +}; + +var fetch_publication_abstract = (pubmed_id, pub_details) => { + $.ajax("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi", + { + "method": "GET", + "data": { + "db": "pubmed", + "id": pubmed_id, + "rettype": "abstract", + "retmode": "xml" + }, + "success": (data, textStatus, jqXHR) => { + update_publication_details({ + ...pub_details, + ...{ + "abstract": Array.from(data + .getElementsByTagName( + "Abstract")[0] + .children) + .map((elt) => {return elt.textContent.trim();}) + .join("\r\n") + }}); + }, + "error": (jqXHR, textStatus, errorThrown) => {}, + "complete": (jqXHR, textStatus) => {}, + "dataType": "xml" + }); +}; + +var fetch_publication_details = (pubmed_id, complete_thunks) => { + error_display = $("#search-pubmed-id-error"); + error_display.text(""); + add_class(error_display, "visually-hidden"); + $.ajax("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi", + { + "method": "GET", + "data": {"db": "pubmed", "id": pubmed_id, "format": "json"}, + "success": (data, textStatus, jqXHR) => { + // process and update publication details + hasError = ( + Object.hasOwn(data, "error") || + Object.hasOwn(data.result[pubmed_id], "error")); + if(hasError) { + error_display.text( + "There was an error fetching a publication with " + + "the given PubMed ID! The error received " + + "was: '" + ( + data.error || + data.result[pubmed_id].error) + + "'. Please check ID you provided and try " + + "again."); + remove_class(error_display, "visually-hidden"); + } else { + fetch_publication_abstract( + pubmed_id, + extract_details(pubmed_id, data.result)); + } + }, + "error": (jqXHR, textStatus, errorThrown) => {}, + "complete": () => { + complete_thunks.forEach((thunk) => {thunk()}); + }, + "dataType": "json" + }); +}; diff --git a/uploader/static/js/species.js b/uploader/static/js/species.js index 1f2aa3b..fb0d2d2 100644 --- a/uploader/static/js/species.js +++ b/uploader/static/js/species.js @@ -1,19 +1,9 @@ -var speciesDataTable = (speciesdata) => { - var lengthMenu = [10, 25, 50, 100, 1000]; - if(speciesdata.length > 1000) { - lengthMenu.push(speciesdata.length) - } - $("#tbl-select-species").DataTable({ - responsive: true, - lengthMenu: lengthMenu, - language: { - processing: "Processing… Please wait.", - loadingRecords: "Loading species — Please wait.", - lengthMenu: "Show _MENU_ species", - info: "Showing _START_ to _END_ of _TOTAL_ species" - }, - data: speciesdata, - columns: [ +$(() => { + var speciesDataTable = buildDataTable( + "#tbl-select-species", + JSON.parse( + $("#tbl-select-species").attr("data-species-list")), + [ { data: (aspecies) => { return `<input type="radio" name="species_id"` @@ -26,12 +16,19 @@ var speciesDataTable = (speciesdata) => { return `${aspecies.FullName} (${aspecies.SpeciesName})`; } } - ] - }); -}; - -$(() => { - dtAddCommonHandlers("#tbl-select-species"); - speciesDataTable(JSON.parse( - $("#tbl-select-species").attr("data-species-list"))); + ], + { + select: "single", + paging: true, + scrollY: 500, + deferRender: true, + scroller: true, + scrollCollapse: true, + layout: { + topStart: "info", + topEnd: "search", + bottomStart: "pageLength", + bottomEnd: false + } + }); }); diff --git a/uploader/static/js/upload_samples.js b/uploader/static/js/upload_samples.js index aed536f..1c25a1d 100644 --- a/uploader/static/js/upload_samples.js +++ b/uploader/static/js/upload_samples.js @@ -87,20 +87,20 @@ function display_preview(event) { var data_preview_table = document.getElementById("tbl:samples-preview"); remove_rows(data_preview_table); - var separator = document.getElementById("select:separator").value; + var separator = document.getElementById("select-separator").value; if(separator === "other") { - separator = document.getElementById("txt:separator").value; + separator = document.getElementById("txt-separator").value; } if(separator == "") { display_error_row(data_preview_table, "Please provide a separator."); return false; } - var delimiter = document.getElementById("txt:delimiter").value; + var delimiter = document.getElementById("txt-delimiter").value; - var firstlineheading = document.getElementById("chk:heading").checked; + var firstlineheading = document.getElementById("chk-heading").checked; - var fileelement = document.getElementById("file:samples"); + var fileelement = document.getElementById("file-samples"); var preview_data = JSON.parse( fileelement.getAttribute("data-preview-content") || "[]"); if(preview_data.length == 0) { @@ -115,18 +115,18 @@ function display_preview(event) { delimiter)); } -document.getElementById("chk:heading").addEventListener( +document.getElementById("chk-heading").addEventListener( "change", display_preview); -document.getElementById("select:separator").addEventListener( +document.getElementById("select-separator").addEventListener( "change", display_preview); -document.getElementById("txt:separator").addEventListener( +document.getElementById("txt-separator").addEventListener( "keyup", display_preview); -document.getElementById("txt:delimiter").addEventListener( +document.getElementById("txt-delimiter").addEventListener( "keyup", display_preview); -document.getElementById("file:samples").addEventListener( +document.getElementById("file-samples").addEventListener( "change", (event) => { read_first_n_lines(event, - document.getElementById("file:samples"), + document.getElementById("file-samples"), 30, - document.getElementById("chk:heading").checked); + document.getElementById("chk-heading").checked); }); diff --git a/uploader/static/js/urls.js b/uploader/static/js/urls.js new file mode 100644 index 0000000..e3fb7c6 --- /dev/null +++ b/uploader/static/js/urls.js @@ -0,0 +1,26 @@ +function baseURL() { + return new URL(`${window.location.protocol}//${window.location.host}`); +}; + +function buildURLFromCurrentURL(pathname, searchParams = new URLSearchParams()) { + var uri = baseURL(); + uri.pathname=pathname; + var _search = new URLSearchParams(window.location.search); + searchParams.forEach(function(value, key) { + _search.set(key, value); + }); + uri.search = _search.toString(); + return uri +}; + +function deleteSearchParams(url, listOfParams = []) { + _params = new URLSearchParams(url.search); + listOfParams.forEach(function(paramName) { + _params.delete(paramName); + }); + + + newUrl = new URL(url.toString()); + newUrl.search = _params.toString(); + return newUrl; +} diff --git a/uploader/static/js/utils.js b/uploader/static/js/utils.js index 045dd47..62d3662 100644 --- a/uploader/static/js/utils.js +++ b/uploader/static/js/utils.js @@ -8,3 +8,31 @@ function trigger_change_event(element) { evt = new Event("change"); element.dispatchEvent(evt); } + + +var remove_class = (element, classvalue) => { + new_classes = (element.attr("class") || "").split(" ").map((val) => { + return val.trim(); + }).filter((val) => { + return ((val !== classvalue) && + (val !== "")) + }).join(" "); + + if(new_classes === "") { + element.removeAttr("class"); + } else { + element.attr("class", new_classes); + } +}; + + +var add_class = (element, classvalue) => { + remove_class(element, classvalue); + element.attr("class", + ((element.attr("class") || "") + " " + classvalue).trim()); +}; + +$(".not-implemented").click((event) => { + event.preventDefault(); + alert("This feature is not implemented yet. Please bear with us."); +}); |
