///////////////////////////////////////////////////////////////// // Sircl 2.x - Core extension // www.getsircl.com // Copyright (c) 2019-2023 Rudi Breedenraedt // Sircl is released under the MIT license, see sircl-license.txt ///////////////////////////////////////////////////////////////// /* tslint:disabled */ // Initialize sircl lib: if (typeof sircl === "undefined") console.warn("The 'sircl-extended' component should be registered after the 'sircl' component. Please review order of script files."); //#region Extended event-actions /// Load event-actions: /////////////////////// sircl.addAttributeAlias(".beforeload-show", "beforeload-show", ":this"); sircl.addAttributeAlias(".beforeload-hide", "beforeload-hide", ":this"); sircl.addRequestHandler("beforeSend", function sircl_ext_beforeSend_requestHandler(req) { req.$initialTarget.find("[beforeload-hide]").each(function () { sircl.ext.visible(sircl.ext.$select($(this), $(this).attr("beforeload-hide")), false); }); req.$initialTarget.find("[beforeload-show]").each(function () { sircl.ext.visible(sircl.ext.$select($(this), $(this).attr("beforeload-show")), true); }); req.$initialTarget.find("[beforeload-removeclass]").each(function () { sircl.ext.removeClass($(this), $(this).attr("beforeload-removeclass")); }); req.$initialTarget.find("[beforeload-addclass]").each(function () { sircl.ext.addClass($(this), $(this).attr("beforeload-addclass")); }); req.$initialTarget.find("[beforeload-toggleclass]").each(function () { sircl.ext.toggleClass($(this), $(this).attr("beforeload-toggleclass")); }); // Move to next handler: this.next(req); }); /// Init event-actions: /////////////////////// sircl.addAttributeAlias(".onload-show", "onload-show", ":this"); sircl.addAttributeAlias(".onload-hide", "onload-hide", ":this"); $$("enrich", function sircl_ext_onload_enrichHandler() { $(this).find(".onload-setvaluefromquery").each(function () { $(this).attr("onload-setvaluefromquery", this.name); }); }); $$(function sircl_ext_onload_processHandler() { /// <* onload-hide="selector"> Will make that element invisible on init. $(this).find("[onload-hide]").each(function () { sircl.ext.visible(sircl.ext.$select($(this), $(this).attr("onload-hide")), false); }); /// <* onload-show="selector"> Will make that element visible on init. $(this).find("[onload-show]").each(function () { sircl.ext.visible(sircl.ext.$select($(this), $(this).attr("onload-show")), true); }); /// <* onload-removeclass="classname [on selector]"> When initializing, removes the class to self or the given selector. $(this).find("[onload-removeclass]").each(function () { sircl.ext.removeClass($(this), $(this).attr("onload-removeclass")); }); /// <* onload-addclass="classname [on selector]"> When initializing, adds the class to self or the given selector. $(this).find("[onload-addclass]").each(function () { sircl.ext.addClass($(this), $(this).attr("onload-addclass")); }); /// <* onload-toggleclass="classname [on selector]"> When initializing, toggles the class to self or the given selector. $(this).find("[onload-toggleclass]").each(function () { sircl.ext.toggleClass($(this), $(this).attr("onload-toggleclass")); }); /// When initializing, evaluates the javascript expression to set the value. $(this).find("[onload-setvalue]").each(function () { var jsexpr = this.getAttribute("onload-setvalue"); var value = eval(jsexpr); this.value = value; this.removeAttribute("onload-setvalue"); $(this).trigger("change"); }); /// Sets the value of the input to the named querystring parameter. $(this).find("[onload-setvaluefromquery]").each(function () { $(this).attr("value", sircl.ext.getUrlParameter($(this).attr("onload-setvaluefromquery"))); $(this).trigger("change"); }); // <* onclick-scrollintoview="selector"> On click scrolls the (first) match of the selector into the view. $(this).find(".onload-scrollintoview").each(function () { this.scrollIntoView(); }); /// Select all text when element gets focus: /// (Can be placed on the input element itself, or one of its parents, i.e. the FORM element) $(document).on("focus", ".onfocus-select", function (event) { // If we get a focus event, select all content: if (event.target === document.activeElement && $(event.target).is("INPUT:not([type=checkbox]):not([type=radio]):not([type=button]):not(.onfocus-noselect)")) { event.target.select(); } }); $(document).on("change", ".onfocus-select", function (event) { // If we get a change on the active element, it's probably an autofill (unless it has an oninput-changeafter attribute), we then reselect all content: if (event.target === document.activeElement && $(event.target).is("INPUT:not([type=checkbox]):not([type=radio]):not([type=button]):not(.onfocus-noselect):not([oninput-changeafter])")) { event.target.select(); } }); /// Trims the text on focus out: /// (Can be placed on the input element itself, or one of its parents, i.e. the FORM element) /// (Though named an onfocusout event-action, technically implemented using a change event on document body, so it is done before all other change events.) $(document.body).on("change", ".onfocusout-trim", function (event) { if ($(event.target).is("INPUT:not([type=checkbox]):not([type=radio]):not([type=button]):not(.onfocusout-notrim):not([oninput-changeafter])")) { event.target.value = (event.target.value + "").trim() } }); }); /// Scroll/Viewport event-actions: ///////////////////////// // From: https://stackoverflow.com/a/7557433/323122 sircl.isElementInView = sircl.isElementInView || function (el) { var rect = el.getBoundingClientRect(); return ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /* or $(window).height() */ rect.right <= (window.innerWidth || document.documentElement.clientWidth) /* or $(window).width() */ ); }; document.addEventListener("DOMContentLoaded", function () { $(window).on("DOMContentLoaded load resize scroll", function () { /// <* class="onscrolltop-fade"> Makes the element visible when scrolling down (using a fading animation), hidden when scrolled at top. if ($(this).scrollTop() > 100) { if ($.fn.fadeIn) { // fadeIn/Out is not available in slim version if jQuery $(".onscrolltop-fade").fadeIn(800); } sircl.ext.visible($(".onscrolltop-fade"), true); } else { if ($.fn.fadeOut) { // fadeIn/Out is not available in slim version if jQuery $(".onscrolltop-fade").fadeOut(400); } sircl.ext.visible($(".onscrolltop-fade"), false); } /// <* ifinview-load="url"> Loads the given URL and places the result in the element when the element is visible in the view. $("[ifinview-load]").each(function () { if (sircl.isElementInView(this)) { var url = $(this).attr("ifinview-load"); $(this).removeAttr("ifinview-load"); $(this).load(url); } }); /// <* ifinview-click="selector"> Clicks the given element when this element is visible in the view. $("[ifinview-click]").each(function () { if (sircl.isElementInView(this)) { var selector$ = $(this).attr("ifinview-click"); $(this).removeAttr("ifinview-click"); sircl.ext.$select($(this), selector$).each(function () { this.click(); }); } }); }); /// <* class="onclick-scrolltop"> If clicked, scrolls the page to top (in slow, animated way). $(document).on("click", ".onclick-scrolltop", function (event) { if ($.fn.animate) { // animate is not available in slim version if jQuery $("body,html").animate({ scrollTop: 0 }, 500); } else { window.scrollTo(0, 0); } return false; }); }); $$(function sircl_ext_ifinview_processHandler() { /// <* ifinview-load="url"> Loads the given URL and places the result in the element when the element is visible in the view. $("[ifinview-load]").each(function () { if (sircl.isElementInView(this)) { var url = $(this).attr("ifinview-load"); $(this).removeAttr("ifinview-load"); $(this).load(url); } }); /// <* ifinview-click="selector"> Clicks the given element when this element is visible in the view. $("[ifinview-click]").each(function () { if (sircl.isElementInView(this)) { var selector$ = $(this).attr("ifinview-click"); $(this).removeAttr("ifinview-click"); sircl.ext.$select($(this), selector$).each(function () { this.click(); }); } }); }); // Animation event-actions: /////////////////////////// document.addEventListener("DOMContentLoaded", function () { // <* onanimationcancel-click="selector"> On animationcancel, triggers a click event on the elements matching the given selector. $(document).on("animationcancel", "*[onanimationcancel-click]", function (event) { var targetSelector = $(this).attr("onanimationcancel-click"); sircl.ext.$select($(this), targetSelector).each(function () { this.click(); // See: http://goo.gl/lGftqn }); //event.preventDefault(); }); // <* onanimationend-click="selector"> On animationend, triggers a click event on the elements matching the given selector. $(document).on("animationend", "*[onanimationend-click]", function (event) { var targetSelector = $(this).attr("onanimationend-click"); sircl.ext.$select($(this), targetSelector).each(function () { this.click(); // See: http://goo.gl/lGftqn }); //event.preventDefault(); }); // <* onanimationiteration-click="selector"> On animationiteration, triggers a click event on the elements matching the given selector. $(document).on("animationiteration", "*[onanimationiteration-click]", function (event) { var targetSelector = $(this).attr("onanimationiteration-click"); sircl.ext.$select($(this), targetSelector).each(function () { this.click(); // See: http://goo.gl/lGftqn }); //event.preventDefault(); }); // <* onanimationstart-click="selector"> On animationstart, triggers a click event on the elements matching the given selector. $(document).on("animationstart", "*[onanimationstart-click]", function (event) { var targetSelector = $(this).attr("onanimationstart-click"); sircl.ext.$select($(this), targetSelector).each(function () { this.click(); // See: http://goo.gl/lGftqn }); //event.preventDefault(); }); }); document.addEventListener("DOMContentLoaded", function () { // <* onanimationend-remove="selector"> On animationend or animationcancel removes the elements matching the given selector. $(document).on("animationend animationcancel", "[onanimationend-remove]", function (event) { sircl.ext.$select($(this), $(this).attr("onanimationend-remove")).remove(); }); // <* onanimationend-hide="selector"> On animationend or animationcancel hides the elements matching the given selector. $(document).on("animationend animationcancel", "[onanimationend-hide]", function (event) { sircl.ext.visible(sircl.ext.$select($(this), $(this).attr("onanimationend-hide")), false, true); }); // <* onanimationend-show="selector"> On animationend or animationcancel shows the elements matching the given selector. $(document).on("animationend animationcancel", "[onanimationend-show]", function (event) { sircl.ext.visible(sircl.ext.$select($(this), $(this).attr("onanimationend-show")), true, true); }); /// <* onanimationend-removeclass="class [on selector]"> On animationend or animationcancel, removes the class. $(document).on("animationend animationcancel", "[onanimationend-removeclass]", function (event) { sircl.ext.removeClass($(this), $(this).attr("onanimationend-removeclass")); }); /// <* onanimationend-addclass="class [on selector]"> On animationend or animationcancel, adds the class. $(document).on("animationend animationcancel", "[onanimationend-addclass]", function (event) { sircl.ext.addClass($(this), $(this).attr("onanimationend-addclass")); }); // <* onanimationend-scrollintoview="selector"> On animationend or animationcancel scrolls the (first) match of the selector into the view. $(document).on("animationend animationcancel", "[onanimationend-scrollintoview]", function (event) { var $target = sircl.ext.$select($(this), $(this).attr("onanimationend-scrollintoview")); if ($target.length > 0) $target[0].scrollIntoView(); }); }); //#endregion //#region Confirmation dialogs document.addEventListener("DOMContentLoaded", function () { /// Checkboxes can have a change confirm dialog: /// $(document.body).on("change", "INPUT[onchange-confirm][type='checkbox']", function (event) { var confirmMessage = $(this).attr("onchange-confirm"); if (confirmMessage) { if (!sircl.ext.confirm(this, confirmMessage, event)) { $this.prop("checked", !$this.prop("checked")); event.stopPropagation(); event.preventDefault(); } } }); /// Inputs and selects can have a change confirm dialog: /// $(document.body).on("change", "INPUT[onchange-confirm]:not([type='checkbox']):not([type='radio']),SELECT[onchange-confirm]", function (event) { var confirmMessage = $(this).attr("onchange-confirm"); if (confirmMessage) { if (!sircl.ext.confirm(this, confirmMessage, event)) { $(this).val(this._beforeConfirmValue); event.stopPropagation(); event.preventDefault(); } else { this._beforeConfirmValue = $(this).val(); } } }); }); $$(function sircl_ext_onchangeConfirm_processHandler() { // Store initial value of input or select having onchange-confirm, to be able to restore if not confirmed: $(this).find("INPUT[onchange-confirm]:not([type='checkbox']):not([type='radio']),SELECT[onchange-confirm]").each(function () { this._beforeConfirmValue = $(this).val(); }); }); //#endregion //#region Drag & Drop document.addEventListener("DOMContentLoaded", function () { /// Limit file count on file input $(document.body).on("change", "INPUT[type='file'][multiple][maxcount], INPUT[type='file']:not([multiple])", function (event) { var $this = $(this); var maxcount = $this.hasAttr("multiple") ? parseInt($this.attr("maxcount")) : 1; if (this.files.length > maxcount) { var maxcountalert = $this.attr("maxcount-alert"); if (maxcountalert) sircl.ext.alert(this, maxcountalert, event); this.value = ""; event.stopPropagation(); event.preventDefault(); } }); /// Limit file type on file input $(document.body).on("change", "INPUT[type='file'][accept]", function (event) { var $this = $(this); var tokens = $this.attr("accept").split(','); for (var i = 0; i < tokens.length; i++) { if (tokens[i][0] === '.') { tokens[i] = tokens[i].toUpperCase(); } else { tokens[i] = tokens[i] .replaceAll("+", "\\+") .replaceAll(".", "\\.") .replaceAll("/", "\\/") .replaceAll("*", ".+"); } } for (var i = 0; i < this.files.length; i++) { var validFile = false; for (var t = 0; t < tokens.length; t++) { if (tokens[t][0] === '.') { if (this.files[i].name.toUpperCase().lastIndexOf(tokens[t]) === (this.files[i].name.length - tokens[t].length)) { validFile = true; break; } } else if (this.files[i].type.match(tokens[t])) { validFile = true; break; } } if (!validFile) { var acceptalert = $this.attr("accept-alert"); if (acceptalert) sircl.ext.alert(this, acceptalert, event); this.value = ""; event.stopPropagation(); event.preventDefault(); break; } } }); /// Limit file size on file input $(document.body).on("change", "INPUT[type='file'][maxsize]", function (event) { var $this = $(this); var maxsize = ($this.attr("maxsize")).toUpperCase(); if (maxsize.indexOf("KB") > 0) maxsize = parseFloat(maxsize.replace("KB", "").trim()) * 1024; else if (maxsize.indexOf("MB") > 0) maxsize = parseFloat(maxsize.replace("MB", "").trim()) * 1024 * 1024; else maxsize = parseFloat(maxsize); for (var i = 0; i < this.files.length; i++) { if (this.files[i].size > maxsize) { var maxsizealert = $this.attr("maxsize-alert"); if (maxsizealert) sircl.ext.alert(this, maxsizealert, event); this.value = ""; event.stopPropagation(); event.preventDefault(); break; } } }); /// Limit total file size on file input $(document.body).on("change", "INPUT[type='file'][maxtotalsize]", function (event) { var $this = $(this); var maxtotalsize = ($this.attr("maxtotalsize")).toUpperCase(); if (maxtotalsize.indexOf("KB") > 0) maxtotalsize = parseFloat(maxtotalsize.replace("KB", "").trim()) * 1024; else if (maxtotalsize.indexOf("MB") > 0) maxtotalsize = parseFloat(maxtotalsize.replace("MB", "").trim()) * 1024 * 1024; else maxtotalsize = parseFloat(maxtotalsize); var totalsize = 0; for (var i = 0; i < this.files.length; i++) { totalsize += this.files[i].size; if (totalsize > maxtotalsize) { var maxtotalsizealert = $this.attr("maxtotalsize-alert"); if (maxtotalsizealert) sircl.ext.alert(this, maxtotalsizealert, event); this.value = ""; event.stopPropagation(); event.preventDefault(); break; } } }); /// Copies the name of the file $(document).on("change", "INPUT[type='file'][onchange-setname]", function (event) { var $this = $(this); if (this.files.length > 0) { var filename = this.files[0].name; var $target = sircl.ext.$select($this, $this.attr("onchange-setname")); $target.each(function () { if (this.tagName == "INPUT") this.value = filename; else this.innerText = filename; }); } }); /// Copies the base name of the file (name without extension) $(document).on("change", "INPUT[type='file'][onchange-setbasename]", function (event) { var $this = $(this); if (this.files.length > 0) { var filename = this.files[0].name; if (filename.indexOf('.') >= 0) filename = filename.substring(0, filename.lastIndexOf('.')); var $target = sircl.ext.$select($this, $this.attr("onchange-setbasename")); $target.each(function () { this.value = filename; }); } }); sircl.addAttributeAlias(".ondropfile-set", "ondropfile-set", ">INPUT[type=file]"); /// Allow dragging file: /// <* ondropfile-set="form-input">... $(document.body).on("dragover", "[ondropfile-set]", function (event) { if (event.originalEvent.dataTransfer.types.length > 0 && event.originalEvent.dataTransfer.types[0] == "Files") { // Allow by preventing default browser behavior: event.preventDefault(); // If has [ondragover-addclass], add class: var $scope = $(this).closest("[ondragover-addclass]"); if ($scope.length > 0) { sircl.ext.addClass($scope, $scope.attr("ondragover-addclass")); } } }); /// Allow dropping file: /// <* ondropfile-set="form-input">... $(document).on("drop", "[ondropfile-set]", function (event) { // Prevent default browser behavior: event.preventDefault(); // Sert file(s): var $this = $(this); var $file = sircl.ext.$select($this, $(this).attr("ondropfile-set")); if ($file.length > 0) { $file[0].files = event.originalEvent.dataTransfer.files; $($file[0]).trigger("change"); } }); /// Disallow dragging file: /// <* class="ondropfile-ignore">... $(document).on("dragover", ".ondropfile-ignore", function (event) { if (event.originalEvent.dataTransfer.types.length > 0 && event.originalEvent.dataTransfer.types[0] == "Files") { // Override default browser behavior: event.preventDefault(); } }); /// Disallow dropping file: /// <* class="ondropfile-ignore">... $(document).on("drop", ".ondropfile-ignore", function (event) { // Prevent default browser behavior: event.preventDefault(); }); }); document.addEventListener("DOMContentLoaded", function () { $(document).on("dragstart", "[draggable]", function (event) { if ($(this).hasAttr("drop-type")) { var dragTypes = $(this).attr("drop-type").split(" "); for (var i = 0; i < dragTypes.length; i++) { if (dragTypes[i].trim() != "") event.originalEvent.dataTransfer.setData(dragTypes[i].trim(), true); } } event.originalEvent.dataTransfer.setData("__id", sircl.ext.getId(this, true)); event.originalEvent.dataTransfer.setData("any", $(this).attr("drop-value")); }); $(document.body).on("dragover", "[ondrop-accept]", function (event) { var acceptTypes = $(this).attr("ondrop-accept").split(" "); for (var i = 0; i < acceptTypes.length; i++) { for (var j = 0; j < event.originalEvent.dataTransfer.types.length; j++) { if (acceptTypes[i].trim().toLowerCase() == event.originalEvent.dataTransfer.types[j]) { // If a match is found, allow drop: event.preventDefault(); // If has [ondragover-addclass], add class: var $scope = $(this).closest("[ondragover-addclass]"); if ($scope.length > 0) { sircl.ext.addClass($scope, $scope.attr("ondragover-addclass")); } } } } }); $(document).on("dragleave", "[ondragover-addclass]", function (event) { sircl.ext.removeClass($(this), $(this).attr("ondragover-addclass")); }); $(document).on("drop", "[ondragover-addclass]", function (event) { sircl.ext.removeClass($(this), $(this).attr("ondragover-addclass")); }); $(document).on("drop", ".ondrop-move", function (event) { // Prevent default browser behavior: event.preventDefault(); // Perform move: var sourceId = event.originalEvent.dataTransfer.getData("__id"); event.originalEvent.target.closest(".ondrop-move").appendChild(document.getElementById(sourceId)); }); $(document).on("drop", ".ondrop-copy", function (event) { // Prevent default browser behavior: event.preventDefault(); // Perform move: var sourceId = event.originalEvent.dataTransfer.getData("__id"); $(event.originalEvent.target.closest(".ondrop-copy")).append(document.getElementById(sourceId).outerHTML.replace("id=\"" + sourceId + "\"", "")); }); $(document).on("drop", ".ondrop-submit", function (event) { // Verify accept types: if ($(this).hasAttr("ondrop-accept")) { var allowDrop = false; var acceptTypes = $(this).attr("ondrop-accept").split(" "); for (var i = 0; i < acceptTypes.length; i++) { for (var j = 0; j < event.originalEvent.dataTransfer.types.length; j++) { if (acceptTypes[i].trim().toLowerCase() == event.originalEvent.dataTransfer.types[j]) { // If a match is found, allow drop: allowDrop = true; } } } if (!allowDrop) return; } // Perform drop submit: var $this = $(this); var $form = ($this.hasAttr("form")) ? $("#" + $this.attr("form")) : $this.closest("FORM"); if ($form.length > 0) { // Copy drop-value to .drop-value input element: var dropvalue = event.originalEvent.dataTransfer.getData("any"); $form.find("INPUT.drop-value").each(function () { $(this).val(dropvalue); }); // Prevent default browser behavior: event.preventDefault(); // Submit form (add a submit button, then click that button): var btnid = "sircl-autoid-" + new Date().getTime(); var btn = " .spinner"); if ($spinners.length > 0) { var $trigger = $(this); var _spinner_to_restore = $trigger[0].innerHTML; setTimeout(function () { $trigger[0].innerHTML = _spinner_to_restore; }, 250); $spinners[0].outerHTML = sircl.html_spinner; } // Retrieve sharing info: var $target = sircl.ext.$select($(this), $(this).attr("onclick-share")); var title = $target.attr("data-share-title") || $target.attr("title") || (($target.hasAttr("data-share-title")) ? undefined : document.title); var url = $target.attr("data-share-url") || $target.attr("href") || (($target.hasAttr("data-share-url")) ? undefined : window.location.href); var text = $target.attr("data-share-text") || $target.text(); // Share: navigator.share({ title: title, text: text, url: url }).then(function () { }).catch(function () { }); } }); }); //#endregion