javascript revealing module pattern and jquery - javascript

I'm trying to up my js foo and start to use the module patter more but I'm struggling.
I have a main page with a jquery-ui element that pops up a dialog that loads an ajax requested page for data entry. The below code is contained within the popup ajax page.
After the pop up is loaded the Chrome console is able to see and execute ProtoSCRD.testing() just fine. If I try to run that in the jQuery.ready block on the page, I get:
Uncaught ReferenceError: ProtoSCRD is not defined
Yet i can execute toggleTypeVisable() in the ready block and life is good. Can anyone shed some light?
$(document).ready(function() {
setHoodStyleState();
$('#hood-style').change(function(){
hstyle = $('#hood-style').val();
if ( hstyle.indexOf('Custom') != -1) {
alert('Custom hood style requires an upload drawing for clarity.');
}
setHoodStyleState();
});
setCapsState();
$('#caps').change(function(){
setCapsState();
});
setCustomReturnVisibility();
$('#return').change(function(){ setCustomReturnVisibility(); });
toggleTypeVisable();
$('#rd_type').change(function(){
toggleTypeVisable();
});
ProtoSCRD.testing();
});
function toggleTypeVisable(){
if ( $('#rd_type').val() == 'Bracket' ) {
$('.endcap-ctl').hide();
$('.bracket-ctl').show();
}
if ( $('#rd_type').val() == 'Endcap' ) {
$('.bracket-ctl').hide();
$('.endcap-ctl').show();
}
if ( $('#rd_type').val() == 'Select One' ) {
$('.bracket-ctl').hide();
$('.endcap-ctl').hide();
}
}
ProtoSCRD = (function($, w, undefined) {
testing = function(){
alert('testing');
return '';
}
getDom = function(){
return $('#prd-order-lines-cnt');
}
return {
testing: testing,
getDom: getDom
};
}(jQuery, window));
calling the popup dialog like so - which is in fact in another ready in a diff file on the parent page:
// enable prototype button
$( "#proto-btn" ).click(function(e){
e.preventDefault();
showPrototype();
});

I don't know if it will solve your problems at all, but you are definitely missing several var statements you really should have:
var ProtoSCRD = (function($, w, undefined) {
var testing = function(){
alert('testing');
return '';
};
var getDom = function(){
return $('#prd-order-lines-cnt');
};
return {
testing: testing,
getDom: getDom
};
}(jQuery, window));
IMHO, it's best practice to use var for every variable you declare. (Function declarations do so implicitly.)
But I really don't know if this will help solve anything. But it should store everything in its proper scope.
Update
Here's one possible issue: if the document is already ready (say this is loading at the end of the body), then perhaps jQuery is running this synchronously. Have you tried moving the definition of ProtoSCRD above the document.ready block?

Related

Issue with variables scope in Js/Jquery

Having issue with - what I supposed to be - basics scope fundamentals.
I am using Javascript with Require JS structure, here are where the issue occurs:
function GameManager() {
//This is the constructor of gamemanager.js file
this.body = $('body');
this.game = $('#game');
}
GameManager.prototype.createGame = function() {
//Code
//this line works
this.body.append(//Some HTML);
}
GameManager.prototype.showGame = function() {
//Code
//this line does not work wtf
this.game.removeClass("display-none");
//and this one does work.
$("#game").removeClass("display-none");
}
I am using this.body succefully so I want to use the same way for this.game but it doesnt work. I can manage to make it work by using directly $("#game") but it's making jquery running through the DOM everytime so not really optimized...
I certainly am missing some basics points here, can someone explain ?
Thanks
This is working for me. I can remove display-none class from div without a problem. It wont display however. You need to change css. this.body.css("display","block"); for example.
window.onload = function() {
function GameManager() {
//This is the constructor of gamemanager.js file
this.body = $('body');
this.game = $('#game');
}
GameManager.prototype.createGame = function() {
//Code
//this line works
this.body.append();
}
GameManager.prototype.showGame = function() {
//Code
this.body.css("display", "block");
//this line does not work wtf
this.game.removeClass("display-none");
//and this one does work.
//$("#game").removeClass("display-none");
}
var myGame = new GameManager();
myGame.showGame();
}

Waiting for multiple iFrames to load before executing function

Forgive my naivety, this probably is quite obvious, I just can't see it now.
Please tell me what is wrong with the following code:
$('#iframe1').load(function(){
$('#iframe2').load(function(){
alert('loaded!');
});
});
The idea is to wait until both iframes have fully loaded, then alert "loaded" - of course this is a simplified example for the sake of stack.
The script sits in script tags at the end of the body of the html doc.
#Quertiy answer is perfectly fine, but not very jQuery-ish. It is hard-coded for 2 iframes only.
The beauty of jQuery is that you can make it work for the most number of people, with as little friction as possible.
I've advised a very simplistic plugin that does nearly what is present on that answer, but in a more open way. It not only works on iframes, but also on images, audio, video and whatever has a onload event!
Without further due, here's the code:
(function($){
$.fn.extend({allLoaded: function(fn){
if(!(fn instanceof Function))
{
throw new TypeError('fn must be a function');
}
var $elems = this;
var waiting = this.length;
var handler = function(){
--waiting;
if(!waiting)
{
setTimeout(fn.bind(window), 4);
}
};
return $elems.one('load.allLoaded', handler);
}});
})(window.jQuery);
It works by adding a load handler to every element in that selection. Since it is a plugin, you can use in whatever way you decide to use it.
Here's an example, that loads 30 random images:
//plugin code
(function($){
$.fn.extend({allLoaded: function(fn){
if(!(fn instanceof Function))
{
throw new TypeError('fn must be a function');
}
var $elems = this;
var waiting = this.length;
var handler = function(){
--waiting;
if(!waiting)
{
setTimeout(fn.bind(window), 4);
}
};
return $elems.one('load.allLoaded', handler);
}});
})(window.jQuery);
$(function(){
//generates the code for the 30 images
for(var i = 0, html = ''; i < 30; i++)
html += '<img data-src="http://lorempixel.com/g/400/200/?_=' + Math.random() + '">';
//stuffs the code into the body
$('#imgs').html(html);
//we select all images now
$('img')
.allLoaded(function(){
//runs when done
alert('loaded all')
})
.each(function(){
//the image URL is on a `data` attribute, to delay the loading
this.src = this.getAttribute('data-src')
})
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.0/jquery.min.js"></script>
<div id="imgs"></div>
Your problem, as said before many times, is that you have a load event attached to your iframe. That event is fired everytime the content change.
After that, you set a new event on #iframe2. When it's content changes, it will fire events left and right, above and beyound what you wish!
The best aproach is to keep track of which ones you loaded or not. After all have been loaded, you simply run the function.
The problem is that you're waiting until #iframe1 loads before you attach a handler for #iframe2 loading. So if #iframe2 loads first, you'll never get your callback.
Instead, watch the load event on both of them and track which ones you've seen:
var seen1 = false,
seen2 = false;
$('#iframe1, #iframe2').load(function(){
if (this.id == "iframe1") {
seen1 = true;
} else {
seen2 = true;
}
if (seen1 && seen2) {
alert('loaded!');
}
});
Why do you expect 2nd iframe to load after the first one?
~function () {
var loaded = 0;
$('#iframe1, #iframe2').load(function (){
if (++loaded === 2) {
alert('loaded!');
}
});
}()

Backbone.Marionette extending region stops onClose() function from calling

I've created a Backbone, Marionette and Require.js application and am now trying to add smooth transitioning between regions.
To do this easily* ive decided to extend the marionette code so it works across all my pages (theres a lot of pages so doing it manually would be too much)
Im extending the marionette.region open and close function. Problem is that it now doesnt call the onClose function inside each of my views.
If I add the code directly to the marionette file it works fine. So I'm probably merging the functions incorrectly, right?
Here is my code:
extendMarrionette: function () {
_.extend(Marionette.Region.prototype, {
open : function (view) {
var that = this;
// if this is the main content and should transition
if (this.$el.attr("id") === "wrapper" && document.wrapperIsHidden === true) {
this.$el.empty().append(view.el);
$(document).trigger("WrapperContentChanged")
} else if (this.$el.attr("id") === "wrapper" && document.wrapperIsHidden === false) {
$(document).on("WrapperIsHidden:open", function () {
//swap content
that.$el.empty().append(view.el);
//tell router to transition in
$(document).trigger("WrapperContentChanged");
//remove this event listener
$(document).off("WrapperIsHidden:open", that);
});
} else {
this.$el.empty().append(view.el);
}
},
//A new function Ive added - was originally inside the close function below. Now the close function calls this function.
kill : function (that) {
var view = this.currentView;
$(document).off("WrapperIsHidden:close", that)
if (!view || view.isClosed) {
return;
}
// call 'close' or 'remove', depending on which is found
if (view.close) {
view.close();
}
else if (view.remove) {
view.remove();
}
Marionette.triggerMethod.call(that, "close", view);
delete this.currentView;
},
// Close the current view, if there is one. If there is no
// current view, it does nothing and returns immediately.
close : function () {
var view = this.currentView;
var that = this;
if (!view || view.isClosed) {
return;
}
if (this.$el.attr("id") === "wrapper" && document.wrapperIsHidden === true) {
this.kill(this);
} else if (this.$el.attr("id") === "wrapper" && document.wrapperIsHidden === false) {
//Browser bug fix - needs set time out
setTimeout(function () {
$(document).on("WrapperIsHidden:close", that.kill(that));
}, 10)
} else {
this.kill(this);
}
}
});
}
Why don't you extend the Marionette.Region? That way you can choose between using your custom Region class, or the original one if you don't need the smooth transition in all cases. (And you can always extend it again if you need some specific behavior for some specific case).
https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.region.md#region-class
var MyRegion = Marionette.Region.extend({
open: function() {
//Your open function
}
kill: function() {
//Your kill function
}
close: function() {
//Your close function
}
});
App.addRegions({
navigationRegion: MyRegion
});
Perhaps your issue is that you are not passing a function to your event listener, but instead calling the code directly in the code below.
setTimeout(function(){
$(document).on("WrapperIsHidden:close", that.kill(that));
}, 10)
It is likely that you want something like this:
setTimeout(function(){
$(document).on("WrapperIsHidden:close", function (){ that.kill(that); });
}, 10)
Another possible problem is that you are mixing up your references to this/that in your kill function. It seems like you probably want var view to either be assigned to that.view or to use this rather than that throughout the method.
Answer to your additional problems:
You should try passing the view variable from the close function directly into your kill function because the reference to currentView is already changed to the new view object when you actually want to old view object. The reason this is happening is that you are setting a timeout before executing the kill function. You can see this if you look at the show source code. It expects close, open and then currentView assignment to happen synchronously in order.

GetElementById of ASP.NET Control keeps returning 'null'

I'm desperate having spent well over an hour trying to troubleshoot this. I am trying to access a node in the DOM which is created from an ASP.NET control. I'm using exactly the same id and I can see that they match up when looking at the HTML source code after the page has rendered. Here's my [MODIFIED according to suggestions, but still not working] code:
ASP.NET Header
<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
<script type="text/javascript">
$(document).ready(
var el = document.getElementById('<%= txtBox.ClientID %>');
el.onchange = alert('test!!');
)
</script>
</asp:Content>
ASP.NET Body
<asp:TextBox ID="txtBox" runat="server"></asp:TextBox>
Resulting Javascript & HTML from above
<script type="text/javascript">
$(document).ready(
var el = document.getElementById('MainContent_txtBox');
el.onchange = alert('test!!');
)
</script>
...
<textarea name="ctl00$MainContent$txtBox" id="MainContent_txtBox"></textarea>
I can only assume that the script is loading before the control id has been resolved, yet when I look at the timeline with Chrome's "Inspect Element" feature, it appears that is not the case. When I created a regular textarea box to test and implement the identical code (different id of course), the alert box fires.
What on earth am I missing here? This is driving me crazy >.<
EDIT: Wierd code that works, but only on the initial page load; firing onload rather than onchange. Even jQuery says that .ready doesn't work properly apparently. Ugh!!
$(document).ready(function() {
document.getElementById('<%= txtBox.ClientID %>').onchange = alert('WORKING!');
})
Assuming the rendered markup does appear in that order, the problem is that the element doesn't yet exist at the time your JavaScript is attempting to locate it.
Either move that JS below the element (preferably right at the end of the body) or wrap it in something like jQuery's document ready event handler.
Update:
In response to your edits, you're almost there but (as others have mentioned) you need to assign a function to the onchange event, not the return result of alert(). Something like this:
$(document).ready(function() {
// Might as well use jQuery to attach the event since you're already using
// it for the document ready event.
$('#<%= txtBox.ClientID %>').change(function() {
alert('Working!');
});
});
By writing onchange = alert('Working');, you were asking JavaScript to assign the result of the alert() method to the onchange property. That's why it was executing it immediately on page load, but never actually in response to the onchange event (because you hadn't assigned that a function to run onchange).
Pick up jQuery.
Then you can
$(function()
{
var el = document.getElementById('<%= txtBox.ClientID %>');
el.onclick() { alert('test!!'); }
});
Other answers have pointed out the error (attempting to access DOM nodes before they are in the document), I'll just point out alternative solutions.
Simple method
Add the script element in the HTML below the closing tag of the element you wish to access. In its easiest form, put it just before the closing body tag. This strategy can also make the page appear faster as the browser doesn't pause loading HTML for script. Overall load time is the same however, scripts still have to be loaded an executed, it's just that this order makes it seem faseter to the user.
Use window.onload or <body onload="..." ...>
This method is supported by every browser, but it fires after all content is loaded so the page may appear inactive for a short time (or perhaps a long time if loading is dealyed). It is very robust though.
Use a DOM ready function
Others have suggested jQuery, but you may not want 4,000 lines and 90kb of code just for a DOM ready function. jQuery's is quite convoluted so hard to remove from the library. David Mark's MyLibrary however is very modular and quite easy to extract just the bits you want. The code quality is also excellent, at least the equal of any other library.
Here is an example of a DOM ready function extracted from MyLibrary:
var API = API || {};
(function(global) {
var doc = (typeof global.document == 'object')? global.document : null;
var attachDocumentReadyListener, bReady, documentReady,
documentReadyListener, readyListeners = [];
var canAddDocumentReadyListener, canAddWindowLoadListener,
canAttachWindowLoadListener;
if (doc) {
canAddDocumentReadyListener = !!doc.addEventListener;
canAddWindowLoadListener = !!global.addEventListener;
canAttachWindowLoadListener = !!global.attachEvent;
bReady = false;
documentReady = function() { return bReady; };
documentReadyListener = function(e) {
if (!bReady) {
bReady = true;
var i = readyListeners.length;
var m = i - 1;
// NOTE: e may be undefined (not always called by event handler)
while (i--) { readyListeners[m - i](e); }
}
};
attachDocumentReadyListener = function(fn, docNode) {
docNode = docNode || global.document;
if (docNode == global.document) {
if (!readyListeners.length) {
if (canAddDocumentReadyListener) {
docNode.addEventListener('DOMContentLoaded',
documentReadyListener, false);
}
if (canAddWindowLoadListener) {
global.addEventListener('load', documentReadyListener, false);
}
else if (canAttachWindowLoadListener) {
global.attachEvent('onload', documentReadyListener);
} else {
var oldOnLoad = global.onload;
global.onload = function(e) {
if (oldOnLoad) {
oldOnLoad(e);
}
documentReadyListener();
};
}
}
readyListeners[readyListeners.length] = fn;
return true;
}
// NOTE: no special handling for other documents
// It might be useful to add additional queues for frames/objects
else {
if (canAddDocumentReadyListener) {
docNode.addEventListener('DOMContentLoaded', fn, false);
return true;
}
return false;
}
};
API.documentReady = documentReady;
API.documentReadyListener = documentReadyListener;
API.attachDocumentReadyListener = attachDocumentReadyListener;
}
}(this));
Using it for your case:
function someFn() {
var el = document.getElementById('MainContent_txtBox');
el.onclick = function() { alert('test!!');
}
API.attachDocumentReadyListener(someFn);
or an anonymous function can be supplied:
API.attachDocumentReadyListener(function(){
var el = document.getElementById('MainContent_txtBox');
el.onclick = function() { alert('test!!');
};
Very simple DOM ready functions can be done in 10 lines of code if you just want one for a specific case, but of course they are less robust and not as reusable.

How to use jQuery in Firefox Extension

I want to use jQuery inside a firefox extension,
I imported the library in the xul file like this:
<script type="application/x-javascript" src="chrome://myExtension/content/jquery.js"> </script>
but the $() function is not recognized in the xul file neither do the jQuery().
I googled about the problem and found some solutions but no one did work with me:
http://gluei.com/blog/view/using-jquery-inside-your-firefox-extension
http://forums.mozillazine.org/viewtopic.php?f=19&t=989465
I've also tried to pass the 'content.document' object(which refrences the 'document' object) as the context parameter to the jQuery function like this:
$('img',content.document);
but still not working,
does any one came across this problem before?
I use the following example.xul:
<?xml version="1.0"?>
<overlay id="example" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<head></head>
<script type="application/x-javascript" src="jquery.js"></script>
<script type="application/x-javascript" src="example.js"></script>
</overlay>
And here is an example.js
(function() {
jQuery.noConflict();
$ = function(selector,context) {
return new jQuery.fn.init(selector,context||example.doc);
};
$.fn = $.prototype = jQuery.fn;
example = new function(){};
example.log = function() {
Firebug.Console.logFormatted(arguments,null,"log");
};
example.run = function(doc,aEvent) {
// Check for website
if (!doc.location.href.match(/^http:\/\/(.*\.)?stackoverflow\.com(\/.*)?$/i))
return;
// Check if already loaded
if (doc.getElementById("plugin-example")) return;
// Setup
this.win = aEvent.target.defaultView.wrappedJSObject;
this.doc = doc;
// Hello World
this.main = main = $('<div id="plugin-example">').appendTo(doc.body).html('Example Loaded!');
main.css({
background:'#FFF',color:'#000',position:'absolute',top:0,left:0,padding:8
});
main.html(main.html() + ' - jQuery <b>' + $.fn.jquery + '</b>');
};
// Bind Plugin
var delay = function(aEvent) {
var doc = aEvent.originalTarget; setTimeout(function() {
example.run(doc,aEvent);
}, 1);
};
var load = function() {
gBrowser.addEventListener("DOMContentLoaded", delay, true);
};
window.addEventListener("pageshow", load, false);
})();
The following solution makes it possibile to use jQuery in contentScriptFile
(Targetting 1.5 Addon-sdk)
In your main.js:
exports.main = function() {
var pageMod = require("page-mod");
pageMod.PageMod({
include: "*",
contentScriptWhen: 'end',
contentScriptFile: [data.url("jquery-1.7.1-min.js") , data.url("notifier.js") , data.url("message.js")],
onAttach: function onAttach(worker) {
//show the message
worker.postMessage("Hello World");
}
});
};
In your message.js :
self.on("message", function(message){
if(message !== "undefined"){
Notifier.info(message);
}
});
Some pitfalls you need to watchs out for:
The order of the contentScriptFile array. if message.js would be placed first: jQuery won't be reconized.
Do not place a http:// url in the data.url (this does not work)!
All your javascript files should be in the data folder. (only main.js should be in lib folder)
There is an excellent article in the mozillaZine forums that describes this step-by-step: http://forums.mozillazine.org/viewtopic.php?f=19&t=2105087
I haven't tried it yet, though so I hesitate to duplicate the info here.
Turns out the current top-answer by #sunsean does not work as expected when it comes to handling multiple loads. The function should properly close over the document and avoid global state.
Also, you have to call jQuery.noConflict(true) to really avoid conflicts with other add-ons!
This is who I would write it (then again, I would avoid jquery (in add-ons) like the plague...).
First the overlay XUL
<?xml version="1.0"?>
<overlay id="test-addon-overlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="text/javascript" src="jquery.js"/>
<script type="text/javascript" src="overlay.js"/>
</overlay>
And then the overlay script:
// Use strict mode in particular to avoid implicitly var declarations
(function() {
"use strict";
// Main runner function for each content window.
// Similar to SDK page-mod, but without the security boundaries.
function run(window, document) {
// jquery setup. per https://stackoverflow.com/a/496970/484441
$ = function(selector,context) {
return new jq.fn.init(selector,context || document);
};
$.fn = $.prototype = jq.fn;
if (document.getElementById("my-example-addon-container")) {
return;
}
let main = $('<div id="my-example-addon-container">');
main.appendTo(document.body).text('Example Loaded!');
main.click(function() { //<--- added this function
main.text(document.location.href);
});
main.css({
background:'#FFF',color:'#000',position:'absolute',top:0,left:0,padding:8
});
};
const log = Components.utils.reportError.bind(Components.utils);
// Do not conflict with other add-ons using jquery.
const jq = jQuery.noConflict(true);
gBrowser.addEventListener("DOMContentLoaded", function load(evt) {
try {
// Call run with this == window ;)
let doc = evt.target.ownerDocument || evt.target;
if (!doc.location.href.startsWith("http")) {
// Do not even attempt to interact with non-http(s)? sites.
return;
}
run.call(doc.defaultView, doc.defaultView, doc);
}
catch (ex) {
log(ex);
}
}, true);
})();
Here is a complete add-on as a gist. Just drop in a copy of jquery and it should be good to go.
I think this is what Eric was saying, but you can load Javascript from the URL directly.
javascript:var%20s=document.createElement('script');s.setAttribute('src','http://YOURJAVASCRIPTFILE.js');document.getElementsByTagName('body')[0].appendChild(s);void(s);
Im assuming you want your extension to load JQuery so you can manipulate the page elements easily? My company's labs has something that does this using Javascript directly here: http://parkerfox.co.uk/labs/pixelperfect
It may be bad practice, but have you considered including it inline?
Instead of
$('img',content.document);
you can try
$('img',window.content.document);
In my case it works.

Categories

Resources