I'm working on a Flask app where some data gets passed to a template, and I want to make that data available to multiple instances of an object. Here's what it would look like if I just hardcoded the desired data into my .js file:
var Module = function(selector) {
var targetDiv = selector,
targetData = 'lorem ipsum sit dolor',
show = function() {
$('<p>' + targetData + '</p>').appendTo(targetDiv);
};
return {
show: show,
};
};
$(document).ready(function() {
firstModule = new Module($('#first'));
secondModule = new Module($('#second'));
firstModule.show();
secondModule.show();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='first'></div>
<div id='second'></div>
I can't just call a function on an unconstructed Module object, so my next step is to create a static-like ModuleFactory that I can load with data from jinja, and then create Modules from there:
var ModuleFactory = function() {
var targetData = null,
setData = function(data) {
targetData = data;
},
create = function(selector) {
return new Module(selector, data);
};
return {
setData: setData,
create: create,
};
} ();
Then I attempt to call ModuleFactory.setData({{data}}); from a <script> tag in the HTML, and do something like ModuleFactory.create($('#first')).show(); in the .js
But of course because I have to include my .js file before using the ModuleFactory in the HTML, I end up constructing the objects before the factory is initialized.
(Past this point, my workaround attempts stop being relevant to the problem.)
Anyway, what's the correct way of getting data from Jinja to my JS module? There has to be a common pattern or something.
This feels awful, but it works:
Have an object with a runProgram method, which takes the desired data as a parameter, then executes the logic that had previously been inside the document ready function. e.g.
var ProgramRunner = function() {
var runProgram = function(data) {
ModuleFactory.setData(data);
firstModule = ModuleFactory.create($('#first'));
firstModule.show();
};
return {
runProgram: runProgram;
};
};
Then just
<script>
$(document).ready(function() { ProgramRunner.runProgram({{data}}) });
</script>
in the HTML.
(Leaving question open, because I suspect there's a much better way of handling this.)
Related
I am struggling with when to destroy backbone views. I know I need to destroy the view somewhere, but I am not sure where.
I have the following code in router.js
routes: {
"names/search": "nameSearch",
"companies/search": "companySearch"
},
initialize: function(){
Backbone.history.start();
this.navigate("#/", true);
}
nameSearch: function () {
require(["app/views/RecordSearch"], function (RecordSearchView) {
var obj = {};
obj.Status = [utils.xlate("On Assignment"), utils.xlate("Candidate")];
var view = new RecordSearchView({ model: obj, el: $(".content") }, { "modelName": "Candidate" });
view.delegateEvents();
});
},
companySearch: function () {
require(["app/views/RecordSearch"], function (RecordSearchView) {
var view = new RecordSearchView({ model: {}, el: $(".content") }, { "modelName": "Company" });
view.delegateEvents();
});
}
And then in RecordSearchView.js I have the following function that is called when a user clicks the search button
doSearch: function () {
require(["app/utils/SearchHelper", "app/models/" + modelName, "app/views/SearchResults"], function (SearchHelper, Model, SearchResultsView) {
var obj = $("#searchForm").serializeArray();
var params = SearchHelper.getQuery(obj);
params["page"] = 1;
params["resultsPerPage"] = 25;
var collection = new Model[modelName + "Collection"]({}, { searchParams: params });
params["Fields"] = collection.getSearchFields();
collection.getPage(params["page"], function (data) {
require(["app/views/SearchResults"], function (SearchResultsView) {
App.Router.navigate(modelName + "/search/results");
var view = new SearchResultsView({ collection: data, el: $(".content") });
view.delegateEvents();
});
});
return false;
});
And SearchResults.js
return BaseView.extend({
init: function () {
this.render();
},
render: function () {
var data = this.collection.convertToSearchResults();
this.$el.html(template(data));
return this;
}
});
The problem is the second time I perform any search (calling the doSearch function from RecordSearch.js). As soon as I perform the second search, the data shown is that belonging to the previous search I performed. (For example I do a name search and it works, then do a company search but the screen shows company search results but then is quickly replaced with name search results).
My questions are
I suspect I need to call some cleanup code on the view before it is re-used. Where is the proper place within a backbone application to run this.
Is there anything wrong with the way I load SearchResults view from within RecordSearch view? SearchResults does not have a path on my router, but it is basically a form post, so I assume it shouldn't?
Any help is appreciated.
This problem is quite common and is known as Zombie Views. Derick Bailey explains this issue very well here: http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/
However unfortunately you can't simply solve it without changing the way you are loading your views.
Because you are loading them inside RequireJS modules that will keep it in the local var scope, you are losing the reference to the views once the route has been fully processed.
In order to solve this problem, you would need to keep the reference of the current view somewhere, and then properly dispose it before calling another view, something like this:
showView: function(view) {
this.currentView && this.currentView.remove();
this.currentView = view;
this.currentView.render();
$('#content').html(this.currentView.el);
}
More about this solution here: http://tiagorg.com/talk-backbone-tricks-or-treats-html5devconf/#/6
I personally suggest you adopting a solution that will take care of this for you, like Marionette.js
It will handle this and quite many other issues, by providing the missing gaps of every Backbone-based architecture.
I have a few questions about Best Practises using javascript in external files and namespacing.
Let's have a namespace MyCompany, global configuration stuff, code for individual pages and maybe some "API"s.
var MyCompany = {};
Global configuration in HTML
MyCompany.root = "/";
Which approach is better
First
MyCompany.Page = {};
(function(ns} {
ns.init = function() {
var root = MyCompany.root;
ajax(root+"somepage.html");
};
}(MyCompany.Page.Home = MyCompany.Page.Home || {});
and in html use
<script>
$( function() {
MyCompany.Page.Home.init();
});
</script>
Second (Page as an Class and its instance)
MyCompany.Page.Home = function() {
var root = MyCompany.root;
this.init = function() {
ajax(root + "somepage.html");
};
};
in html
<script>
var page = new MyCompany.Page.Home();
$( function() {
page.init();
});
</script>
Submodules and Mixing API with Page javascript
If our Homepage has some reviews.
MyCompany.Page.Home.Reviews = function() {
this.init = function() {
load_stuff();
}
};
And now inside Page init use
MyCompany.Home.Page = function(data) {
var reviews = new MyCompany.Home.Page.Reviews();
this.init = function() {
reviews.init();
};
};
Could that cause troubles?
It's obvious that Reviews extends MyCompany.Home.Page, but MyCompany.Home.Page requires Reviews.
It shouldn't cause troubles if instance on MyCompany.Home.Page is created after MyCompany.Home.Page.Reviews are loaded, right? Because Reviews in fact will extend the function object, is that right?
I guess this depends on answer to first question.
It also could be
(function(ns) {
ns.init = function() { MyCompany.Page.Home.Reviews.init(); };
})(MyCompany.Page.Home = MyCompany.Page.Home || {} );
(function(ns) {
ns.init = function() { load_stuff(); };
})(MyCompany.Page.Home.Reviews = MyCompany.Page.Home.Reviews || {});
Also should I somehow separate API of Page javascript?
Such as
MyCompany.APIS.Maps = function(location) {
/* Private variables */
var _location = location;
/* Private functions */
function search_address(address) { .. do search .. }
/* Public interface */
this.search = search_address;
do some initialization ...
};
I'd be glad if anyone reads it all to leave some comment.
Thank you in advance.
Which approach is better? Revealing singleton module (first) or a constructor function/class and its instance (second)?
Depends on your use case. If you don't expect multiple page objects to exist at once (and you hardly seem to), the singleton (with an init function) is really fine. Everything else could be considered wrong or at least overkill.
Same thing holds true for your MyCompany.Page.Home.Reviews (or MyCompany.Home.Page.Reviews?) class module, of which you seem to need only one instance.
It shouldn't cause troubles if instance on MyCompany.Home.Page is created after MyCompany.Home.Page.Reviews are loaded, right? Because Reviews in fact will extend the function object, is that right?
Yes.
(function(ns) {
ns.init = function() { MyCompany.Page.Home.Reviews.init(); };
})(MyCompany.Page.Home = MyCompany.Page.Home || {} );
If you have that ns shortcut available, you should use it:
(function(ns) {
ns.init = function() { ns.Reviews.init(); };
})(MyCompany.Page.Home = MyCompany.Page.Home || {} );
Also should I somehow separate API of Page javascript?
For development: Yes, in every case. Each module should have its own file. When deploying, you might concatenate them together for faster loading, but that's a different question.
I am trying to use the Javascript Module pattern. I want my module to have a public method that can be called when a json file is loaded. In this simple example, the module loads the json on init and (should) load an image into a DOM element when the public method 'loadPic' is called. The source of the image is in the json file.) When I run the script the first time, I get the following error: Uncaught TypeError: Cannot read property 'src' of undefined. My interpretation is that the method 'loadPic' is called automatically, before the data is loaded... but I don't know how to prevent that. Here is the code (PS: I am using Zepto, the 'light' version of jQuery):
<!-- language: lang-js -->
/* in module file: DualG.js */
;(function ($) {
$.extend($.fn, {
DualG: function (element, options) {
var defaults = { // not used yet...
indexStart:0,
url: 'js/data.json'
},
my = {},
init,
loadPic,
plugin = this;
plugin.settings = {};
plugin.pics = [];
init = function () {
plugin.settings = $.extend{}, defaults, options);
$.getJSON(plugin.settings.url, function (data) {
var l = data.images.length, i;
for (i = 0; i < l; i = i + 1) {
plugin.pics[data.images[i].index] = data.images[i];
}
});
init();
my.loadPic = function (index, container) {
$(container).empty().append($("<img>").attr({"src":plugin.pics[index].src}));
};
return my;
}
});
}(Zepto));
My problem was that data is loaded a-synchronistically - so I was tying to use data that was not loaded yet. I added an event trigger in the init, after loading the data. Then, I listen to the event and call the loadPic method only then... Thanks all for your help.
I am attempting to use namespaces to better organize my sites very complicated code. I have:
var myApp = {};
myApp.fp = {
brandCarousel: null,
init: function() {
// initialize brand carousel
this.brandCarousel = new Pluit.Carousel('brand-scroll-outer', { circular: false });
}
};
document.observe("dom:loaded", function() { myApp.fp.init(); });
I am using prototype 1.7 and the Pluit Carousel library.
If I try to call one of Pluit Carousels methods (moveNext) by typing:
myApp.fp.brandCarousel.moveNext()
I get a JavaScript error:
TypeError: Object # has no method 'moveNext'
Is there something I am getting wrong when it comes to scope or namespacing? How can I access this internal function?
Nothing about your particular example looks especially wrong (except that it switches between myApp and masApp (typo?)). Here's a stripped-down, dummy example to show that your approach works (and that your problem must be in another location):
var myApp = {};
myApp.fp = {
brandCarousel: null,
init: function() {
// initialize brand carousel
this.brandCarousel = new (function() {
this.moveNext = function() {
alert("moving!");
};
})
}
};
myApp.fp.init()
myApp.fp.brandCarousel.moveNext()
The above code alerts moving! as expected.
I'm trying to organize my ExtJS javascript a little better. I've an ExtJS object like this:
Ext.define('QBase.controller.ControlModelConfigurationController', {
extend: 'Ext.app.Controller',
views: [
'ControlModelConfiguration'
],
init: function() {
console.log('Initialized ControlModelConfigurationController');
this.control({
'#testBtn': {
click: this.loadModel
}
});
},
loadModel: function() {
console.log('Load Model....');
var conn = new Ext.data.Connection;
conn.request({
url: 'partsV10.xml',
callback: function(options, success, response)
{
if (success)
{
alert("AHHH");
var dq = Ext.DomQuery;
var xml = response.responseXML;
var nodes = dq.select('part', xml,parent);
Ext.Array.forEach(nodes,handleNode);
}
}
});
},
handleNode: function(items) {
console.log(item.name);
}
});
The posted code above is not working. Ext.Array.forEach(nodes,handleNode) causes trouble. Instead of using an anonymous function like :
...
Ext.Array.forEach(nodes,function(item) {
console.log(item)});
}
...
I'd like to extract the anonymous function as a named external one. Unfortunately I'm unable to figure out the right syntax to establish a code structure as shown above.
Meanwhile, I figured out, that putting
function handleNode(item) {
{console.log(item)}
}
at the very end of the file works. Is it possible to make the handleNode method an object - "member" of the controller?
Thanks in advance
Chris
handleNode is a member of the containing object. When loadModel is called, this contains the right object, but at the time the callback is invoked, it will not point to the one we are interested in. You can save this to the local variable self, and use it instead.
loadModel: function() {
var self = this
console.log('Load Model....');
var conn = new Ext.data.Connection;
conn.request({
url: 'partsV10.xml',
callback: function(options, success, response)
{
if (success)
{
alert("AHHH");
var dq = Ext.DomQuery;
var xml = response.responseXML;
var nodes = dq.select('part', xml,parent);
Ext.Array.forEach(nodes, self.handleNode);
}
}
});
},
The solution posted by vhallac is not entirely correct. It assumes that handleNode doesn't reference the current object through this variable. Maybe just a typo, but additionally it's not really the ExtJS way...
Whenever ExtJS provides a callback parameter, there is nearly always a scope parameter to set the value of this within the callback function.
loadModel: function() {
console.log('Load Model....');
var conn = new Ext.data.Connection;
conn.request({
url: 'partsV10.xml',
callback: function(options, success, response) {
if (success) {
alert("AHHH");
var dq = Ext.DomQuery;
var xml = response.responseXML;
var nodes = dq.select('part', xml, parent);
Ext.Array.forEach(nodes, this.handleNode, this);
}
},
scope: this
});
},
handleNode: function(node) {
// From within here you might want to call some other method.
// Won't work if you leave out the scope parameter of forEach.
this.subroutine();
}
Just like forEach uses a scope parameter, the request method uses a scope config option. This is ExtJS convention for passing around the scope. You can alternatively create an extra local variable and reference the scope from there, but in the context of ExtJS this style will feel awkward, plus (I'm pretty sure) it's a lot more bug-prone.