I have been playing around with Scala/Lift/Comet/Ajax etc. recently. I came across a problem which boils down to this:
Summary
I want to update a specific div (by id) when a certain event occurs. If the div does not exist yet, it must be created and appended to the HTML body.
Currently I cannot get this to work when using the Lift framework.
Source File
LIFT_PROJECT/src/main/webapp/static/mouseViewTest.html:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Test</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.js" type="text/javascript"></script>
<script type="text/javascript">
// <![CDATA[
$(document).ready(function() {
updateOrCreateMouseDiv('123', 'coords')
});
function updateOrCreateMouseDiv(uniqueId, coords) {
if ($('#mouse_'+uniqueId).length == 0) {
$('body').append('<div id=' + uniqueId + '>' + coords + '</div>');
}
$('#mouse_'+uniqueId).html(coords)
}
// ]]>
</script>
</head>
<body></body>
</html>
The Error
If I open the above file directly in a browser (file:///LIFT_PROJECT/src/main/webapp/static/mouseViewTest.html) it works i.e. a new div is created.
But if I run it through Lift/Jetty (http://localhost:8080/static/mouseViewTest) I get the following JavaScript error:
Chrome:
Uncaught Error: NO_MODIFICATION_ALLOWED_ERR: DOM Exception 7
Firefox (Firebug):
An invalid or illegal string was specified" code: "12
Comparing the Sources in Browser
When comparing the page sources in the browser, I can see only one difference, namely: Lift adds the following JavaScript just before the closing </body> tag:
<script type="text/javascript" src="/ajax_request/liftAjax.js"></script>
<script type="text/javascript">
// <![CDATA[
var lift_page = "F320717045475W3A";
// ]]>
</script>
Questions
Does anyone have an idea why this happens?
If I would want to move the JavaScript code into the Scala file (using Lift's JavaScript and jQuery support), what would the code look like?
Please note: When I used Jq("body") ~> JqAppend() to create new divs, it worked. I just didn't know how to check whether the div id already existed. Thats why I moved the code into the template, planning on using Lift's Call function to execute the JS function. And thats when these problems started...
Thanks!
I recently ran into a similar problem and, from what I've gathered, the problem is because the page when served by lift is served as XHTML and there are some issues when writing to the DOM if the page is XHTML vs. HTML. I don't know whether this is a bug with jQuery or Safari or if it's just something that's not possible in XHTML, but a quick way to fix it is to modify your Boot.scala to tell Lift to not use XHTML as the mime type with this line:
LiftRules.useXhtmlMimeType = false
Related
Here is some HTML (Fiddle):
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>Test Write</title>
<script>
function test() {
if (window.location.hash == '#test') {
alert('The hash is already set!');
} else {
document.open();
document.write('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\
<html>\
<head>\
<title>Hello There!</title>\
<script>\
function test() {\
window.location.hash="test";\
}\
</'+'script>\
</head>\
<body>\
<button onclick="test()">Test Hash Now</button>\
</body>\
</html>');
document.close();
}
}
</script>
</head>
<body>
<button onclick="test()">Click to write new page</button>
</body>
</html>
If you run this - then click "Click to write new page" - it will write a new HTML fragment onto the document. This time there is a button to "Test Hash Now" - when you click all it does is update the window.location.hash.
In FireFox the page tries to reload again unexpectedly. In all other browsers it works fine. In Fiddle you will see an error message {"error": "Please use POST request"} if you create a HTML file it will work better, you'll see the "Click to write new page" button appear again after clicking "Test Hash Now" which is (or should be) wrong.
Why is this happening?
My use case is simple - I have a bootstrap page that contains a javascript that fetches a bunch of data using AJAX. It then generates a large HTML string (including doctype/head/body/etc) and then it just needs to render that HTML string.
In this case I am using an inline HTML as a constant - but in the real case it is generating it using some logic. I am trying to simulate what the server will respond with and eliminate any server side requirements for the client.
I would just have a bunch of HTML and JS files zipped up into one file, and I could give that to anyone (developer or not) to run without any need for a server or database. Then they could see the page as if it were the real-deal. That's my goal. If I simply put the HTML into an existing element on the page (such as just manipulating the body of the current page) - it won't behave EXACTLY the same as if the entire document were generated. Plus I want the bootstrap HTML file to be really small (with just one javascript include at the top).
Everything seems to work - except Firefox is the only one that fails. It all works unless there is a script referenced inside the page that needs to write something to the window.location.hash - then it tries to reload the whole page... Uggg...
Any better way to accomplish this?
I tried to create an iframe and using the same document.open();document.write(html);document.close() - but the same exact issue happens.
Try this code to push the hash state:
if (history.pushState) {
history.pushState(null, null, '#test');
}
else {
location.hash = '#test';
}
but I haven't tested it myself.
Added: Try naming your functions test1() and test2(). This won't fix it necessarily, but it may help to debug/discover what is happening.
I am very confused.
I created the following script which is located at http://tapmeister.com/test/dom.html. For some unknown reason, tinymce.editors.ta1 and tinymce.editors[0] show up as undefined, and attempting to use a method under them results in an error. But when I inspect tinymce or tinymce.editors using FireBug, I see them in the DOM.
So, I create a jsfiddle http://jsfiddle.net/JWyWM/ to show the people on stackoverflow. But when I test it out, tinymce.editors.ta1 and tinymce.editors[0] are no longer undefined, and the methods work without error.
What is going on??? Maybe something to do with public/protected/private properties? How do I access methods such as tinymce.editors.ta1.hide()? Thank you!!!
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />
<title>Testing</title>
<script src="http://tinymce.cachefly.net/4.0/tinymce.min.js"></script>
<script type="text/javascript">
tinymce.init({selector: "textarea#ta1"});
tinymce.init({selector: "textarea#ta2"});
console.log(tinymce);
console.log(tinymce.editors);
console.log(tinymce.editors.ta1);
console.log(tinymce.editors[0]);
//tinymce.editors.ta1.hide();
//alert('pause');
//tinymce.editors.ta1.show();
</script>
</head>
<body>
<form>
<textarea id="ta1"></textarea>
<textarea id="ta2"></textarea>
</form>
</body>
</html>
TinyMCE doesn't do all of the setup work immediately when you call init. It provides a callback, setup, to tell you when the work is done.
So if you provide a setup callback, you can interact with the editor instance then.
Here's an example (I've also moved your scripts to the end, which is best practice regardless):
Live Example | Live Source
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />
<title>Testing</title>
</head>
<body>
<form>
<textarea id="ta1"></textarea>
<textarea id="ta2"></textarea>
</form>
<script src="http://tinymce.cachefly.net/4.0/tinymce.min.js"></script>
<script type="text/javascript">
tinymce.init({
selector: "#ta1, #ta2",
setup: function(e) {
console.log("Editor " + e.id + " is ready");
}
});
</script>
</body>
</html>
Now, if you want to actually access the editor instance, bizarrely TinyMCE doesn't add it to tinymce.editors until after calling the setup function. But if you throw in a brief yield, you're all set. Here's the above with a changed setup function:
Live Copy | Live Source
setup: function(e) {
// Bizarrely, TinyMCE calls `setup` *before* adding
// the relevant editor to `tinymce.editors`,
// so we have to yield briefly
console.log("Editor " + e.id + " is ready");
if (e.id === "ta2") {
console.log("It's ta2, I'll hide it in a moment.");
setTimeout(function() {
tinymce.editors[e.id].hide();
}, 0);
}
}
So why did it work on jsFiddle? Well, jsFiddle has a truly brain dead surprising default setting, which is to put all of your script in a window#load callback function. window#load happens very late in the load process, after all external resources have been loaded. (You can see that in the jsFiddle UI, it's the second drop-down list on the left.) So apparently TinyMCE was completely ready at that point, where it isn't earlier in the cycle.
Side note: 99.9% of the time, there is absolutely no point in supplying a tag name with an id selector, e.g. textarea#ta1. id values are unique, so you don't have to qualify them unless you explicitly want to avoid matching an element that may sometimes have one tag name, or other times have another, which is a pretty unusual use case.
There's a large chance that your script is running before tinyMCE has actually loaded. It might be the case that it loads faster from your test site so that is why it works.
Use as a quick check.
So what i am trying to achieve is to manipulate a javascript variable from a website, so that when I load the page it changes to a value I have pre determined (inmyscript.js).
In Safari's extension builder I have the following setup:
Access Level: All (To make sure it is running correctly for the time being)
Start Scripts: jquery.min.js (the jquery script)
End Scritps: myscript.js (myscript)
The Start Scripts, will load the jquery script, as I want to use jquery for some DOM manipulation.
The End Script is the script which contains an overwrite for the variable I am trying to change in the html document.
Currently myscript.js looks like:
$(document).ready(function(){
var numberImThinkingOf = 999;
});
For an example of what I am trying to do: The following page, prints out the value of numberImThinkingOf, by creating a new paragraph element every time the submit button is pressed.
So with out the extension it will print out
Value: 5
Value: 5
Value: 5
If pressed three times.
However I want my Safari Extension to change the default value of the numberImThinkingOf variable once all DOM elements are loaded, to that specified in myscript.js So that when I press the submit button it will output:
Value: 999
Ideally I don't want to manipulate the DOM so that it inserts another script element. I originally though that javascript attached variables to the window object. But I guess I was wrong. Event if I have a script such as
$(document).ready(function(){
alert(numberImThinkingOf)
});
It returns undefined. :( Any help would me much appreciated.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<script>
var numberImThinkingOf = 5;
function whatNumber(){
var newElement = document.createElement("p");
newElement.textContent = "Value: "+numberImThinkingOf;
document.body.insertBefore(newElement, document.body.firstChild);
}
</script>
</head>
<body>
<input type="submit" value="Continue →" onclick="whatNumber()" />
</body>
</html>
I believe your script and the page get different window objects, to avoid unexpected contamination. Here's what the Safari Extensions Development Guide says:
Injected scripts have an implied namespace—you don’t have to worry about your variable or function names conflicting with those of the website author, nor can a website author call functions in your extension. In other words, injected scripts and scripts included in the webpage run in isolated worlds, with no access to each others’ functions or data.
It sounds like you need to attach this variable to the DOM in some fashion, perhaps as an attribute of a tag.
Content scripts are sandboxed, if you want to access parent page variables you have to inject <script> tag with your code. You can find some examples here.
I'm working through some javascript examples, and I just did this one:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Page title</title>
<script type="text/javascript">
function displayText()
{
document.getElementById('targetDIV').innerHTML = "You're using Javascript";
}
</script>
</head>
<body onload="displayText()">
<h2>This should be before the other text.</h2>
<div id="targetDIV">
</div>
</body>
</html>
OK. Very basic, I know-but I realized I was confused about the "why" of some things. Could it be accurate to say that:
Function=WHAT will happen.
The call (the body onload...)= WHEN it will happen.
and div id="targetDIV" = WHERE it will happen
I know this is the case in this example, but in general is that the way things work in Javascript?
Yes, that's a pretty good working model to carry in your head.
onload for the body is called an Event and many objects issue events. Your function displayText is called in response to the onload Event and is therefore an event handler.
The code inside your function can do anything, but in this case it dynamically loads some text into a tag on your page.
There are a couple of other things worth pointing out at this point. You access the tag using document.getElementById. document is variable available to you in Javascript which contains a model of the page called the DOM or document object model. This is extremely powerful as it presents a hierarchical layout of everything on your page and allows you to manipulate the contents.
getElementById() is a very useful function which searches the DOM tree and returns the object which has the ID that you specify, it's a sort of search. The text gets to your tag because you added the targetDIV id to the DIV tag and therefore you could find it via the DOM function.
Welcome to programming in Javascript. Now you have a goood working model you'll find loads of really clever things you can do and your life as a web programmer will never be the same again.
Sound good to me.
I have found several other questions here on S.O. (and the web in general) that ask roughly this same question, but the answers always seem to suggest other ways of structuring code that avoid the need for addressing this underlying issue.
For example, many people suggest (and a good suggestion, I agree) to put your code in the jquery load method's callback, on the calling page and not the called page. However I have unique scripts that may appear in certain resources, so I would not want to do that for every load and nor do I necessarily know what these scripts will be.
Here is a test setup to demonstrate what I'm trying to do. The short summary is that when I load partial.htm from main.htm, its script does not fire.
main.htm:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>main file</title>
</head>
<body>
<ul id="links">
<li>some page1</li>
<li>some page 2</li>
<li>some other partial page</li>
</ul>
<div id="panel" style="display:none; padding:20px; background-color:#CCC;">
LOADED CONTENT WILL GO HERE
</div>
<script type="text/javascript" src="/path/to/jquery-1.3.2.min.js"> </script>
<script type="text/javascript">
$(function(){
$('#links a').click(function() {
var $panel = $('#panel');
$panel.show();
$panel.html('Please wait...');
var href = $(this).attr('href');
$('#panel').load(href + ' #content');
return false;
});
});
</script>
</body>
</html>
OK, very simple functionality on this page. Imagine there are many more links, and some of them may require scripting while others do not.
Here is partial.htm:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>partial file</title>
</head>
<body>
<div id="content">
<p>Hey, I am the partial file!</p>
<script type="text/javascript">
alert('I am some JS in the partial file! But sadly I do not execute...');
</script>
</div>
<div>
I am some other content on the page that won't be included by jquery.load()...
</div>
</body>
</html>
Notice that my script in partial.htm does not fire. So, my question remains: how to get this to fire, excluding any answers that tell me to put this in the .load() method's callback. (This would be because I may not have the fore-knowledge of which scripts these partial pages may contain or require!)
Thank you!
Update #1:
I suppose an acceptable answer is simply "you can't." However, I'd like to know if this is definitively the case. I haven't been able to find anything that officially states this yet.
Also, when I use firebug to inspect the panel region afterwards, there is no script element present at all. It is as if it is being parsed out by load.
Update #2:
I've narrowed this down to be a problem only when using the selector as part of the href. Loading the entire "somepage.html" will execute the script, but loading "somepage.html #someregion" does not.
$panel.load('somepage.html'); // my script fires!
$panel.load('somepage.html #someregion'); // script does not fire
I'm going to try and hunt down why this may be the case in the jquery source...
Well it seems that this is by design. Apparently to make IE happy, the rest of us suffer. Here's the relevant code in the jquery source:
// See if a selector was specified
self.html( selector ?
// Create a dummy div to hold the results
jQuery("<div/>")
// inject the contents of the document in, removing the scripts
// to avoid any 'Permission Denied' errors in IE
.append(res.responseText.replace(/<script(.|\s)*?\/script>/g, ""))
// Locate the specified elements
.find(selector) :
// If not, just inject the full result
res.responseText );
I'm wondering if, instead of just stripping out the scripts, I could modify the jquery source to include them in some other way that makes IE happy? I still have yet to find anything else on the web discussing this matter, I'm sure I'm not the only person stumped by this?
I have run across issues before with IE not running injected <script>s that didn't contain the defer attribute. This discussion thread has some good information about the topic: innerHTML and SCRIPT tag