javascript revealing module pattern with multiple files/modules and global vars - javascript

I apologize if this has been asked elsewhere, but all the info I've seen doesn't really help me understand both the answer and best practice.
I'm attempting to use the revealing module pattern for javascript. I have a base js file that contains methods and properties that are needed on every page of the application.
Then each page has its own js file that has it's own unique functions and variables, but also needs to reference the variables in the base js file.
I seem to be really misunderstanding variable scope, or how I'm setting this up is somehow very bad, because I'm getting confusing results.
Questions:
If i'm structuring this wrong, what is the right way?
How can I access variables correctly across modules?
Am I correct to initialize the modules on doc ready?
What I'm doing right now is this:
var base = (function() {
init = function() {
url = $(location).attr('href');
url = url.substr(-1) === '/' ? url.slice(0, -1) : url;
segments = url.split('/');
baseUrl = this.segments[0] + '//' + this.segments[2] + '/';
resource = this.segments[3];
this.segments = segments; //need access to this across all pages
this.resource = resource; //need access to this across all pages
this.baseUrl = baseUrl; //need access to this across all pages
}
}
doStuffGlobally = function() {
//do stuff for all pages
}
return {
init: init,
baseUrl: this.baseUrl,
segments: this.segments,
resource: this.resource
};
}());
$(document).ready(function() {
base.init();
}
And on another Page I'm doing this:
var otherPage = (function() {
init = function() {
//do otherPage stuff;
}
}
doStuff = function() {
base.doStuffGlobally(); //need to access this.
console.log(base.segments); //need to access this.
}
return {
init: init
};
}());
$(document).ready(function() {
otherPage.init();
}

You don't need an init method for your base module, it doesn't need to wait for domready but can (and should) run immediately. So write
var base = (function() {
var url = $(location).attr('href');
if (url.substr(-1) === '/') url = url.slice(0, -1);
var segments = url.split('/');
var baseUrl = this.segments[0] + '//' + this.segments[2] + '/';
var resource = this.segments[3];
function doStuffGlobally() {
//do stuff for all pages
}
return {
baseUrl,
segments,
resource,
doStuffGlobal,
};
}());

Related

Pass custom variables to onResourceRequested with PhantomJS

In order to avoid regressions with our analytics tagging, I want to use PhantomJS to automate testing for Adobe Analytics tags.
To do so I want to be able to test 2 things:
Presence of certain js variables declarations in the HTML source
Compare the variables in HTML source with Ajax calls made to Adobe Analytics and ensure they have the same values
Exemple of variable in the HTML source I want to spy on:
<script>s.events="event27";</script>
Here is the js test script I have so far:
"use strict";
if (!String.prototype.contains) {
String.prototype.contains = function (arg) {
return !!~this.indexOf(arg);
};
}
var page = require('webpage').create();
page.onResourceRequested = function(request) {
var obj = JSON.stringify(request, undefined, 4);
var needle = '2o7';
var url = request.url;
if (url.contains(needle)) {
var calledUrl = decodeURI(decodeURIComponent(url));
console.log(calledUrl);
}
};
page.onResourceReceived = function(response) {
var jsonResponse = JSON.stringify(response, undefined, 4);
};
page.open('http://www.domain.com/page.html', function() {
var s_account = page.evaluate(function () {
return window.s_account;
});
var s_events = page.evaluate(function () {
return window.s.events;
});
phantom.exit();
});
I would like to be able to pass s_account and s_events variables to the onResourceRequested function so that I can assert equality between these 2 variables and _GET params in calledUrl var.
But I can not figure out how to do so. Any help would be appreciated!
I found the way to do it.
I actually get the content of window.s_account from within the onResourceRequested callback:
page.onResourceRequested = function(request) {
var obj = JSON.stringify(request, undefined, 4);
var needle = '2o7';
var url = request.url;
var result = false;
if (url.contains(needle)) {
var s_account = page.evaluate(function () {
return window.s_account;
});
result = true;
var calledUrl = decodeURI(decodeURIComponent(url));
console.log(s_account);
}
};
It might not be the most elegant way to do it however but it works.

Access a javascript variable in another js file in backbone.js

I do have a HomeRoute.js file in application which has BackBone.Model.extend implementation . I'm calling an ajax in the render method of this HomeRoute.js which provides me the session json. Now from this json i want to extract an variable sessionId and i want to use this sessionId variable in another HeaderView.js file.
I researched a lot on this but couldn't find any way to pass this variable to the HeaderView.js
HomeRoute.js code
var HomeRoute = BackBone.Model.extend({
render: function() {
this.createSession("userId","tenantId","orgId","accountNumb");
},
createSession : function(userId, tenantId, orgUnit, userAccountNum) {
var self = this;
var showHtml = function(data){
if(data.status == "valid"){
var sessionId = data.session.sessionId;// This variable to be used in another js file
self.displayHtml(self, sessionId);
}else{
// To be implemented Later
}
}
$.ajax({
type : 'post',
url : "url",
success : showHtml,
error : function(){
alert("An Error Occurred while creating session");
}
});
},
Any lead would be very much helpful to me.
There are quite a few ways of doing this.
I'd just create a variable outside of HomeRoute (in the same file) called sessionId (right now you only have that variable in the showHtml function, within the HomeRoute object; move it much higher in the scope). Any variable at that very top scope can be seen by other .js files included after this one, including your HeaderView.js file.
For example:
var sessionId;
var HomeRoute = BackBone.Model.extend({
render: function() {
this.createSession("userId","tenantId","orgId","accountNumb");
},
createSession : function(userId, tenantId, orgUnit, userAccountNum) {
var self = this;
var showHtml = function(data){
if(data.status == "valid"){
sessionId = data.session.sessionId;// This variable to be used in another js file
self.displayHtml(self, sessionId);
}else{
// To be implemented Later
}
}
$.ajax({
type : 'post',
url : "url",
success : showHtml,
error : function(){
alert("An Error Occurred while creating session");
}
});
},
Then in the other file (HeaderView.js, included -after- HomeRoute.js), you can simply access that variable:
alert(sessionId);
...assuming the post function has already returned, of course. (And also assuming that you can change a variable outside the scope of an object in JS, which I'm not positive about. If not, you're going to have to return the value from the instantiated HomeRoute object and assign it to sessionId.)
If sessionId is a critical part of HomeRoute, you can make it as part of the model and initialize it under the initializer:
var HomeRoute = BackBone.Model.extend({
initialize: function() {
this.sessionId = null;
},
...
}
and have it updated under 'showHtml':
var showHtml = function(data){
if(data.status == "valid"){
self.sessionId = data.session.sessionId;// This variable to be used in another js file
self.displayHtml(self, sessionId);
}else{
// To be implemented Later
}
}
You will need to export the model object in your view file to get access to sessionId
var _homeRoute = new HomeRoute ();
var _sid = _homeRoute.sessionId;

How to change request URI in ExtJS 4.1.0 Proxy or Reader

As it is, ExtJS 4.1 with a Rest proxy and a Json reader asks for a URI like this (urlencoded, though):
http://localhost:8000/api/v1/choice/?filter=[{"property":"question_id","value":2}]
my server wants filter requests to look like:
http://localhost:8000/api/v1/choice/?question_id=2
I've looked at the filterParam config for the proxy, but it doesn't seem relevant. Is there a practical way to achieve the request URI that the server needs?
Following ain't pretty, but it works. Now to fix the damn Store...
/**
* Customized to send ../?prop=val&prop2=val2 urls.
*/
buildUrl: function(request) {
var url = this.url;
var filters = eval(request.params['filter']);
if (filters) {
delete request.params['filter'];
url += '?'
for (var i = 0; i < filters.length; i++) {
var filterString = filters[i].property + "=" + filters[i].value;
if (url.slice(url.length-1) === '?') {
url += filterString;
} else {
url += '&' + filterstring;
}
}
};
return url;
},
There is no simple (easy) way. You will have to extend existing Proxy class. Take a look at the source code for Ext.data.proxy.Proxy and Ext.data.proxy.Server. Start with looking at functions getParams and buildUrl

backbone sync override, append url with query string?

I have some trouble appending a token to the backbone url query string and hope you guys could help me out here. Three things to know,
There is a rest api that expects a token with each request
An nginx backend that does auth, serves the backbone app + proxy req to the api under /api
i'm a new to javascript + backbone :/
The backbone app actually reads the token from a cookie and I need to append this to the request url everytime backbone makes a call. I see this can be done by overriding backbone sync. but it troubles me in a few different things. like, this is what I do
console.log('overriding backbone sync');
var key ="token";
Backbone.old_sync = Backbone.sync
Backbone.sync = function(method, model, options) {
if (method === 'read') {
if (!(model.url.indexOf('?key=') != -1)) {
model.url = model.url + '?key=' + key;
}
} else {
old_url = model.url();
if (!(old_url.indexOf('?key=') != -1)) {
model.url = function() {
return old_url + '?key=' + key;
}
}
}
Backbone.old_sync(method, model, options);
};
model.url was returning a function when its not a "read" method and didn't know how to handle it well and the other trouble is when a consecutive request is made, the token is added twice. I tried to remove it with that indexOf stuff with no luck.
Is there a better way to do this ?
I don't think you need to override sync at all:
var globalKey = 'key123';
var urlWithKey = function(url, key) {
return function() {
return url + "?key=" + key;
};
};
var MyModel = Backbone.Model.extend({
url: urlWithKey('/my/url/', globalKey)
});
If you now create an object and save it, a POST request to my/url/?key=key123 is sent.
I guess you could also override the url method if this is the behavior you need for all of your Backbone models.
A general note: in Backbone most parameters, such as url can be a function or a value. I don't know why in your example it was a function once and a value in another case, but you always must be able to handle both ways if you override some of the internal functions. If you look at Backbone's sourcecode you will see that they use getValue to access these parameters:
var getValue = function(object, prop) {
if (!(object && object[prop])) return null;
return _.isFunction(object[prop]) ? object[prop]() : object[prop];
};
Update: Overloading the url method for all models could work like this:
var globalKey = 'key123';
(function() {
var baseUrl = Backbone.Model.prototype.url;
Backbone.Model.prototype.url = function() {
return this.baseUrl + "?key=" + globalKey;
};
})()
var MyModel = Backbone.Model.extend({
baseUrl: '/my/url/'
});
You could also leave the regular Backbone.Model as it is, and create your own base class. See http://documentcloud.github.com/backbone/#Model-extend for details.
Just set your URL like so:
url : function() {
return "/my/url" + this.key;
}
In your overridden .sync, you only need to set the key property.

How to set my local javascript variable as a json data on remote website

I have a javascript code on my website, there is a variable:
var remoteJsonVar;
On the other hand there is a json file on a remote website
https://graph.facebook.com/?ids=http://www.stackoverflow.com
I need to set the variable remoteJsonVar to this remote jason data.
I am sure that it is very simple, but I can't find the solution.
A small working example would be nice.
Because you're trying to get the data from a different origin, if you want to do this entirely client-side, you'd use JSON-P rather than just JSON because of the Same Origin Policy. Facebook supports this if you just add a callback parameter to your query string, e.g.:
https://graph.facebook.com/?ids=http://www.stackoverflow.com?callback=foo
Then you define a function in your script (at global scope) which has the name you give in that callback parameter, like this:
function foo(data) {
remoteJsonVar = data;
}
You trigger it by creating a script element and setting the src to the desired URL, e.g.:
var script = document.createElement('script');
script.src = "https://graph.facebook.com/?ids=http://www.stackoverflow.com?callback=foo";
document.documentElement.appendChild(script);
Note that the call to your function will be asynchronous.
Now, since you may want to have more than one outstanding request, and you probably don't want to leave that callback lying around when you're done, you may want to be a bit more sophisticated and create a random callback name, etc. Here's a complete example:
Live copy | Live source
(function() {
// Your variable; if you prefer, it could be a global,
// but I try to avoid globals where I can
var responseJsonVar;
// Hook up the button
hookEvent(document.getElementById("theButton"),
"click",
function() {
var callbackName, script;
// Get a random name for our callback
callbackName = "foo" + new Date().getTime() + Math.floor(Math.random() * 10000);
// Create it
window[callbackName] = function(data) {
responseJsonVar = data;
display("Got the data, <code>shares = " +
data["http://www.stackoverflow.com"].shares +
"</code>");
// Remove our callback (`delete` with `window` properties
// fails on some versions of IE, so we fall back to setting
// the property to `undefined` if that happens)
try {
delete window[callbackName];
}
catch (e) {
window[callbackName] = undefined;
}
}
// Do the JSONP request
script = document.createElement('script');
script.src = "https://graph.facebook.com/?ids=http://www.stackoverflow.com&callback=" + callbackName;
document.documentElement.appendChild(script);
display("Request started");
});
// === Basic utility functions
function display(msg) {
var p = document.createElement('p');
p.innerHTML = msg;
document.body.appendChild(p);
}
function hookEvent(element, eventName, handler) {
// Very quick-and-dirty, recommend using a proper library,
// this is just for the purposes of the example.
if (typeof element.addEventListener !== "undefined") {
element.addEventListener(eventName, handler, false);
}
else if (typeof element.attachEvent !== "undefined") {
element.attachEvent("on" + eventName, function(event) {
return handler(event || window.event);
});
}
else {
throw "Browser not supported.";
}
}
})();
Note that when you use JSONP, you're putting a lot of trust in the site at the other end. Technically, JSONP isn't JSON at all, it's giving the remote site the opportunity to run code on your page. If you trust the other end, great, but just remember the potential for abuse.
You haven't mentioned using any libraries, so I haven't used any above, but I would recommend looking at a good JavaScript library like jQuery, Prototype, YUI, Closure, or any of several others. A lot of the code above has already been written for you with a good library. For instance, here's the above using jQuery:
Live copy | Live source
jQuery(function($) {
// Your variable
var responseJsonVar;
$("#theButton").click(function() {
display("Sending request");
$.get("https://graph.facebook.com/?ids=http://www.stackoverflow.com&callback=?",
function(data) {
responseJsonVar = data;
display("Got the data, <code>shares = " +
data["http://www.stackoverflow.com"].shares +
"</code>");
},
"jsonp");
});
function display(msg) {
$("<p>").html(msg).appendTo(document.body);
}
});

Categories