replacing focused span by input with focus - javascript

This question gives more details about context and motivation. Notice that I am on Linux and cares only about recent Firefox (at least 38) & Chrome.
Basically, I want to edit some AST interactively with a web interface.
In the MELT monitor on github commit 7b869102332bd29309 I would like to have a focusable <span> (which has tabindex='0' so can get focus) which, when I press the spacebar, is replaced by some <input type='text'/> which has already the focus...
I am not using contenteditable anymore, see this, because it looks that contenteditable is really messy (and don't work as well as I want)!
I've made a jsfiddle containing a simple example with:
<div id='mydiv_id'>
*before* <span id='myspan_id' tabindex='0'>in span</span> !after!
</div>
and the JQuery 2 code:
var $mydiv = null;
var $myspan = null;
$(document).ready(function() {
$myspan = $('#myspan_id');
$mydiv = $('#mydiv_id');
console.log (" mydiv=", $mydiv, " myspan=", $myspan);
$myspan.on("keypress", function (ev) {
console.log ("myspan keypress ev=", ev);
if (ev.keyCode === 32 || ev.key === ' ') {
console.log ("myspan got space ev=", ev);
var myinput =
$("<input type='text' id='myinput_id' width='16' class='myinp_cl'/>");
$myspan.replaceWith(myinput);
myinput.focus();
console.log ("myspan replaced with myinput=", myinput);
}
});
console.log (" mydiv=", $mydiv, " myspan=", $myspan);
});
but it does not work as expected.
Or perhaps a focused <span> element cannot be replaced (on space keypress) with a focused <input> element?
(in the MELT monitor, I'm using jquery 2.1.4 embedded inside)
addenda
the updated jsfiddle works (sorry for my mistake, it needs jquery 2.1.4, with which it is working -and I regret having asked the question here), and since the Javascript of the MELT monitor is AJAX generated, I am not seeing every error in the console (see this question).
NB: In commit df3bdf3984bc202f I now have a case when, after programmatically moving the focus to a newly created <input>, $(':focus') is an empty object, and document.activeElement is the <body> itself....
I am now tempted to delete this question, it is probably useless...

As i saw your fiddle, what i noticed:
You are using .on() method.
.on() is not introduced in jQuery version 1.6.x but 1.7+.
So you can change to this:
$myspan.keypress(function (ev) {
Updated fiddle.
cares only about recent Firefox (at least 38) & Chrome.
So, best to upgrade the jQuery version to latest one as possible and take the version 2.x tree.

You can bind focus event on the span like this:
var $mydiv = null;
var $myspan = null;
$(document).ready(function() {
$myspan = $('#myspan_id');
$mydiv = $('#mydiv_id');
$myspan.on('focus', function() { var myinput =
$("<input type='text' id='myinput_id' width='16' class='myinp_cl'/>");
$myspan.replaceWith(myinput);
myinput.focus();
});
});
Demo:
http://jsfiddle.net/wLf0e3cs/

Related

How were Chrome and other browsers able to initialize this JavaScript variable?

I'm working on a shopping cart that asks the user to check the box indicating they agree to the terms of service before they can "review order" and finally make the purchase.
I have to accomplish this with JavaScript by getting the element containing the "review order" and "continue shopping" buttons and changing the inner HTML to be what I need. I have to do it this way because the cart I am using does not give me full control over these elements in the cart source code.
Here is the code I originally came up with, which worked on Chrome, Edge, and other browsers, but not IE.
var x = document.getElementById('CHECKOUT_LINKS');
x.innerHTML = '<div class="checkoutLinksBottom"><input id="tosBox" type="checkbox" name="tosBox">I agree to the Terms of Service<br>Continue ShoppingReview Order</div>';
document.addEventListener('DOMContentLoaded', function() {
document.querySelector('#tosBox').addEventListener('change', changeHandler);
});
var checkbox = document.getElementById("tosBox");
checkbox.checked = true;
checkbox.checked = false;
function changeHandler() {
if (!tosBox.checked)
alert("You must agree to the Terms of Service");
}
function clicker() {
if (!tosBox.checked)
alert("You must agree to the Terms of Service");
else { // Go to review order page
}
}
As you can see the CHECKOUT_LINKS element's inner HTML is changed to what I need on the fly as the page loads. The primary point is to add the id="tosBox" element, then capture the click on id="reviewOrderButton" element and filter it though the simple JS functions changeHandler() and clicker().
In IE developer tools, the console reports 'tosBox' is undefined when I click on id="reviewOrderButton" element. This makes sense when looking at var checkbox = document.getElementById("tosBox"); the variable created is called checkbox, but the variable I try to use later is called tosBox. I simply changed checkbox to tosBox and then everything worked on IE as well.
What's shocking to me is that the original code worked on Chrome and Edge. How did it work? Should I expect it to work and IE is faulting?

Select and Copy input text onclick?

I've actually seen a few questions about this, most of them are from at least 5 or 6 years ago.
I want to have an input box:
<input id="copy-text" type="text" value="Click this text!">
Here's the JavaScript I've been trying to work with:
document.getElementById("copy-text").onclick = function() {
this.select();
execCommand('copy');
alert('This is a test...');
}
I know my code doesn't work. If I remove execCommand('copy'); then the alert() pops up, but it seems to be hitting an error at that line. I've tried making it this.execCommand('copy'); as well, not really sure what to do here.
Fiddle: https://jsfiddle.net/6v24k4sk/
The idea is that I want the user to click the input box, it will select all the text, and then copy it to the clipboard.
Any ideas?
You should put a document. in front of the execCommand.
document.getElementById("copy-text").onclick = function() {
this.select();
document.execCommand('copy');
alert('This is a test...');
}
Here you can find a working example:
https://jsfiddle.net/9q3c1k20/
edit:
The function also returns whether this functionality is supported in the browser. I think you should check the value, because execCommand still has no final specification and is therefore not guaranteed to work in all browsers.
Use this function with your copy_btn (without onclick function).
function function_name() {
var c = document.getElementById("copy");
c.select();
document.execCommand('copy');
}

How can I make this function watch for changes?

https://jsfiddle.net/1vm0259x/ I want to have it so when the contents of #b changes it immediately makes the div display. Is that possible? I'm having to get a little hacky because of the limitations of a CMS plugin. I don't know jQuery very well.
Markup
<div id="a">
<span id="b">0 items</span>
</div>
jQuery
$(document).ready(function(){
if($('#b:contains("0 items")')) {
$('#a').css("display", "none");
}
if($('#b:not(:contains("0 items"))')) {
$('#a').css("display", "block");
}
});
The best way to monitor for text changes like this are to find a way to hook into some related and existing event in the browser and then see if the text has changed to what you want.
In the newer browsers (such as IE11+, recent versions of Chrome, Firefox and Safari), you can use a DOM MutationObserver to directly watch to see if the text nodes change.
The mutation callback is called anytime the children of the specified element are changed (children include the text nodes).
Here's some runnable code that watches for a text change in a div in this code snippet:
document.getElementById("go").addEventListener("click", function(e) {
var t = document.getElementById("test");
t.innerHTML = parseInt(t.innerHTML, 10) + 1;
});
var m = new MutationObserver(function(mRecords, obj) {
log("Current text value: " + document.getElementById("test").innerHTML);
}).observe(document.getElementById("test"), {childList: true, characterData: true, subtree: true});
function log(x) {
var d = document.createElement("div");
d.innerHTML = x;
document.body.appendChild(d);
}
<button id="go">Click Me to Change the Text</button><br><br>
<div id="test">1</div><br><br>
If you need support for older version of IE, then the next best thing would be to figure out what existing events in the browser precede the text change and monitor those events. When one of those events occurs, you can then check the text. For example, if the action that triggers the text change always comes after an Ajax call, you can monitor/hook Ajax calls on a system wide basis and then check your text after each Ajax call completes. Since this only ever does anything when other things are already happening in the web page, it's very efficient. Or, if the text only changes after a particular button is clicked or some text field is changed, you can monitor those DOM elements with event listeners.
To suggest how to do that more specifically, we'd need to see the details of your actual circumstance and would have to understand what events in the page lead to the changing text. Such short duration timers can also negatively affect the performance of things like animations running in your page or in other tabs.
It is NOT recommended to use a short duration timer to poll the DOM because this kills mobile battery life and, in fact, mobile browsers will attempt to delay or slow down any long running interval timers you use in order to try to preserve battery life.
On top of MutationObserver, eloquently put by #jfriend00, there is an older API available at our disposal as well by the name DOMSubtreeModified. Combine that with onpropertychange that of Internet Explorer and I believe you get a nice backward compatible change event. Take a look at the snippet below, not thoroughly tested though:
Snippet:
var myDIV=document.getElementById('a');
var mySpan=document.getElementById('b');
var myButton=document.getElementById('button');
var myResult=document.getElementById('result');
if(window.addEventListener){
myButton.addEventListener('click',onButtonClicked,false);
mySpan.addEventListener('DOMSubtreeModified',onSpanModified,false);
}else{
myButton.attachEvent('onclick',onButtonClicked);
mySpan.attachEvent('onpropertychange',onSpanModified);
}
function onButtonClicked(){
//mySpan.innerText=Math.random();
mySpan.innerHTML=Math.random();
}
function onSpanModified(){
myResult.innerHTML=mySpan.innerHTML;
}
<div id="a">
<span id="b">0</span>
</div>
<input id="button" type="button" value="Click Me" />
<span id="result"></span>
Hope this helps in some way though. Apologies if this was not what you were looking for and if I misunderstood your problem completely.
You can't really "watch" something in JavaScript since it's an event-driven language (there are a few exceptions to this rule when it comes to object properties, but it is not something that is commonly useful).
Instead you should set an interval that updates regularly. I used 50 millisecond intervals, you can choose to use whatever interval you like:
function update() {
if($('#b:contains("0 items")').length) {
$('#a').css("display", "none");
}
if($('#b:not(:contains("0 items"))').length) {
$('#a').css("display", "block");
}
}
$(document).ready(function () {
setInterval(update, 50);
});

insertBefore and IE7

First apologies, the pages I working on a behind passwords, I hope this is enough but if you'd like more code, just ask!
I've got a webpage that list events, each event is a complex set of elements all wrapped in an relative positioned div, an eventRow.
When adding a new event, I find where it should be placed and use:
document.getElementById('eventRows').insertBefore(div, document.getElementById('eventRow' + id));
div is the new event row, id is the id of the event I want to insert before!
This works perfectly in Chrome, Safari, Firefox and IE8, but in IE7 it goes wrong - the new event row seems to be placed correctly, but the rows that surround it aren't correctly moved out of the way, leaving a mess of overlapping text.
After a while I found this can be fixed, after the insert, using the code:
$('eventRows').innerHTML = $('eventRows').innerHTML;
So I've almost solved it, but I'm not very happy, any thoughts on the following questions:
Should I just do this as it seems to work?
Should I only do it if the browser is IE7?
Should I find a better fix?
Many thanks
Ben.
You can try forcing a redraw. There are a few ways that I can think of:
The one you describe
node.className = node.className is reported to work although I've never tried it.
Append a text node
Add a class and then remove it.
Make other style changes and then remove them (padding, border, margin)
Append a text node:
function redraw(node) {
var doc = node.ownerDocument;
var text = doc.createTextNode(' ');
node.appendChild(text);
setTimeout(function() {
node.removeChild(text);
},0);
}
Add/Remove a class
function redraw(node) {
node.className += ' redraw';
setTimeout(function() {
node.className = node.className.replace(/\sredraw$/, '');
}, 0);
}
This code is untested since I don't have IE7
These methods also exist in the various libraries out there. For Ext it's Element.repaint(). There is a force_redraw plugin for jquery: http://plugins.jquery.com/content/forceredraw-102. I'm sure there are others, I just don't know about them.
After much playing I've plumped for the following:
function redrawElement(e) {
if (/MSIE (\d+\.\d+);/.test(navigator.userAgent) && new Number(RegExp.$1) <= 7) {
e.innerHTML = e.innerHTML
}
}
Many of the other ideas nearly worked, but not quite, the padding idea worked once, but caused a nasty jump with the timeout, this does't seem to clause any issues with performance - so why not! :-)
Thanks for your help!!

unobtrusive "default" text in input WITHOUT jQuery

i'm trying to write unobtrusive default/placeholder text in input (actually, relatively placed label over input, which hides on onFocus, and stays hidden if input isn't empty on onBlur), but I don't want to use jQuery, because this is the only javascript used on page - therefore using jQuery seems a bit over the top.
Please, how can I do this without jQuery?
Thank you.
EDIT: I know the idea (getElementByID), but I'm more looking into how to add it to document - preferably something you have used before. Thank you.
EDIT: Thank you all, I finally went with jQuery, seeing answers :] (my example is here: http://jsbin.com/ehega/3 - it's concept, I'll probably add more eye candy. As an answer I Robert Koritnik - because of valid points... and styling ;])
you will need to manually attach the onfocus and onblur events, having got a handle on the input with getElementById.
here is an example: http://www.aspsnippets.com/Articles/Watermark-TextBox-using-JavaScript.aspx
I suggest you use jQuery
jQuery is nothing more than a cross-browser library that makes it easier for developers to achieve something and not worry about browser particularities. And when you load it once (it's rather small) it's cached so I wouldn't worry because it will save you lots of development/testing time later.
No? Then do it manually but make it more reusable
But if you do decide to do something manually you can always use regular Javascript and manipulate DOM as you wish. You best friends in this case would of course be (as Andrew pointed out):
getElementById() and
getElementsByTagName()
functions, but since you'll be manipulating DOM and styles, make sure you test your code against all common browsers. If you use custom attributes on INPUT elements it's good to use the second function, so you'll attach additional functionality to all inputs at once and only to those that define that particular custom attribute like:
<input type=text id="inputX" name="inputX" placeholder="Enter something">
Your script would then get all inputs and you'd check for the custom attribute existance and attach events to those elements that do define that attribute. This way you won't depend on IDs and make your code universal so you can reuse it app wide. Or even on other projects.
Just a sidenote: Andrew's example works somehow differently than what you said would like to do (using labels), but I suggest you use the same approach, because you'll be running scripts anyway. For the sake of unobtrusiveness make sure that you set default content using Javascript so default values and styles on textboxes won't be set for those users that are not running Javascript.
You can use jQuery and still be unobtrusive and use the ability of HTML5 Browsers, make your input like this:
<input type="whatever" placeholder="Your Default Text"/>
I user Modernizr to check the html5 capabilities of the browser and if the browser doesn't understand the placeholder attribute than I use this little javascript to emulate this function.
if (!Modernizr.input.placeholder) {
$('input').each(function(){
var obj = $(this);
var placeholder = obj.attr('placeholder');
if (placeholder) {
obj.val(placeholder);
obj.focus(function(){
var obj2 = $(this);
if (obj2.val() == obj2.attr('placeholder')) obj2.val('');
});
obj.blur(function(){
var obj2 = $(this);
if (obj2.val() == '') obj2.val(obj2.attr('placeholder'));
});
}
});
}
It is unobtrusive, because you don't need any javascript in your html code. the function above can easily changed if you want to use any other framework. I wouldn't use a solution without any Framework, because the frameworks do a great job in working around the incompatibilities between browsers.
This is how I would do it, without JQuery. It grays out the control when it shows the default text, and allows entering the default text if need be. The "title" tag will fallback to a tooltip for people who disable JavaScript:
<html>
<body>
<input type="text" value="" title="default text" />
<script type="text/javascript">
function DefaultInput(e) {
// Get the elements
this.e = e
this.d = e.title
this.s = e.style
e.removeAttribute('title') // remove the tooltip
e.value = '' // IE cached value remove HACK!
// Bind the events
e.onblur = this.bind(this.onblur)
e.onfocus = this.bind(this.onfocus)
// Show the initial value in gray
this.onblur()
}
DefaultInput.prototype = {
bind: function(f) {
// Return `f` so it's always called as an object of DefaultInput
var o = this
return function(){
f.apply(o, arguments)
}
},
onblur: function() {
// Gray out my value and show the default text if my value's blank
if (!this.h && !this.e.value) {
this.s.color = 'gray'
this.e.value = this.d
this.h = true // true -> help text displayed
// false -> help text hidden/user entered value
}
},
onfocus: function() {
// Make the text black and blank the text if in "help" mode
if (this.h) {
this.s.color = 'black'
this.e.value = ''
this.h = false
}
}
}
// Make sure the page is loaded before
// running for twitchy browsers like IE
window.onload = function() {
// Add defaults for all text input elements which have a `title`
var L = document.getElementsByTagName('input')
for (var i=0; i<L.length; i++) {
var e = L[i]
if (e.type=='text' && 'title' in e)
new DefaultInput(e)
}
}
</script>
</body>
EDIT: Cleared up the comments a bit, fixed some IE bugs and made it so it looks for <input> tags with title's to make it so different pages have less conversion time rather than individually intitializing the input controls ;-)
I think that you need something like this:
<input type="text" onfocus="if (this.value == this.getAttribute('mydefaulttext')) this.value = '';" onblur="if (this.value == '') this.value = this.getAttribute('mydefaulttext');" mydefaulttext="click here..." value="click here..."/>
<input name="test" type="text" id="test" value="testValue" />
<script type="text/javascript">
var myInput = document.getElementById("test");
myInput.onfocus = function() {
this.value = '';
}
myInput.onblur = function() {
if(this.value == '') this.value = "testValue";
}
</script>
Here's how I do:
Online Working Example
http://jsbin.com/ehivo3 (source code)
HTML
<input type="text" name="myfield" id="myfield" value="Please, fill my field!!!" />
jQuery
$(document).ready(function() {
// Handle each input on focus() and blug()
$('input[type="text"]').each(function() {
$(this)
// Store the default value internally
// Don't use .val() because browser autofill will poison it
.data('defaultValue', $(this).attr('value'))
// Handle the focus() (when you enter the field)
.focus(function() {
if ($(this).val() == $(this).data('defaultValue'))
$(this).val('');
})
// Handle the blur() (when you leave the field)
.blur(function() {
if ($(this).val() == '')
$(this).val($(this).data('defaultValue'));
});
});
// Clear all fields with "default value" on submit
$('form').submit(function() {
$('input[type="text"]', $(this)).each(function() {
// If the input still with default value, clean it before the submit
if ($(this).val() == $(this).data('defaultValue'))
$(this).val('');
});
});
});
And that's all! No invalid or extra attributes, valid markup and all handled in your jQuery file. :)

Categories

Resources