What's wrong with this simple Safari Extension code? - javascript

I'm creating a Safari extension that will stay in Safari's menubar, and upon being clicked, it will open all links containing a certain string. However, it's not working.
This is what my extension builder screen looks like: http://i.imgur.com/xRXB1.png
I don't have any external scripts set as I have the script in my HTML file, because I only want it to run when clicked.
And I have a global.html page with the following code in it:
<!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" lang="en" xml:lang="en">
<head>
<script type="text/javascript" src="jquery.js"></script>
</head>
<body>
<script>
safari.application.addEventListener("comnand", performCommand, false);
Function performCommand(event) {
if (event.command == "open-designs") {
$(document).ready(function() {
$('a[href*="/Create/DesignProduct.aspx?"]').each(function() {
window.open($(this).attr('href'),'_blank');
});
});
}
}
</script>
</body>
</html>
Should this not work? I'm allowed to mix jQuery and JS write, as jQuery is JS? And isn't that how I'd target the links?

The problem is that your extensions Global page does not have direct access to the currently loaded page's DOM. To be able to achieve what you need, you'll have to use an Injected Script and use the messaging proxy to talk to the page.
For instance, your global would look like:
<!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" lang="en" xml:lang="en">
<head>
<script type="text/javascript" src="jquery.js"></script>
</head>
<body>
<script>
$(document).ready(function() {
safari.application.addEventListener("command", performCommand, false);
});
function performCommand(event) {
if (event.command == "open-designs") {
safari.application.activeBrowserWindow.activeTab.page.dispatchMessage("open-designs", "all");
}
}
</script>
</body>
</html>
And then, in Extension Builder, you'd need to add two "Start Scripts", one is jquery, the other, a new file that gets loaded into the page and looks similar to this:
function extensionname_openAll(event)
{
if (event.name == 'open-designs')
{
$('a[href*="/Create/DesignProduct.aspx?"]').each(function(index,elem) {
window.open($(elem).attr('href'),'_blank');
});
}
}
safari.self.addEventListener("message", extensionname_openAll, true);

One clear thing I'm seeing is that your $(document).ready() function is located within another function. This essentially alleviates the need for a $(document).ready() provided you only call that function once the DOM and jQuery are fully loaded.
Rearrange your code to only add the event listener once the DOM and jQuery are loaded. That is what you use the $(document).ready() callback for.
In addition there is one more issue I see with the callback function for .each(). That function needs to handle two parameters the index and the element that it references. A call to each() iterates over a collection of elements. For each element entering the callback function, its index is passed as a parameter and also the element itself that is located at that index. Check out the documentation for more info.
$(document).ready(function() {
safari.application.addEventListener("command", performCommand, false);
console.log("Document is ready to go!");
});
function performCommand(event) {
console.log("event recieved");
if (event.command == "open-designs") {
console.log("got 'open-designs' event");
$('a[href*="/Create/DesignProduct.aspx?"]').each(function(index,elem) {
console.log("opening window", index, elem);
window.open($(elem).attr('href'),'_blank');
});
}
}
You use the $(document).ready() callback as an indication that your DOM is ready and jQuery has been initialized. Once you know everything is ready, you can setup your event listener.
The function performCommand() can not be called before the listener is added (unless there is some other reference to it).

Related

Javascript Function Not Being Called after dynamic loading

I have a weird issue that i am hoping someone can help resolve.
Problem
When i load html dynamically via .load() function, if any aspect of html in the loaded fragment tries to access the javascript query functions in original HTML page, it doesn't work. Example code below:
Main HTML page (main.html)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head lang="en">
<!--javascript load functions etc... standard header stuff -->
</head>
<body>
<div id="dynamic_section_fragment"></div>
Load Fragment
<script type="text/javascript">
// <![CDATA[
function loadFragment() {
$("#dynamic_section_fragment").load("/api/fragment/");
};
$(".checkvalue").click(function () {
$.getJSON("/api/checkvalue", {term: $(this).attr('value')}, function () {
console.info("submitted for checking");
})
});
// ]]>
</script>
</body>
</html>
FRAGMENT File (fragment.html)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head lang="en">
</head>
<body>
<div th:fragment="check_value">
<br/>
Check the value in the attribute field
<br/>
<a href="javascript:" th:attr="value='123'" class="checkvalue">Check This<a/>
</div>
</body>
</html>
SPRING MVC Controller Method
#RequestMapping("/api/checkvalue")
public String getFragment(Model model) {
return "fragment :: check_value";
}
So a run down of actions:
-Main.html page loads
-User clicks on Load Fragment hyperlink
-Javascript dynamically loads the relevant fragment into the div
-User clicks on Check This hyperlink, nothing happens
Is there something i am missing or something i need to be aware?
It is as if Thymeleaf has preregistered all the possible scenarios of events and doesn't allow any others.
Only way i have been able to get it to work is by injecting the "checkvalue" javascript within the fragment, which as you can agree is a bad way of doing things.
Help is appreciated.
You are applying the click event listener to all existing objects with the checkvalue class.
$(".checkvalue").click(function ()
What you rather wish to do (to make the click event apply to all the existing and any new added, dynamically) is to set a event on a parent in the dom tree (parent both to the existing and to all that will be added).
In your case, the body tag would probably be the safe bet.
The following should suffice:
$('body').on('click', '.checkvalue', function() { ...
Simplified, the code will apply a listener on the body element instead of the .checkvalue objects, and whenever a object with the .checkvalue class is clicked (wether dynamically or statically loaded), the event will fire.
edit
I would also suggest that you, in your javascript, don't use jquery before you know for certain that it is loaded.
The jquery lib have a way of fixing this for you, by using the $( document ).ready() function:
$( document ).ready(function() {
// All jquery dependant code here.
});

2 versions of jQuery -> 2 documents. why?

I have a small page:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title></title>
<script type="text/javascript" src="jquery-1.4.2.js"></script>
<script type="text/javascript" src="temp.js"></script>
</head>
<body>
<p>foo</p>
<p>bar</p>
</body>
</html>
and I'm trying to load two different versions of jQuery:
// temp.js
jQueryScriptOutputted = false;
initJQuery = function() {
//if the jQuery object isn't available
if (typeof(myjQuery) == 'undefined') {
if (!jQueryScriptOutputted) {
//only output the script once..
jQueryScriptOutputted = true;
//output the script (load it from google api)
document.write("<script type=\"text/javascript\" src=\"jquery-1.6.4.js\"></script>");
document.write("<script type=\"text/javascript\">var myjQuery = $.noConflict(true);</script>");
}
setTimeout("initJQuery()", 50);
} else {
myjQuery(function() {
// Check jQuery versions
console.log('myjQuery version = ' + myjQuery().jquery);
console.log('$ version = ' + $().jquery);
console.log('jQuery version = ' + jQuery().jquery);
// Get the data of the actual poll
document.write("Where is foo and bar?!?");
});
}
}
initJQuery();
but it seems that this loads two different documents. I mean, when you open the page, the paragraphs get lost. How come?!?
Calling document.write after the page has loaded will overwrite the entire page with the document.write parameter. Consider using something else like $().append or $().html to change the markup.
i.e.
myjQuery(function() {
$('body').append("<p>Where is foo and bar?!?</p>");
});
You must only load one version or the other.
In other words, only have one jquery library installed to the page.
The problem is that you are writing the <script> tags to the document and not the <head>
Please see these instructions for full information on how to dynamically load jQuery.
The tutorial explains how to do it really well.
Hope this helps.

Cannot bind click() event to HTML control

Here's my setup. I'm using .NET:
I have a Main.aspx lets call it. That page inherits a master page and the Master Page as usual includes the jQuery library and other includes that we use for jQuery that are global in scope
In Main.aspx is an HTML plain vanilla IFrame:
In that IFrame is another .aspx page. Lets call it for all tense and purposes Sub.aspx
In Sub.aspx I've got the following code:
<!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 id="Head1" runat="server">
<link href="Content/Styles/Site.css" rel="stylesheet" type="text/css" />
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div id="facebookPhotos-iFrameContent">
<div>
<p id="buttoTestContainer">
<input type="image" id="btnLogin" src="images/loginBtn.jpg" />
</p>
</div>
</div>
</form>
<script type="text/javascript">
var loginButtonID = 'btnLogin';
//alert(loginButtonID);
window.fbAsyncInit = function () {
// Initialize/load the JS SDK
// cookie is set to true to activate JS SDK cookie creation & management
FB.init({ appId: facebookApplicationID, status: true, cookie: true, xfbml: false });
alert("got here");
// also handles the case when they are already logged in
$('#' + loginButtonID).click(function () {
alert("login button was fired");
TestLogin();
});
//...rest of code
});
The problem:
When trying to debug to make sure that .click() event gets called so it binds to my control, I never get to the first alert "got here" so that I know the JS was called at least up to that point. So not sure why. I see absolutely no JS errors in my FireBug console either.
Your function never executes. Remove the window.fbAsyncInit = function() { } and the code will run as interpreted. Or, use $(document).ready(function() { }); to execute it after the DOM is ready.
Also, the Javascript libraries in the parent frame are not inherited by the child. But you can reference them like parent.fbAsyncInit = function() { } or parent.jQuery(); for example.
resolved. That init function should be the only thing in there. Moved all other code outside the window.fbAsyncInit because I do not want to load the others asynchronously, I want to load it after the DOM has completed. The only thing that should be loaded at the same time is the registering/Initialization of that SDK.

JavaScript: trouble assigning event handlers

I'm not sure what I'm doing wrong here:
index.html
<?xml version="1.0" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "XHTML1-s.dtd" >
<html xmlns="http://www.w3.org/TR/1999/REC-html-in-xml" xml:lang="en" lang="en" >
<head>
<script type="text/javascript" src="scripts/eventInit.js"></script>
</head>
<body>
<p id="javascriptWarning">This page will not work with JavaScript disabled.</p>
</body>
</html>
eventInit.js
window.onload = function () {
alert("check"); // works
var jsWarning = document.getElementById("javascriptWarning");
jsWarning.onclick = function () {
alert("hi"); // works
};
jsWarning.onload = function () {
alert("loaded"); // fails
};
}
And yet, nothing happens. What am I doing wrong? I've tried other events, like onmouseover and onload.
I'm doing this in Visual Studio, and intellisense isn't giving me options for setting any event handlers. Is that because I'm doing this wrong?
I have confirmed that JS is working on my setup; just putting alert("hi") in a script and including it does work.
It might be important to note that I'm doing this in JScript, since I'm using Visual Studio 2010, so perhaps event handling is different?
Updated to remove '-' from the ID name, but it still doesn't work.
Updated added the window.onload block. Now onclick works, but onload doesn't.
You are trying to set a load event on a paragraph. Only objects which load external data (window, frame, iframe, img, script, etc) have a load event.
Some JS libraries implement an available event (such as YUI) — but you know the paragraph is available, since you're setting an event on it, and you couldn't do that if it was unavailable.
maybe you forgot to have the code block inside a
window.onload = function() {
// btn click code here
}
You have to wait for the document to be parsed before you can go looking for elements by "id" value. Put your event handling setup into an "onload" function on the window object.
The browser won't fire an "onload" event on your <p> tag. You won't need that anyway if you do your work in the "onload" handler for the window as a whole.
[soapbox] Use a framework.
The script is executed before the desired element exists. Additionally, I don't think, p has an onload-Event. Windows, frames and images, yes, but paragraphs?
You should use <body onload="init();"> or window.onload=function(){ … } or a library function, if you use a library. Example:
index.html
<?xml version="1.0" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "XHTML1-s.dtd" >
<html xmlns="http://www.w3.org/TR/1999/REC-html-in-xml" xml:lang="en" lang="en" >
<head>
<script type="text/javascript" src="scripts/eventInit.js"></script>
</head>
<body>
<p id="javascriptWarning">This page will not work with JavaScript disabled.</p>
</body>
</html>
scripts/eventInit.js
window.onload=function(){
alert('JS is working!');}
Edit: Okay, I am very sure, p makes no use of an onload event handler. And it's no wonder, you don't need it. If you want to execute JS code just after the paragraph is finished, do this:
<p>
<!-- stuff -->
</p>
<script type="text/javascript">
/* stuff */
</script>
Instead of this:
jsWarning.onload = function () {
alert("loaded"); // fails
};
try this
if(jsWarning) alert("loaded");
I think someone above mentioned checking for the existence of the element. At this stage the element should be present but it does no harms to check for it.
I think you have to make sure your JavaScript is binding.
Is your javascript before or after your paragraph element, for some reason my brain is aiming towards that.
I would look into using something like jQuery, it will help.
using jQuery your code would be (with the relevant jQuery files included of course):
$(document).ready(function()
{
$("#javascript-warning").click(function(){
alert("HELLO");
});
});
I don't think hyphens are valid in class names when used in conjunction with JavaScript. Try an underscore instead.
onload is a window event.

Why does appending a <script> to a dynamically created <iframe> seem to run the script in the parent page?

I'm attempting to create an <iframe> using JavaScript, then append a <script> element to that <iframe>, which I want to run in the context of the <iframe>d document.
Unfortunately, it seems I'm doing something wrong - my JavaScript appears to execute successfully, but the context of the <script> is the parent page, not the <iframe>d document. I also get a 301 Error in Firebug's "Net" tab when the browser requests iframe_test.js, though it then requests it again (not sure why?) successfully.
This is the code I'm using (live demo at http://onespot.wsj.com/static/iframe_test.html):
iframe_test.html
<!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"/>
<title><iframe> test</title>
</head>
<body>
<div id="bucket"></div>
<script type="text/javascript" src="http://jqueryjs.googlecode.com/files/jquery-1.3.2.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$('#bucket').append('<iframe id="test"></iframe>');
setTimeout(function() {
var iframe_body = $('#test').contents().find('body');
iframe_body.append('<scr' + 'ipt type="text/javascript" src="http://onespot.wsj.com/static/iframe_test.js"></scr' + 'ipt>');
}, 100);
});
</script>
</body>
</html>
iframe_test.js
$(function() {
var test = '<p>Shouldn\'t this be inside the <iframe>?</p>';
$('body').append(test);
});
One thing that seems unusual is that the the code in iframe_test.js even works; I haven't loaded jQuery in the <iframe> itself, only in the parent document. That seems like a clue to me, but I can't figure out what it means.
Any ideas, suggestions, etc. would be much appreciated!
Had the same problem, took me hours to find the solution.
You just need to create the script's object using the iframe's document.
var myIframe = document.getElementById("myIframeId");
var script = myIframe.contentWindow.document.createElement("script");
script.type = "text/javascript";
script.src = src;
myIframe.contentWindow.document.body.appendChild(script);
Works like a charm!
I didn't find an answer to my original question, but I did find another approach that works even better (at least for my purposes).
This doesn't use jQuery on the parent page (which is actually a good thing, as I'd prefer not to load it there), but it does load jQuery in the <iframe> in an apparently completely valid and usable way. All I'm doing is writing over the <iframe>'s document object with a new one created from scratch. This allows me to simply include a <script> element in a string which I then write to the <iframe>'s document object.
The code:
<!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"/>
<title>frame</title>
</head>
<body>
<div id="test"></div>
<script type="text/javascript">
// create a new <iframe> element
var iframe = document.createElement('iframe');
// append the new element to the <div id="bucket"></div>
var bucket = document.getElementById('test');
bucket.appendChild(iframe);
// create a string to use as a new document object
var val = '<scr' + 'ipt type="text/javascript" src="http://jqueryjs.googlecode.com/files/jquery-1.3.2.min.js"></scr' + 'ipt>';
val += '<scr' + 'ipt type="text/javascript"> $(function() { $("body").append("<h1>It works!</h1>"); }); </scr' + 'ipt>';
// get a handle on the <iframe>d document (in a cross-browser way)
var doc = iframe.contentWindow || iframe.contentDocument;
if (doc.document) {
doc = doc.document;
}
// open, write content to, and close the document
doc.open();
doc.write(val);
doc.close();
</script>
</body>
</html>
I hope this helps someone down the road!
The answer to the original question is simple - the execution of the script is done by jquery, and since jquery is loaded in the top frame, this is where the script runs too, no matter where you are appending it. A smarter implementation of jquery can no doubt be made to use the correct window object, but for now things are how they are.
As to the workarounds, you already have two good answers (even if one is your own). What I might add is that you can use one of those workarounds to include jquery.js in the iframe, and then get that jquery object instead of the top one to insert your additional markup... but that may very well be overkill too.

Categories