I am trying to get the down arrow keyup event to fire automagically using jQuery. The annotorious/seadragon combination has a listener that opens all preconfigured tags when I press the down arrow.
I have written jQuery code to find the input field, put focus on it and then trigger the keyup event.
function triggerDownArrowOnInput() {
$("[id^=downshift][id$=input]").each(function(index) {
// There should only be 1, but let's not assume.
console.log(index);
if (index == 0) {
console.log("Found an input: " + $(this).attr("id"))
$(this).focus();
var event = jQuery.Event("keyup");
event.keyCode = event.which = 40; // down arrow
$(this).trigger(event);
} else {
console.log("Multiple elements found that match the id: " + $(this).attr("id"));
} // if
})
} // triggerDownArrowOnInput
The focus is working great, but not the trigger. If I manually hit the down arrow key, then the preconfigured tags all appear:
I have tried "keyCode" and "which" separately.
I have tried triggering $(this).keyup(event).
I have tried putting in a delay between the focus call and the trigger/keyup call.
I have tried calling $(document).trigger(event).
I thought maybe I was sending the event to the wrong element, but it appears (going through Dev tools) that only the Input field and the document have the listeners enabled.
No matter what I do, I can't get the event to fire. Any ideas?
Thanks.
I think I've got this working without jQuery, using a KeyboardEvent and dispatchEvent. With my tests I don't think you need the focus before hand either because it's an event on the element, but worth testing this on your application.
function triggerDownArrowOnInput() {
$("[id^=downshift][id$=input]").each(function(index) {
// There should only be 1, but let's not assume.
console.log(index);
if (index == 0) {
console.log("Found an input: " + $(this).attr("id"))
$(this).focus();
this.dispatchEvent(new KeyboardEvent('keyup',{'keyCode': 40, 'key':'ArrowDown', 'code':'ArrowDown'}));
} else {
console.log("Multiple elements found that match the id: " + $(this).attr("id"));
}
})
}
Have you tried keydown?
var e = jQuery.Event("keydown");
e.which = 40;
e.keyCode = 40
$(this).trigger(e);
function triggerDownArrowOnInput() {
$("[id^=downshift][id$=input]").each(function(index) {
// There should only be 1, but let's not assume.
console.log(index);
if (index == 0) {
console.log("Found an input: " + $(this).attr("id"))
$(this).focus();
var event = jQuery.Event("keydown");
event.keyCode = event.which = 40;
$(this).trigger(event);
} else {
console.log("Multiple elements found that match the id: " + $(this).attr("id"));
}
})
} // triggerDownArrowOnInput
I was able to get the event to fire, but still wasn't able to open the menu on focus. I ended up having to create a development environment for:
recogito/recogito-client-core
recogito/recogito-js
recogito/annotorious
recogito/annotorious-openseadragon
I then modified Autocomplete.jsx in recogito/recogito-client-core, added an OnFocus listener and then added the following code:
const onFocus = evt => {
if (!isOpen) {
this.setState({ inputItems: this.props.vocabulary }); // Show all options on focus
openMenu()
} // if
} // onFocus
Way more than I wanted to do, but it is working now.
I am currently working on a Table Editor in Javascript and in my code I have the following setup which attempts to add a listener, after the HTML has been generated and appended to the table (a single row):
$(self.tableName + ' tbody').append(htmlString);
$(self.tableName + ' tbody tr').each(function (outerIndex) {
let id = $(this).find('td')[0].innerText;
if (id === elements[0]) {
$(this).find(':input').each(function (inputIndex) {
$(this).change(function (e) {
self.OnFieldValueChanged(objectCollection, elements[0], (inputIndex + 1), $(this).val());
});
if (self.options !== undefined) {
if (self.options.eventListeners !== undefined && self.options.eventListeners[inputIndex] !== undefined) {
let eventListenerArrLength = self.options.eventListeners[inputIndex].length;
for (let eventIndex = 0; eventIndex < eventListenerArrLength; eventIndex++) {
let event = self.options.eventListeners[inputIndex][eventIndex].event;
$(this).on(event, (e) => { self.options.eventListeners[inputIndex][eventIndex].listenerFunction(e) });
}
}
}
});
}
});
Now the weird thing is, this line here
$(this).change(function (e) {
self.OnFieldValueChanged(objectCollection, elements[0], (inputIndex + 1), $(this).val());
});
Actually works as intended. When a value changes in the table, this makes sure that my object representation of the table updates accordingly. However, this piece of the code:
if (self.options !== undefined) {
if (self.options.eventListeners !== undefined && self.options.eventListeners[inputIndex] !== undefined) {
let eventListenerArrLength = self.options.eventListeners[inputIndex].length;
for (let eventIndex = 0; eventIndex < eventListenerArrLength; eventIndex++) {
let event = self.options.eventListeners[inputIndex][eventIndex].event;
$(this).on(event, (e) => { self.options.eventListeners[inputIndex][eventIndex].listenerFunction(e) });
}
}
}
Does not do as intended. While the code does run it doesn't do as I want it to. The idea here is that given an options object, certain columns may have event listeners attached to do who knows what. My current function that I refer to here (self.options.eventListeners[inputIndex][eventIndex].listenerFunction(e)) is a function designed to only allow certain characters in a field and ignore everything else. So I use the 'keydown' event to achieve this.
But, alas, after the table has made the row and input elements that this code is attached to, the keydown event never triggers the function I added with $(this).on(...).
The let event = self.options.eventListeners[inputIndex][eventIndex].event; is literally just a string taken from an enum like const I've made. It currently holds either "change" or "keydown". So that should be good.
What am I missing about this setup?
I have some pages with forms in my application.
How can I secure the form in such a way that if someone navigates away or closes the browser tab, they should be prompted to to confirm they really want to leave the form with unsaved data?
Short, wrong answer:
You can do this by handling the beforeunload event and returning a non-null string:
window.addEventListener("beforeunload", function (e) {
var confirmationMessage = 'It looks like you have been editing something. '
+ 'If you leave before saving, your changes will be lost.';
(e || window.event).returnValue = confirmationMessage; //Gecko + IE
return confirmationMessage; //Gecko + Webkit, Safari, Chrome etc.
});
The problem with this approach is that submitting a form is also firing the unload event. This is fixed easily by adding the a flag that you're submitting a form:
var formSubmitting = false;
var setFormSubmitting = function() { formSubmitting = true; };
window.onload = function() {
window.addEventListener("beforeunload", function (e) {
if (formSubmitting) {
return undefined;
}
var confirmationMessage = 'It looks like you have been editing something. '
+ 'If you leave before saving, your changes will be lost.';
(e || window.event).returnValue = confirmationMessage; //Gecko + IE
return confirmationMessage; //Gecko + Webkit, Safari, Chrome etc.
});
};
Then calling the setter when submitting:
<form method="post" onsubmit="setFormSubmitting()">
<input type="submit" />
</form>
But read on...
Long, correct answer:
You also don't want to show this message when the user hasn't changed anything on your forms. One solution is to use the beforeunload event in combination with a "dirty" flag, which only triggers the prompt if it's really relevant.
var isDirty = function() { return false; }
window.onload = function() {
window.addEventListener("beforeunload", function (e) {
if (formSubmitting || !isDirty()) {
return undefined;
}
var confirmationMessage = 'It looks like you have been editing something. '
+ 'If you leave before saving, your changes will be lost.';
(e || window.event).returnValue = confirmationMessage; //Gecko + IE
return confirmationMessage; //Gecko + Webkit, Safari, Chrome etc.
});
};
Now to implement the isDirty method, there are various approaches.
You can use jQuery and form serialization, but this approach has some flaws. First you have to alter the code to work on any form ($("form").each() will do), but the greatest problem is that jQuery's serialize() will only work on named, non-disabled elements, so changing any disabled or unnamed element will not trigger the dirty flag. There are workarounds for that, like making controls readonly instead of enabling, serializing and then disabling the controls again.
So events seem the way to go. You can try listening for keypresses. This event has a few issues:
Won't trigger on checkboxes, radio buttons, or other elements that are being altered through mouse input.
Will trigger for irrelevant keypresses like the Ctrl key.
Won't trigger on values set through JavaScript code.
Won't trigger on cutting or pasting text through context menus.
Won't work for virtual inputs like datepickers or checkbox/radiobutton beautifiers which save their value in a hidden input through JavaScript.
The change event also doesn't trigger on values set from JavaScript code, so also won't work for virtual inputs.
Binding the input event to all inputs (and textareas and selects) on your page won't work on older browsers and, like all event handling solutions mentioned above, doesn't support undo. When a user changes a textbox and then undoes that, or checks and unchecks a checkbox, the form is still considered dirty.
And when you want to implement more behavior, like ignoring certain elements, you'll have even more work to do.
Don't reinvent the wheel:
So before you think about implementing those solutions and all required workarounds, realize you're reinventing the wheel and you're prone to running into problems others have already solved for you.
If your application already uses jQuery, you may as well use tested, maintained code instead of rolling your own, and use a third-party library for all of this.
jquery.dirty (suggested by #troseman in the comments) provides functions for properly detecting whether a form has been changed or not, and preventing the user from leaving the page while displaying a prompt. It also has other useful functions like resetting the form, and setting the current state of the form as the "clean" state. Example usage:
$("#myForm").dirty({preventLeaving: true});
An older, currently abandoned project, is jQuery's Are You Sure? plugin, which also works great; see their demo page. Example usage:
<script src="jquery.are-you-sure.js"></script>
<script>
$(function() {
$('#myForm').areYouSure(
{
message: 'It looks like you have been editing something. '
+ 'If you leave before saving, your changes will be lost.'
}
);
});
</script>
Custom messages not supported everywhere
Do note that since 2011 already, Firefox 4 didn't support custom messages in this dialog. As of april 2016, Chrome 51 is being rolled out in which custom messages are also being removed.
Some alternatives exist elsewhere on this site, but I think a dialog like this is clear enough:
Do you want to leave this site?
Changes you made may not be saved.
Leave Stay
Check out the JavaScript onbeforeunload event. It's non-standard JavaScript introduced by Microsoft, however it works in most browsers and their onbeforeunload documentation has more information and examples.
Universal solution requiring no configuration that automatically detects all input modification, including contenteditable elements:
"use strict";
(() => {
const modified_inputs = new Set;
const defaultValue = "defaultValue";
// store default values
addEventListener("beforeinput", (evt) => {
const target = evt.target;
if (!(defaultValue in target || defaultValue in target.dataset)) {
target.dataset[defaultValue] = ("" + (target.value || target.textContent)).trim();
}
});
// detect input modifications
addEventListener("input", (evt) => {
const target = evt.target;
let original;
if (defaultValue in target) {
original = target[defaultValue];
} else {
original = target.dataset[defaultValue];
}
if (original !== ("" + (target.value || target.textContent)).trim()) {
if (!modified_inputs.has(target)) {
modified_inputs.add(target);
}
} else if (modified_inputs.has(target)) {
modified_inputs.delete(target);
}
});
// clear modified inputs upon form submission
addEventListener("submit", (evt) => {
modified_inputs.clear();
// to prevent the warning from happening, it is advisable
// that you clear your form controls back to their default
// state with evt.target.reset() or form.reset() after submission
});
// warn before closing if any inputs are modified
addEventListener("beforeunload", (evt) => {
if (modified_inputs.size) {
const unsaved_changes_warning = "Changes you made may not be saved.";
evt.returnValue = unsaved_changes_warning;
return unsaved_changes_warning;
}
});
})();
via jquery
$('#form').data('serialize',$('#form').serialize()); // On load save form current state
$(window).bind('beforeunload', function(e){
if($('#form').serialize()!=$('#form').data('serialize'))return true;
else e=null; // i.e; if form state change show warning box, else don't show it.
});
You can Google JQuery Form Serialize function, this will collect all form inputs and save it in array. I guess this explain is enough :)
Built on top of Wasim A.'s excellent idea to use serialization. The problem there was that the warning was also shown when the form was being submitted. This has been fixed here.
var isSubmitting = false
$(document).ready(function () {
$('form').submit(function(){
isSubmitting = true
})
$('form').data('initial-state', $('form').serialize());
$(window).on('beforeunload', function() {
if (!isSubmitting && $('form').serialize() != $('form').data('initial-state')){
return 'You have unsaved changes which will not be saved.'
}
});
})
It has been tested in Chrome and IE 11.
Based on the previous answers, and cobbled together from various places in stack overflow, here is the solution I came up with which handles the case when you actually want to submit your changes:
window.thisPage = window.thisPage || {};
window.thisPage.isDirty = false;
window.thisPage.closeEditorWarning = function (event) {
if (window.thisPage.isDirty)
return 'It looks like you have been editing something' +
' - if you leave before saving, then your changes will be lost.'
else
return undefined;
};
$("form").on('keyup', 'textarea', // You can use input[type=text] here as well.
function () {
window.thisPage.isDirty = true;
});
$("form").submit(function () {
QC.thisPage.isDirty = false;
});
window.onbeforeunload = window.thisPage.closeEditorWarning;
It's worth noting that IE11 seems to require that the closeEditorWarning function returns undefined for it not to show an alert.
The following one-liner has worked for me.
window.onbeforeunload = s => modified ? "" : null;
Just set modified to true or false depending on the state of your application.
You can use serialize() to create a URL encoded text string by serializing form values and check whether the form has changed beforeunload
$(document).ready(function(){
var form = $('#some-form'),
original = form.serialize()
form.submit(function(){
window.onbeforeunload = null
})
window.onbeforeunload = function(){
if (form.serialize() != original)
return 'Are you sure you want to leave?'
}
})
Refer this link https://coderwall.com/p/gny70a/alert-when-leaving-page-with-unsaved-form
Written by Vladimir Sidorenko
Following code works great. You need to reach your form elements' input changes via id attribute:
var somethingChanged=false;
$('#managerForm input').change(function() {
somethingChanged = true;
});
$(window).bind('beforeunload', function(e){
if(somethingChanged)
return "You made some changes and it's not saved?";
else
e=null; // i.e; if form state change show warning box, else don't show it.
});
});
Tested Eli Grey's universal solution, only worked after I simplified the code to
'use strict';
(() => {
const modified_inputs = new Set();
const defaultValue = 'defaultValue';
// store default values
addEventListener('beforeinput', evt => {
const target = evt.target;
if (!(defaultValue in target.dataset)) {
target.dataset[defaultValue] = ('' + (target.value || target.textContent)).trim();
}
});
// detect input modifications
addEventListener('input', evt => {
const target = evt.target;
let original = target.dataset[defaultValue];
let current = ('' + (target.value || target.textContent)).trim();
if (original !== current) {
if (!modified_inputs.has(target)) {
modified_inputs.add(target);
}
} else if (modified_inputs.has(target)) {
modified_inputs.delete(target);
}
});
addEventListener(
'saved',
function(e) {
modified_inputs.clear()
},
false
);
addEventListener('beforeunload', evt => {
if (modified_inputs.size) {
const unsaved_changes_warning = 'Changes you made may not be saved.';
evt.returnValue = unsaved_changes_warning;
return unsaved_changes_warning;
}
});
})();
The modifications to his is deleted the usage of target[defaultValue] and only use target.dataset[defaultValue] to store the real default value.
And I added a 'saved' event listener where the 'saved' event will be triggered by yourself on your saving action succeeded.
But this 'universal' solution only works in browsers, not works in app's webview, for example, wechat browsers.
To make it work in wechat browsers(partially) also, another improvements again:
'use strict';
(() => {
const modified_inputs = new Set();
const defaultValue = 'defaultValue';
// store default values
addEventListener('beforeinput', evt => {
const target = evt.target;
if (!(defaultValue in target.dataset)) {
target.dataset[defaultValue] = ('' + (target.value || target.textContent)).trim();
}
});
// detect input modifications
addEventListener('input', evt => {
const target = evt.target;
let original = target.dataset[defaultValue];
let current = ('' + (target.value || target.textContent)).trim();
if (original !== current) {
if (!modified_inputs.has(target)) {
modified_inputs.add(target);
}
} else if (modified_inputs.has(target)) {
modified_inputs.delete(target);
}
if(modified_inputs.size){
const event = new Event('needSave')
window.dispatchEvent(event);
}
});
addEventListener(
'saved',
function(e) {
modified_inputs.clear()
},
false
);
addEventListener('beforeunload', evt => {
if (modified_inputs.size) {
const unsaved_changes_warning = 'Changes you made may not be saved.';
evt.returnValue = unsaved_changes_warning;
return unsaved_changes_warning;
}
});
const ua = navigator.userAgent.toLowerCase();
if(/MicroMessenger/i.test(ua)) {
let pushed = false
addEventListener('needSave', evt => {
if(!pushed) {
pushHistory();
window.addEventListener("popstate", function(e) {
if(modified_inputs.size) {
var cfi = confirm('确定要离开当前页面嘛?' + JSON.stringify(e));
if (cfi) {
modified_inputs.clear()
history.go(-1)
}else{
e.preventDefault();
e.stopPropagation();
}
}
}, false);
}
pushed = true
});
}
function pushHistory() {
var state = {
title: document.title,
url: "#flag"
};
window.history.pushState(state, document.title, "#flag");
}
})();
var unsaved = false;
$(":input").change(function () {
unsaved = true;
});
function unloadPage() {
if (unsaved) {
alert("You have unsaved changes on this page. Do you want to leave this page and discard your changes or stay on this page?");
}
}
window.onbeforeunload = unloadPage;
Short answer:
let pageModified = true
window.addEventListener("beforeunload",
() => pageModified ? 'Close page without saving data?' : null
)
The solution by Eerik Sven Puudist ...
var isSubmitting = false;
$(document).ready(function () {
$('form').submit(function(){
isSubmitting = true
})
$('form').data('initial-state', $('form').serialize());
$(window).on('beforeunload', function() {
if (!isSubmitting && $('form').serialize() != $('form').data('initial-state')){
return 'You have unsaved changes which will not be saved.'
}
});
})
... spontaneously did the job for me in a complex object-oriented setting without any changes necessary.
The only change I applied was to refer to the concrete form (only one form per file) called "formForm" ('form' -> '#formForm'):
<form ... id="formForm" name="formForm" ...>
Especially well done is the fact that the submit button is being "left alone".
Additionally, it works for me also with the lastest version of Firefox (as of February 7th, 2019).
Adding to te idea of #codecaster
you could add this to every page with a form (in my case i use it in global way so only on forms would have this warn) change his function to
if ( formSubmitting || document.getElementsByTagName('form').length == 0)
Also put on forms submit including login and in cancel buttons links so when person press cancel or submit the form won't trigger the warn also in every page witouth a form...
<a class="btn btn-danger btn-md" href="back/url" onclick="setFormSubmitting()">Cancel</a>
You could check for a detailed explanation here:
http://techinvestigations.redexp.in/comparison-of-form-values-on-load-and-before-close/
The main code:
function formCompare(defaultValues, valuesOnClose) {
// Create arrays of property names
var aPropsFormLoad = Object.keys(defaultValues);
var aPropsFormClose = Object.keys(valuesOnClose);
// If number of properties is different,
// objects are not equivalent
if (aPropsFormLoad.length != aPropsFormClose.length) {
return false;
}
for (var i = 0; i < aPropsFormLoad.length; i++) {
var propName = aPropsFormLoad[i];
// If values of same property are not equal,
// objects are not equivalent
if (defaultValues[aPropsFormLoad]+"" !== valuesOnClose[aPropsFormLoad]+"") {
return false;
}
}
// If we made it this far, objects
// are considered equivalent
return true;
}
//add polyfill for older browsers, as explained on the link above
//use the block below on load
for(i=0; i < document.forms[0].elements.length; i++){
console.log("The field name is: " + document.forms[0].elements[i].name +
" and it’s value is: " + document.forms[0].elements[i].value );
aPropsFormLoad[i] = document.forms[0].elements[i].value;
}
//create a similar array on window unload event.
//and call the utility function
if (!formCompare(aPropsOnLoad, aPropsOnClose))
{
//perform action:
//ask user for confirmation or
//display message about changes made
}
I did it differently, sharing here so that someone can get help, tested only with Chrome.
I wanted to warn user before closing the tab only if there are some changes.
<input type="text" name="field" value="" class="onchange" />
var ischanged = false;
$('.onchange').change(function () {
ischanged = true;
});
window.onbeforeunload = function (e) {
if (ischanged) {
return "Make sure to save all changes.";
}
};
Works good, but got an-other issue, when i submit the form i get the unwanted warning, i saw lots of workaround on it, this is because onbeforeunload fires before onsubmit thats why we can't handle it in onsubmit event like onbeforeunload = null, but onclick event of submit button fires before these both events, so i updated the code
var isChanged = false;
var isSubmit = false;
window.onbeforeunload = function (e) {
if (isChanged && (!isSubmit)) {
return "Make sure to save all changes.";
}
};
$('#submitbutton').click(function () {
isSubmit = true;
});
$('.onchange').change(function () {
isChanged = true;
});
I made following code. It can compare changes in all fields (except those marked with .ignoreDirty class) or optionally for currently visible fields only. It can be reinitialized for new fields added by Javascript. From that reason I save not the form status but the status of each control.
/* Dirty warning for forms */
dirty = (skipHiddenOrNullToInit) => {
/* will return True if there are changes in form(s)
for first initialization you can use both: .dirty(null) or .dirty() (ignore its result)
.dirty(null) will (re)initialize all controls - in addititon use it after Save if you stay on same page
.dirty() will initialize new controls - in addititon use it if you add new fields with JavaScript
then
.dirty() (or: .dirty(false)) says if data are changed without regard to hidden fields
.dirty(true) says if data are changed with regard to hidden fields (ie. fields with .d-none or .hidden class)
controls with .ignoreDirty class will be skipped always
previous about .d-none, .hidden, .ignoreDirty applies to the control itself and all its ancestors
*/
let isDirty = false;
let skipSelectors = '.ignoreDirty';
if (skipHiddenOrNullToInit) {
skipSelectors += ', .d-none, .hidden'
} else if (skipHiddenOrNullToInit === undefined) {
skipHiddenOrNullToInit = false;
}
$('input, select').each(
function(_idx, el) {
if ($(el).prop('type') !== 'hidden') {
let dirtyInit = $(el).data('dirty-init');
if (skipHiddenOrNullToInit === null || dirtyInit === undefined) {
try {
isChromeAutofillEl = $(el).is(":-webkit-autofill");
} catch (error) {
isChromeAutofillEl = false;
}
if (isChromeAutofillEl && $(el).data('dirty-init') === undefined) {
setTimeout(function() { // otherwise problem with Chrome autofilled controls
$(el).data('dirty-init', $(el).val());
}, 200)
} else {
$(el).data('dirty-init', $(el).val());
}
} else if ($(el).closest(skipSelectors).length === 0 && dirtyInit !== $(el).val()) {
isDirty = true;
return false; // breaks jQuery .each
}
}
}
);
return isDirty;
}
I have additional troubles with Chrome autofill values because it is difficult to initizialize and have them loaded already. So I do not initialize on page load but in any focusin event. (But: Maybe there is still problem with control values changed by JavaScript.) I use following code which I call at page load:
let init_dirty = (ifStayFunc) => {
/* ifStayFunc: optional callback when user decides to stay on page
use .clearDirty class to avoid warning on some button, however:
if the button fires JavaScript do't use .clearDirty class and instead
use directly dirty(null) in code - to be sure it will run before window.location */
$('input, select').on('focusin', function(evt) {
if (!$('body').data('dirty_initialized')) {
dirty();
$('body').data('dirty_initialized', true);
}
});
window.addEventListener('beforeunload', (evt) => {
if (dirty(true)) {
if (ifStayFunc) {
ifStayFunc();
}
evt.preventDefault();
evt.returnValue = ''; // at least Google Chrome requires this
}
});
$('.clearDirty').on('click', function(evt) {
dirty(null);
});
};
So, I add the .clearDirty class to the buttons which provide Save and that way I prevent the warning in this case.
Callback ifStayFunc allows me to do something if user will Stay on Page while he is warned. Typically I can show additional Save Button (if I have still visible only some default/primary button, which makes Safe+SomethingMore - and I want allow Save withou this "SomethingMore").
i have a strange problem only in Chrome using an iframe but working in all others common browser.
the problem: If i type in the IFRAME and then press the button to send, it work fine, the focus back to the IFRAME and the cursor BLINK.
But if i type and then press ENTER to invoke the event handler function, the focus back but the cursor disappear. And then if you go in another window and then back the cursor appear. This happen only in Chrome. I did the example page to show the problem in action. Click the link below to see.
UPDATE: I added the code also here below
var editorFrame = 'myEditor'
function addFrame() {
var newFrame = new Element('iframe', {
width: '520',
height: '100',
id: editorFrame,
name: editorFrame,
src: 'blank.asp',
class: 'myClass'
});
$('myArea').appendChild(newFrame);
window.iframeLoaded = function() {
// this is call-back from the iframe to be sure that is loaded, so can safety attach the event handler
var iframeDoc, UNDEF = "undefined";
if (typeof newFrame.contentDocument != UNDEF) {
iframeDoc = newFrame.contentDocument;
} else if (typeof newFrame.contentWindow != UNDEF) {
iframeDoc = newFrame.contentWindow.document;
}
if (typeof iframeDoc.addEventListener != UNDEF) {
iframeDoc.addEventListener('keydown', keyHandler, false);
} else if (typeof iframeDoc.attachEvent != UNDEF) {
iframeDoc.attachEvent('onkeydown', keyHandler);
}
};
}
function resetContent()
{
var myIFrame = $(editorFrame);
if (myIFrame) myIFrame.contentWindow.document.body.innerHTML='';
}
function setEditFocus()
{
var iFrame = document.frames ? document.frames[editorFrame] : $(editorFrame);
var ifWin = iFrame .contentWindow || iFrame;
ifWin.focus();
}
function send()
{
resetContent();
setEditFocus();
}
function keyHandler (evt) {
var myKey=(evt.which || evt.charCode || evt.keyCode)
if (myKey==13) {
if (!evt) var evt = window.event;
evt.returnValue = false;
if (Prototype.Browser.IE) evt.keyCode = 0;
evt.cancelBubble = true;
if (evt.stopPropagation) evt.stopPropagation();
if (evt.preventDefault) evt.preventDefault();
send();
}
}
In the HTML page
<body onload="addFrame()">
<div id="myArea"></div>
<input id="myButton" type="button" value="click me to send [case 1]" onclick="send()">
To make more easy to understand the problem i've create a specific page to reproduce the problem with full example and source included.
You can view here by using Google Chrome:
example of the problem
I really need your help because i tried to solve this problem for many days with no luck. And all the suggestions, tips and workaround are well accepted.
Thanks in advance.
I'm not really sure what the cause of the issue is, as there are times where Chrome will give focus to the element correctly, though most of the time it does not. You shouldn't need to request focus at all, since the focus is not lost when you press the key. If you omit the setEditFocus() call, you should notice that it still works correctly in everything but Chrome, which apparently gets offended that you've removed all of the content in the body.
When you set contenteditable, every browser sets the innerHTML of the iframe document's body element to be something different:
Browser | innerHTML
-----------------------------
Internet Explorer | ''
Opera | '<br>\n'
Firefox | '<br>'
Chrome/Safari | '\n'
If you're not expecting to see that extra stuff when you parse the content later, you might want to remove it upfront in addFrame().
I was able to "fix" the problem by doing the following:
First, update the event handler so we can return false in it and prevent Opera from generating HTML for fun when we call getSelection() later...
function addFrame() {
...
window.iframeloaded = function() {
...
if (typeof iframeDoc.addEventListener != UNDEF) {
iframeDoc.addEventListener('keypress', keyHandler, false);
} else if (typeof iframeDoc.attachEvent != UNDEF) {
iframeDoc.attachEvent('onkeypress', keyHandler);
}
}
}
Edit: Removed original function in favour of the new one included below
Finally, return false from the key press handler to fix the Opera issue mentioned above.
function keyHandler (evt) {
var myKey=(evt.which || evt.charCode || evt.keyCode)
if (myKey==13) {
...
return false;
}
}
I had originally done what syockit suggested, but I found it was doing weird things with the caret size in Chrome, which this method seems to avoid (although Firefox is still a bit off...). If you don't care about that, setting the innerHTML to be non-blank is likely an easier solution.
Also note that you should be using className instead of class in the object you pass to new Element(), since IE seems to consider it a reserved word and says that it's a syntax error.
Edit: After playing around with it, the following function seems to work reliably in IE8/Firefox/Chrome/Safari/Opera for your more advanced test case. Unfortunately, I did have to include Prototype's browser detection to account for Opera, since while everything looks the same as far as the JavaScript is concerned, the actual behaviour requires different code that conflicts with the other browsers, and I wasn't able to find a better way to differentiate between them.
Here's the new function, which focuses on the editable content of the iframe, and makes sure that if there is already content in there, that the caret is moved to the end of that content:
function focusEditableFrame(frame) {
if (!frame)
return;
if (frame.contentWindow)
frame = frame.contentWindow;
if (!Prototype.Browser.Opera) {
frame.focus();
if (frame.getSelection) {
if (frame.document.body.innerHTML == '')
frame.getSelection().extend(frame.document.body, 0);
else
frame.getSelection().collapseToEnd();
} else if (frame.document.body.createTextRange) {
var range = frame.document.body.createTextRange();
range.moveEnd('character', frame.document.body.innerHTML.length);
range.collapse(false);
range.select();
}
} else {
frame.document.body.blur();
frame.document.body.focus();
}
}
Updated setEditFocus() (Not really necessary now, but since you already have it):
function setEditFocus()
{
focusEditableFrame($(editorFrame));
}
You know how I solved this one? In resetContent(), replace '' with ' ':
if (myIFrame) myIFrame.contentWindow.document.body.innerHTML=' ';
If it works, good. Don't ask why though, it might be one of those Webkit glitches with Range object, file a bug if you will.
Just quickly, can you try adding semicolons to the end of the lines inside your send() function? And see if that works.
function send() {
resetContent();
setEditFocus();
}