Cleaner way to attach event handler function and execute it on ready - javascript

When I have a page with button that modifies the layout (let's say checkbox that shows/hides a box) I end up writing following blocks of code:
$(function(){
var advancedOptionsToggle = function() {
var isChecked = $('#advanced-options-checkbox').checked;
$('#advanced-options').toggle(isChecked);
};
advancedOptionsToggle();
$('#advanced-options-checkbox').change(advancedOptionsToggle);
});
The reason is that my page content is dynamically generated on the server side, instead of rendered by JS on the client side, so the checkbox might already come checked.
It looks to like a common problem, so I believe there must be a pattern that solves it cleaner. Is there a jQuery function that can encapsulate it?

Your solution is certainly serviceable, however your question suggests that you may have several of these. May I suggest creating a common pattern for this kind of usage, rather than resort to IDs?
$(function(){
var toggleOptionCheck = function(el){
var isChecked = $(el).is(':checked');
$($(el).data('related')).toggle(isChecked);
}
$('.options-toggle').each(function(idx, el){
toggleOptionCheck(el);
});
// Separate call for illustration/simplicity.
$('.options-toggle').on('change', function(){toggleOptionCheck(this)});
});
<input type="checkbox" checked name="show_advanced"
class="options-toggle" data-related="#advanced-options" />
<div id="advanced-options" class="hidden-at-start"></div>

You can create a simple jQuery plugin like this to clean up the code a bit:
$.fn.advancedOptionsToggle = function(selector) {
return this.each(function() {
var isChecked = $(this).checked;
$(this).unbind('change').change(function(){
$(selector).toggle(isChecked));
});
});
};
$(function(){
$('#advanced-options-checkbox').advancedOptionsToggle('#advanced-options');
});

Related

Need changes to javascript snippet related to FacetWP

I have a snippet of js provided to me by the developers of FacetWP plugin for Wordpress and I am having difficulty making changes to it to suit my needs.
The snippet is as follows;
<script>
(function($) {
$(document).ready(function() {
$(".select-all").click(function () {
var available = [];
$( '.facetwp-facet-product_categories .facetwp-checkbox' ).each( function (index, item) {
available.push( $(item).attr( 'data-value' ) );
});
FWP.facets['product_categories'] = available;
FWP.is_reset = true; // don't parse facets
FWP.refresh();
});
});
})(jQuery);
</script>
The purpose of the above code is to 'select' all checkbox options for a given facet, in this case the 'product_categories' facet, upon click of the div with the class .select-all
I need to make 2 changes here.
Firstly I need to ONLY select 1 choice for that facet.
Secondly, I need to make additional selections from additional facets.
Can anybody suggest what I might do next? I have very little experience actually so am hoping for a working example. Many thanks.

'onclick' Storing Value Not Working

so I have this <a> tag:
<a href="/book-testdrive" class="addtocart" value="YX57WDL" title="Book Test Drive">
<i class="glyphicon glyphicon-road"></i>
<span> Book Test Drive</span>
</a>
As you can see it is given a value of value="YX57WDL" now what I would like to do is capture that value when the a tag is clicked and placed into a variable.
There are many <a> tags on my page with many different values that are created dynamically. If a user presses another a tag I'd like it storing in the same variable but replace the value with that of the unique <a> tag value.
Also the variable needs to be stored site wide, I guess the solution to this would be Web Storage API.
So far I've tried using this Javascript:
var links = document.querySelectorAll('.addtocart ');
links.onclick = function(){
localstorage['storedValue'] = this.value ;
}
However when I console.log() the links variable it contains nothing.
Any idea why this might be happening?
Thanks
The problem is that document.querySelectorAll returns a (non-live) node list. This means that it is basically an array, so you could do a loop for each one instead:
for (var i = 0; i < links.length; i++) {
links[i].onclick = function(){
localStorage['storedValue'] = this.value ;
}
}
Also note that I changed localstorage to localStorage because it's case sensitive.
You will need to wait until the DOM is loaded, or else the call to document.querySelectorAll() will not find anything if the element you are looking for has not been added to the DOM yet.
I see you added jquery as a tag, so I assume you are using jquery. If that is the case, you can wrap your code with the jquery function to wait for the DOM to be ready, like this:
$(document).ready(function() {
var links = document.querySelectorAll('.addtocart ');
links.onclick = function(){
localStorage['storedValue'] = this.value ;
}
});
Also if you are using jquery, you could be using its on function to make this a lot simpler.
$(document).ready(function() {
$('.addtocart').on('click', function() {
localStorage['storedValue'] = this.value;
}
});
If you are not using jquery, see this question about how to wait for the DOM to load without the jquery $(document).ready() function.
Try ".getAttribute('value')" instead of ".value":
var links = document.querySelector('a.addtocart');
links.onclick = function(){
localStorage['storedValue'] = links.getAttribute('value');
}
Since you are using jQuery, you can do something like this:
$("body").on("click", ".addtocart", function(e) {
localstorage['storedValue'] = $(this).val();
});
You surely wonder why did your attempt fail. In fact, you were not too far from the solution, but you probably ran your script before your links were created. With the .on() function of jQuery you have a listener to the current and future elements matching the selector (which is ".addtocart").

Avoid giant cascading in jQuery?

I feel like this is something that is solved by "deferreds" or "promises" that I've heard about in jQuery, but looking searching for related articles on that doesn't exactly show me what I'm looking for.
I want to be able to do a simple jquery function call (like animate() or slideUp()) then call another simple function when it is completed. Of course I know about slideUp(400, function(){ //onComplete... }); but if you have a large cascade of animations, that can get pretty hairy pretty quickly.
Check out the following jsfiddle:
http://jsfiddle.net/ue3daeab/
When you click the first button, you see the visual effect I want to acheive. However, I'm accomplishing it with "cascade hell," and the relevant code being:
$("#clickme").click(function(){
//Cascade hell
$("#my1").slideUp(400, function(){
$("#my2").slideUp(400, function(){
$("#my3").slideUp(400, function(){
$("#my4").slideUp(400, function(){
$("#my5").slideUp(400, function(){
$("#my6").slideUp(400, function(){
$("#my7").slideUp(400, function(){
$("#my8").slideUp(400, function(){
$("#my9").slideUp(400, function(){
$("#my10").slideUp(400);
});
});
});
});
});
});
});
});
});
});
When you click button 2, all the divs collapse at once, which isn't the effect I want. I feel like I should be able to do something like this, but obviously it doesn't work. The relevant code for the 2nd button is:
$.when($("#my1").slideUp())
.done($("#my2").slideUp())
.done($("#my3").slideUp())
.done($("#my4").slideUp())
.done($("#my5").slideUp())
.done($("#my6").slideUp())
.done($("#my7").slideUp())
.done($("#my8").slideUp())
.done($("#my9").slideUp())
.done($("#my10").slideUp());
Any advice? Thanks.
Why not use a simple array of ids to collapse, and then collapse them one item at a time?
$("#clickme").click(function(){
var toCollapse = ["#my1", "#my2", ...];
(function collapse(){
var id = toCollapse.shift();
if (!id) return;
$(id).slideUp(400, collapse);
})();
});
I edited your jsfiddle with this example too: http://jsfiddle.net/ue3daeab/2/
I would do something like this:
UNTESTED
$.each($('.item', '#container'), function(index, value) {
$(this).delay(50*index).slideUp(400);
});
This way everything doesn't try to happen all at once.

Updating written text to view while typing

We all know StackOverFlow's system, which basically enables you to see what your written text look like while sending a question.
I'm looking to create this as well on my website, and I would like something to start with.
I obviously don't expect you to write that code for me, but to explain a bit what do I need for that and how would that work.
Edit: Using vanilla js instead of jquery
http://jsfiddle.net/wmjnaj6n/4/
HTML
<input type='text' id='input'>
<div id='update'></div>
Javascript
var element = document.getElementById('input');
var target = document.getElementById('update');
element.addEventListener('keyup', function() {
target.innerHTML = this.value;
});
For completeness, the jquery way would be:
$('#input').keyup(function() {
//do stuff here
$('#update').text( $(this).val() );
});

Clearing <input type='file' /> using jQuery

Is it possible to clear an <input type='file' /> control value with jQuery? I've tried the following:
$('#control').attr({ value: '' });
But it's not working.
Easy: you wrap a <form> around the element, call reset on the form, then remove the form using .unwrap(). Unlike the .clone() solutions otherwise in this thread, you end up with the same element at the end (including custom properties that were set on it).
Tested and working in Opera, Firefox, Safari, Chrome and IE6+. Also works on other types of form elements, with the exception of type="hidden".
window.reset = function(e) {
e.wrap('<form>').closest('form').get(0).reset();
e.unwrap();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form>
<input id="file" type="file">
<br>
<input id="text" type="text" value="Original">
</form>
<button onclick="reset($('#file'))">Reset file</button>
<button onclick="reset($('#text'))">Reset text</button>
JSFiddle
As Timo notes below, if you have the buttons to trigger the reset of the field inside of the <form>, you must call .preventDefault() on the event to prevent the <button> from triggering a submit.
EDIT
Does not work in IE 11 due to an unfixed bug. The text (file name) is cleared on the input, but its File list remains populated.
Quick answer: replace it.
In the code below I use the replaceWith jQuery method to replace the control with a clone of itself. In the event you have any handlers bound to events on this control, we'll want to preserve those as well. To do this we pass in true as the first parameter of the clone method.
<input type="file" id="control"/>
<button id="clear">Clear</button>
var control = $("#control");
$("#clear").on("click", function () {
control.replaceWith( control = control.clone( true ) );
});
Fiddle: http://jsfiddle.net/jonathansampson/dAQVM/
If cloning, while preserving event handlers, presents any issues you could consider using event delegation to handle clicks on this control from a parent element:
$("form").on("focus", "#control", doStuff);
This prevents the need for any handlers to be cloned along with the element when the control is being refreshed.
Jquery is supposed to take care of the cross-browser/older browser issues for you.
This works on modern browsers that I tested: Chromium v25, Firefox v20, Opera v12.14
Using jquery 1.9.1
HTML
<input id="fileopen" type="file" value="" />
<button id="clear">Clear</button>
Jquery
$("#clear").click(function () {
$("#fileopen").val("");
});
On jsfiddle
The following javascript solution also worked for me on the browsers mention above.
document.getElementById("clear").addEventListener("click", function () {
document.getElementById("fileopen").value = "";
}, false);
On jsfiddle
I have no way to test with IE, but theoretically this should work. If IE is different enough that the Javascript version does not work because MS have done it in a different way, the jquery method should in my opinion deal with it for you, else it would be worth pointing it out to the jquery team along with the method that IE requires. (I see people saying "this won't work on IE", but no vanilla javascript to show how it does work on IE (supposedly a "security feature"?), perhaps report it as a bug to MS too (if they would count it as such), so that it gets fixed in any newer release)
Like mentioned in another answer, a post on the jquery forum
if ($.browser.msie) {
$('#file').replaceWith($('#file').clone());
} else {
$('#file').val('');
}
But jquery have now removed support for browser testing, jquery.browser.
This javascript solution also worked for me, it is the vanilla equivalent of the jquery.replaceWith method.
document.getElementById("clear").addEventListener("click", function () {
var fileopen = document.getElementById("fileopen"),
clone = fileopen.cloneNode(true);
fileopen.parentNode.replaceChild(clone, fileopen);
}, false);
On jsfiddle
The important thing to note is that the cloneNode method does not preserve associated event handlers.
See this example.
document.getElementById("fileopen").addEventListener("change", function () {
alert("change");
}, false);
document.getElementById("clear").addEventListener("click", function () {
var fileopen = document.getElementById("fileopen"),
clone = fileopen.cloneNode(true);
fileopen.parentNode.replaceChild(clone, fileopen);
}, false);
On jsfiddle
But jquery.clone offers this [*1]
$("#fileopen").change(function () {
alert("change");
});
$("#clear").click(function () {
var fileopen = $("#fileopen"),
clone = fileopen.clone(true);
fileopen.replaceWith(clone);
});
On jsfiddle
[*1] jquery is able to do this if the events were added by jquery's methods as it keeps a copy in jquery.data, it does not work otherwise, so it's a bit of a cheat/work-around and means things are not compatible between different methods or libraries.
document.getElementById("fileopen").addEventListener("change", function () {
alert("change");
}, false);
$("#clear").click(function () {
var fileopen = $("#fileopen"),
clone = fileopen.clone(true);
fileopen.replaceWith(clone);
});
On jsfiddle
You can not get the attached event handler direct from the element itself.
Here is the general principle in vanilla javascript, this is how jquery an all other libraries do it (roughly).
(function () {
var listeners = [];
function getListeners(node) {
var length = listeners.length,
i = 0,
result = [],
listener;
while (i < length) {
listener = listeners[i];
if (listener.node === node) {
result.push(listener);
}
i += 1;
}
return result;
}
function addEventListener(node, type, handler) {
listeners.push({
"node": node,
"type": type,
"handler": handler
});
node.addEventListener(type, handler, false);
}
function cloneNode(node, deep, withEvents) {
var clone = node.cloneNode(deep),
attached,
length,
evt,
i = 0;
if (withEvents) {
attached = getListeners(node);
if (attached) {
length = attached.length;
while (i < length) {
evt = attached[i];
addEventListener(clone, evt.type, evt.handler);
i += 1;
}
}
}
return clone;
}
addEventListener(document.getElementById("fileopen"), "change", function () {
alert("change");
});
addEventListener(document.getElementById("clear"), "click", function () {
var fileopen = document.getElementById("fileopen"),
clone = cloneNode(fileopen, true, true);
fileopen.parentNode.replaceChild(clone, fileopen);
});
}());
On jsfiddle
Of course jquery and other libraries have all the other support methods required for maintaining such a list, this is just a demonstration.
For obvious security reasons you can't set the value of a file input, even to an empty string.
All you have to do is reset the form where the field or if you only want to reset the file input of a form containing other fields, use this:
function reset_field (e) {
e.wrap('<form>').parent('form').trigger('reset');
e.unwrap();
}​
Here is an exemple: http://jsfiddle.net/v2SZJ/1/
This works for me.
$("#file").replaceWith($("#file").clone());
http://forum.jquery.com/topic/how-to-clear-a-file-input-in-ie
Hope it helps.
In IE8 they made the File Upload field read-only for security. See the IE team blog post:
Historically, the HTML File Upload Control () has been the source of a significant number of information disclosure vulnerabilities. To resolve these issues, two changes were made to the behavior of the control.
To block attacks that rely on “stealing” keystrokes to surreptitiously trick the user into typing a local file path into the control, the File Path edit box is now read-only. The user must explicitly select a file for upload using the File Browse dialog.
Additionally, the “Include local directory path when uploading files” URLAction has been set to "Disable" for the Internet Zone. This change prevents leakage of potentially sensitive local file-system information to the Internet. For instance, rather than submitting the full path C:\users\ericlaw\documents\secret\image.png, Internet Explorer 8 will now submit only the filename image.png.
$("#control").val('') is all you need! Tested on Chrome using JQuery 1.11
Other users have tested in Firefox as well.
I got stuck with all the options here. Here's a hack that I made which worked:
<form>
<input type="file">
<button type="reset" id="file_reset" style="display:none">
</form>
and you can trigger the reset using jQuery with a code similar to this:
$('#file_reset').trigger('click');
(jsfiddle: http://jsfiddle.net/eCbd6/)
I ended up with this:
if($.browser.msie || $.browser.webkit){
// doesn't work with opera and FF
$(this).after($(this).clone(true)).remove();
}else{
this.setAttribute('type', 'text');
this.setAttribute('type', 'file');
}
may not be the most elegant solution, but it work as far as I can tell.
I have used https://github.com/malsup/form/blob/master/jquery.form.js, which has a function called clearInputs(), which is crossbrowser, well tested, easy to use and handles also IE issue and hidden fields clearing if needed. Maybe a little long solution to only clear file input, but if you are dealing with crossbrowser file uploads, then this solution is recommended.
The usage is easy:
// Clear all file fields:
$("input:file").clearInputs();
// Clear also hidden fields:
$("input:file").clearInputs(true);
// Clear specific fields:
$("#myfilefield1,#myfilefield2").clearInputs();
/**
* Clears the selected form elements.
*/
$.fn.clearFields = $.fn.clearInputs = function(includeHidden) {
var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list
return this.each(function() {
var t = this.type, tag = this.tagName.toLowerCase();
if (re.test(t) || tag == 'textarea') {
this.value = '';
}
else if (t == 'checkbox' || t == 'radio') {
this.checked = false;
}
else if (tag == 'select') {
this.selectedIndex = -1;
}
else if (t == "file") {
if (/MSIE/.test(navigator.userAgent)) {
$(this).replaceWith($(this).clone(true));
} else {
$(this).val('');
}
}
else if (includeHidden) {
// includeHidden can be the value true, or it can be a selector string
// indicating a special test; for example:
// $('#myForm').clearForm('.special:hidden')
// the above would clean hidden inputs that have the class of 'special'
if ( (includeHidden === true && /hidden/.test(t)) ||
(typeof includeHidden == 'string' && $(this).is(includeHidden)) )
this.value = '';
}
});
};
The value of file inputs is read only (for security reasons). You can't blank it programatically (other than by calling the reset() method of the form, which has a broader scope than just that field).
I was able to get mine working with the following code:
var input = $("#control");
input.replaceWith(input.val('').clone(true));
I have been looking for simple and clean way to clear HTML file input, the above answers are great, but none of them really answers what i'm looking for, until i came across on the web with simple an elegant way to do it :
var $input = $("#control");
$input.replaceWith($input.val('').clone(true));
all the credit go's to Chris Coyier.
// Referneces
var control = $("#control"),
clearBn = $("#clear");
// Setup the clear functionality
clearBn.on("click", function(){
control.replaceWith( control.val('').clone( true ) );
});
// Some bound handlers to preserve when cloning
control.on({
change: function(){ console.log( "Changed" ) },
focus: function(){ console.log( "Focus" ) }
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="file" id="control">
<br><br>
Clear
The .clone() thing does not work in Opera (and possibly others). It keeps the content.
The closest method here for me was Jonathan's earlier, however ensuring that the field preserved its name, classes, etc made for messy code in my case.
Something like this might work well (thanks to Quentin too):
function clearInput($source) {
var $form = $('<form>')
var $targ = $source.clone().appendTo($form)
$form[0].reset()
$source.replaceWith($targ)
}
I have managed to get this to work using the following...
function resetFileElement(ele)
{
ele.val('');
ele.wrap('<form>').parent('form').trigger('reset');
ele.unwrap();
ele.prop('files')[0] = null;
ele.replaceWith(ele.clone());
}
This has been tested in IE10, FF, Chrome & Opera.
There are two caveats...
Still doesn't work properly in FF, if you refresh the page, the file element gets re-populated with the selected file. Where it is getting this info from is beyond me. What else related to a file input element could I possible try to clear?
Remember to use delegation on any events you had attached to the file input element, so they still work when the clone is made.
What I don't understand is who on earth thought not allowing you to clear an input field from an invalid unacceptable file selection was a good idea?
OK, don't let me dynamically set it with a value so I can't leach files from a user's OS, but let me clear an invalid selection without resetting an entire form.
It's not like 'accept' does anything other than a filter anyhow and in IE10, it doesn't even understand MS Word mime types, it's a joke!
On my Firefox 40.0.3 only work with this
$('input[type=file]').val('');
$('input[type=file]').replaceWith($('input[type=file]').clone(true));
its works for me in every browser.
var input = $(this);
var next = this.nextSibling;
var parent = input.parent();
var form = $("<form></form>");
form.append(input);
form[0].reset();
if (next) {
$(next).before(input);
} else {
parent.append(input);
}
I tried with the most of the techniques the users mentioned, but none of they worked in all browsers. i.e: clone() doesn't work in FF for file inputs.
I ended up copying manually the file input, and then replacing the original with the copied one. It works in all browsers.
<input type="file" id="fileID" class="aClass" name="aName"/>
var $fileInput=$("#fileID");
var $fileCopy=$("<input type='file' class='"+$fileInput.attr("class")+" id='fileID' name='"+$fileInput.attr("name")+"'/>");
$fileInput.replaceWith($fileCopy);
$("input[type=file]").wrap("<div id='fileWrapper'/>");
$("#fileWrapper").append("<div id='duplicateFile' style='display:none'>"+$("#fileWrapper").html()+"</div>");
$("#fileWrapper").html($("#duplicateFile").html());
This works with Chrome, FF, and Safari
$("#control").val("")
May not work with IE or Opera
Make it asynchronous, and reset it after the button's desired actions have been done.
<!-- Html Markup --->
<input id="btn" type="file" value="Button" onchange="function()" />
<script>
//Function
function function(e) {
//input your coding here
//Reset
var controlInput = $("#btn");
controlInput.replaceWith(controlInput = controlInput.val('').clone(true));
}
</script>
function clear() {
var input = document.createElement("input");
input.setAttribute('type', 'file');
input.setAttribute('value', '');
input.setAttribute('id', 'email_attach');
$('#email_attach').replaceWith( input.cloneNode() );
}
it does not work for me:
$('#Attachment').replaceWith($(this).clone());
or
$('#Attachment').replaceWith($('#Attachment').clone());
so in asp mvc I use razor features for replacing file input.
at first create a variable for input string with Id and Name and then use it for showing in page and replacing on reset button click:
#{
var attachmentInput = Html.TextBoxFor(c => c.Attachment, new { type = "file" });
}
#attachmentInput
<button type="button" onclick="$('##(Html.IdFor(p => p.Attachment))').replaceWith('#(attachmentInput)');">--</button>
An easy way is changing the input type and change it back again.
Something like this:
var input = $('#attachments');
input.prop('type', 'text');
input.prop('type', 'file')
You can replace it with its clone like so
var clone = $('#control').clone();
$('#control').replacewith(clone);
But this clones with its value too so you had better like so
var emtyValue = $('#control').val('');
var clone = emptyValue.clone();
$('#control').replacewith(clone);
It's easy lol (works in all browsers [except opera]):
$('input[type=file]').each(function(){
$(this).after($(this).clone(true)).remove();
});
JS Fiddle: http://jsfiddle.net/cw84x/1/
What?
In your validation function, just put
document.onlyform.upload.value="";
Assuming upload is the name:
<input type="file" name="upload" id="csv_doc"/>
I'm using JSP, not sure if that makes a difference...
Works for me, and I think it's way easier.

Categories