DOM equivalent to jQuery `.detach()`? - javascript

I’m trying to remove an input field by clicking an “X button”. After it is removed it will not post its value when the form is submitted. A “+ button” appears that allows the user to add said input again. The input has an onclick event that opens a calendar and after reattaching, the calendar does not open on click anymore. I can’t use jQuery.
adderBtn.onclick = function (e) {
var elem = that.hiddenElems.shift();
that.collectionItemContainer.append(elem);
}
removerBtn.onclick = function (e) {
collectionItemElem.remove();
that.hiddenElems.push(collectionItemElem);
}
The question is how do I remove and reattach DOM nodes without losing the Events.

When you remove an element, as long as you keep a reference to it, you can put it back. So:
var input = /*...code to get the input element*/;
input.parentNode.removeChild(input); // Or on modern browsers: `input.remove();`
later if you want to put it back
someParentElement.appendChild(input);
Unlike jQuery, the DOM doesn't distinguish between "remove" and "detach" — the DOM operation is always the equivalent of "detach," meaning if you add the element back, it still has its handlers:
Live Example:
var input = document.querySelector("input[type=text]");
input.addEventListener("input", function() {
console.log("input event: " + this.value);
});
input.focus();
var parent = input.parentNode;
document.querySelector("input[type=button]").addEventListener("click", function() {
if (input.parentNode) {
// Remove it
parent.removeChild(input);
} else {
// Put it back
parent.appendChild(input);
}
});
<form>
<div>
Type in the input to see events from it
</div>
<label>
Input:
<input type="text">
</label>
<div>
<input type="button" value="Toggle Field">
</div>
</form>
If you remove the element without keeping any reference to it, it is eligible for garbage collection, as are any handlers attached to it (provided nothing else refers to them, and ignoring some historic IE bugs in that regard...).

To detach an element in function form:
function detatch(elem) {
return elem.parentElement.removeChild(elem);
}
This will return the 'detached' element

Related

jQuery alternative for replacing html without breaking Event Listeners

At a certain point of my code, I need to pick up an html string coming from an AJAX request and replace a certain container with it.
I have this but, as stated in several references across the web, it is garbage collected and unbinds the event listeners
this.rightPanel.innerHTML = content;
This however, doesn't happen with jQuery .html() function, which keeps the event listeners working, so doing
$(this.rightPanel).html(content);
works without flaw.
I didn't want to use jQuery for DOM manipulation, even so when browsers support it natively. What's the best alternative I have to reproduce the same behavior as jQuery .html()?
Thank you
This however, doesn't happen with jQuery .html() function...
Yes, it does.
...which keeps the event listeners working...
No, it doesn't. :-)
Like-for-like, they do the same thing to the event listeners:
// DOM
document.getElementById("btn1").addEventListener("click", function() {
console.log("Button 1");
});
// jQuery
$("#btn2").on("click", function() {
console.log("Button 2");
});
// Replace 'em
$("#btnRep").on("click", function() {
var wrap1 = document.getElementById("wrap1");
wrap1.innerHTML = wrap1.innerHTML + " (replaced)";
var wrap2 = $("#wrap2");
wrap2.html(wrap2.html() + " (replaced)");
});
<p>Click Button 1, then Button 2, Then Replace, then Buttons 1 and 2 again -- you'll see *neither* of them has a handler anymore after being replaced.</p>
<div id="wrap1">
<input type="button" id="btn1" value="Button 1">
</div>
<div id="wrap2">
<input type="button" id="btn2" value="Button 2">
</div>
<input type="button" id="btnRep" value="Replace">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
What you've probably seen is that jQuery makes event delegation really, really easy: That's where you actually hook the event on an ancestor element (perhaps even body), but you ask jQuery to only trigger your handler if it passed through an element during bubbling. That way, you can replace the descendant elements, because the event listener isn't on them, it's on the container/ancestor.
You can do that without jQuery as well, it's just a bit more work:
function addDelegated(element, eventName, selector, handler) {
element.addEventListener(eventName, function(e) {
// Start with the target element, and go through its parents
// until we reach the element we hooked the event on (`this`)
var element = e.target;
while (element && element !== this) {
// `matches` test the element against a CSS selector
if (element.matches(selector)) {
// Yes, trigger the handler
return handler.call(element, e);
}
element = element.parentNode;
}
});
}
// Hook the event on wrap1
addDelegated(document.getElementById("wrap1"), "click", "#btn1", function() {
console.log("Button 1");
});
// Replace
document.getElementById("btnRep").addEventListener("click", function() {
var wrap1 = document.getElementById("wrap1");
wrap1.innerHTML = wrap1.innerHTML + " (replaced)";
});
<p>Click Button 1, then Replace, then Button 1 again.</p>
<div id="wrap1">
<input type="button" id="btn1" value="Button 1">
</div>
<input type="button" id="btnRep" value="Replace">

How to apply validation to cloned elements in Jquery?

There is a textbox with label; having validation of isnumeric.
Money: <input type="text" id="dollar" name="dollar" data-require-numeric="true" value="">
//Textbox with id dollar0
At run time, I have created clone of above; by clicking on button named add; and this created another textbox with different id and other attributes same; say.
Money: <input type="text" id="dollar1" name="dollar1" data-require-numeric="true" value="">
//Cloned textbox; cloned by clicking on a button named clone
On both textboxes data-require-numeric is true.
Issue: For default textbox the JQuery validation is getting executed. But for new clone; JQuery is not running.
Following is jquery:
var economy= {
init: function () {
$('input[type="text"][data-require-numeric]').on("change keyup paste", function () {
check isnumeric; if yes then border red
});
}};
$(economy.init);
How to resolve this?
Try this : You need to register click event handler using .on() in following way where registering the click handler for document which will delegate the event to 'input[type="text"][data-require-numeric]'. This way you can handle events for dynamically added elements.
var economy= {
init: function () {
$(document).on("change keyup paste",'input[type="text"][data-require-numeric]',
function () {
check isnumeric; if yes then border red
});
}};
$(economy.init);
to bind change event on dynamic dom elements . use class reference instead of id . And bind the event to its parent like,
$(document).ready(function(){
$(".parent").on("keyup",".dynamicdom",function(e){
value = $(e.target).val()
//then do your validation here. and you can attach multiple events to it
})
})
<div class="parent">
<input type="text" class="dynamicdom" >
<input type="text" class="dynamicdom" >
</div>

JQuery function not firing on enter keypress

I have the following jQuery/js controlling a trello style input field. So when you click on the span element, it switches to an input field. Then after you finish editing the element and take focus away from it(blur), it switches back to the span element with the new text. I also having it submitting to an ajax script to submit the new text to my database.
Now this all works flawlessly and I have no problems with what is described above. The problem came when I tried to make the "switch back to the span element" work on "enter" key press.
I have tested the code with an alert so I know the enter key is being detected but I can not get the "editableTextBlurred" function to fire from within the keypress function. Which means that it will not switch back to the span element on enter press.
function divClicked(div) {
div = "#"+div;
var divHtml = $(div).text();
var editableText = $('<input type="text" id="firstname" name="firstname" placeholder="First Name" onChange="profileUpdate(this)">');
editableText.val(divHtml);
$(div).replaceWith(editableText);
editableText.focus();
console.log(editableText);
// setup the blur event for this new textarea
editableText.blur(editableTextBlurred);
$(":input").keypress(function(event) {
if (event.which == '13') {
event.preventDefault();
editableTextBlurred();
}
});
}
function editableTextBlurred() {
var html = $(this).val();
var viewableText = $("<span id='firtname_text' onClick='divClicked(this.id)'></span>");
viewableText.html(html);
$(this).replaceWith(viewableText);
}
<span id='firtname_text' onClick='divClicked(this.id)'>
".ucfirst($fname)."
</span>
Any insight on what I missing or doing wrong will be greatly appreciated,
Thanks!
I suspect it's successfully calling editableTextBlurred, but then that function isn't working. And the reason it's not working is that it expects this to refer to the input element, but the way you're calling it, this will be the global object (in loose mode) or undefined (in strict mode).
To make the this in editableTextBlurred the same as the this in the keypress handler, use .call:
editableTextBlurred.call(this);
Alternately, just pass the element as an argument:
editableTextBlurred(this);
...and then
function editableTextBlurred(input) {
var html = $(input).val();
var viewableText = $("");
viewableText.html(html);
$(input).replaceWith(viewableText);
}
Separately, there's a problem with that onClick code, it will end up with this.id (the actual text) in the onClick attribute. You probably wanted:
var viewableText = $("<span id='firtname_text' onClick='divClicked(\"'" + input.id + "'\")'></span>");
...though I wouldn't use onClick for this at all; use jQuery.
Rather than adding/removing event handlers, this is the kind of situation that cries out for event delegation:
// Delegated handler for converting to inputs
$("#container").on("click", ".click-to-edit", function(e) {
var $div = $(this),
$input = $("<input type=text>");
$div.toggleClass("click-to-edit editing");
$input.val($div.text());
$div.empty().append($input);
setTimeout(function() { // Some browsers want this delay
$input.focus();
}, 50);
});
// Delegated handler for converting back
$("#container").on("keypress", ".editing input", function(event) {
var $input = $(this), $div;
if (event.which == 13) {
event.preventDefault();
$div = $input.closest(".editing");
$div.toggleClass("click-to-edit editing");
$div.empty().text($input.val());
}
});
<div id="container">
<div class="click-to-edit">Testing 1 2 3</div>
<div class="click-to-edit">Testing 4 5 6</div>
<div class="click-to-edit">Testing 7 8 9</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

JavaScript click has different behavior than manual one

With prototype I'm listening for a click event on several checkboxes. On checkbox click I want to disable all <select> elements. I'm using prototype. So, I have this code:
$$('.silhouette-items input[type="checkbox"]').invoke('observe', 'click', function(event) {
var liItem = this.up('li.item');
if(this.checked) {
alert('checked');
liItem.removeClassName('inactive');
var selectItem = liItem.select('select');
for(i=0;i<selectItem.length;i++) {
selectItem[i].disabled=false;
if (selectItem[i].hasClassName('super-attribute-select')) {
selectItem[i].addClassName('required-entry');
}
}
} else {
alert('unchecked');
liItem.addClassName('inactive');
var selectItem = liItem.select('select');
for(i=0;i<selectItem.length;i++){
selectItem[i].disabled=true;
if (selectItem[i].hasClassName('super-attribute-select')) {
selectItem[i].removeClassName('required-entry');
}
}
}
calculatePrice();
});
When I manually click on the checkbox, everything seems to be fine. All elements are disabled as wanted.
However, I have also this button which on click event it fires one function which fires click event on that checkbox.
In Opera browser it works. In others, not. It's like Opera first (un)check and then executes event. Firefox first fires event, then (un)check element.
I don't know how to fix it.
The HTML:
<ul class="silhouette-items">
<li>
<input type="checkbox" checked="checked" id="include-item-17" class="include-item"/>
<select name="super_attribute[17][147]">(...)</select>
<select name="super_group[17]">(...)</select>
<button type="button" title="button" onclick="addToCart(this, 17)">Add to cart</button>
</li>
<!-- Repeat li few time with another id -->
</ul>
Another JS:
addToCart = function(button, productId) {
inactivateItems(productId);
productAddToCartForm.submit(button);
}
inactivateItems = function(productId) {
$$('.include-item').each(function(element) {
var itemId = element.id.replace(/[a-z-]*/, '');
if (itemId != productId && element.checked) {
simulateClickOnElement(element);
}
if (itemId == productId && !element.checked) {
simulateClickOnElement(element);
}
});
}
simulateClickOnElement = function(linkElement) {
fireEvent(linkElement, 'click');
}
Where fireEvent is a Magento function that triggers an event
Don't bother simulating a onclick if you can get away with not doing so. Having a separate function that can be called from within the event handler and from outside should work in your case.
var handler = function(){
//...
}
var nodeW = $('#node');
handler.call(nodeW);
Of course, this doesn't trigger all onclick handlers there might be but it is simpler so it should work all right. Points to note for when you use .call to call a function:
Whatever you pass as the first parameter is used as the this inside the call. I don't recall exactly what JQuery sets the this too but you should try passing the same thing for consistency.
The other parameters become the actual parameters of the function. In my example I don't pass any since we don't actually use the event object and also since I don't know how to emulate how JQuery creates that object as well.
replacing
fireEvent(linkElement, 'click');
with
linkElement.click();
works in firefox 5 and safari 5.1, so maybe the problem lies in the fireEvent() method.

Proper handling of input change event

It may be a correct behavior of change event, but the below behavior is bit annoying. When the value is updated from the field history (see explanation below), the event is not triggered.
Please see example code below. the result input field is updated with the change in input field 'input1'. The form and submit button is not fully relevant, but needed to submit a form to make the browser keep the history of field values.
To test:
enter any input in the field (say ABC)
Submit the form
enter first character of input from 1 (A)
use the down arrow to select the previous value + Enter
or use the mouse to select the previous value from the history
No input change is detected.
Which event/ how should this code should modify so that an event is generated whenever the input value is changed.
thanks.
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
</head>
<body>
Result:<input type="text" id="result" readonly></input>
<form method="post" action="">
<input type="text" id="input1" />
<button type=submit>Submit</button>
</form>
<script >
$(document).ready(function() {
$('#input1').change(
function(){
$('#result').val($('#input1').val());
});
});
</script>
</body>
</html>
I think this has nothing to do with jQuery.
A change event should be dispatched when the content of a control has changed and the control loses focus. In practice, the implementation of the change event is inconsistent in browsers, e.g. Firefox dispatches a change event when radio buttons are clicked on rather then when they lose focus. Also in IE, selecting a value from a list of previous values then causing a blur event doesn't fire a change event.
Note that for form controls to be successful, they must have a name attribute with a value. A simple test case is:
<form action="#">
<input type="text" name="input1" onchange="alert('changed');">
<input type="submit">
</form>
One solution is to use the blur event instead and compare the control's current value to its defaultValue - if they're different, perform whatever it is you were going to do for the change event. If the value may be changed a number of times, after the first time you need to compare with the last value onblur rather than the defaultValue.
Anyhow, here's a function that can be called onblur to see if a text input has changed. It needs a bit of work if you want to use it with other types of form control, but I don't think that's necessary.
<form action="#">
<input type="text" name="input1" onblur="
var changed = checkChanged(this);
if (changed[0]) {
alert('changed to: ' + changed[1]);
}
">
<input type="submit">
</form>
<script type="text/javascript">
// For text inputs only
var checkChanged = (function() {
var dataStore = [];
return function (el) {
var value = el.value,
oValue;
for (var i=0, iLen=dataStore.length; i<iLen; i+=2) {
// If element is in dataStore, compare current value to
// previous value
if (dataStore[i] == el) {
oValue = dataStore[++i];
// If value has changed...
if (value !== oValue) {
dataStore[i] = value;
return [true, value];
// Otherwise, return false
} else {
return [false, value];
}
}
}
// Otherwise, compare value to defaultValue and
// add it to dataStore
dataStore.push(el, value);
return [(el.defaultValue != value), value];
}
}());
</script>
Try the keyup event:
$(document).ready(function() {
$('#input1').keyup(
function(){
$('#result').val($('#input1').val());
});
});
http://jsfiddle.net/kaptZ/7/
It seems like it's definitely a browser bug. Not much you can do besides implement your own change handler with focus and blur. This example is not very reusable, but it solved the problem and can be used as inspiration for something reusable.
http://jsfiddle.net/kaptZ/9/
var startValue;
var input1 = $('#input1');
input1.focus(function(){
startValue = this.value;
});
input1.blur(function(){
if (this.value != startValue) {
$('#result').val(this.value);
}
});
A dirty alternative is to use autocomplete="off"
It looks like this bug which was supposed to be fixed in November 2009.
In modern browsers you can use the input event and update as you type. It can be bound either to the text input:
$('#input1').bind('input', function(){
$('#result').val($('#input1').val());
});
Or to the form:
$('#input1').closest('form').bind('input', function(){
$('#result').val($('#input1').val());
});

Categories

Resources