This example is borrowed from Backbone directory demo app
https://github.com/ccoenraets/backbone-directory/blob/master/web/js/utils.js#L11
// The Template Loader. Used to asynchronously load templates located in separate .html files
window.templateLoader = {
load: function(views, callback) {
var deferreds = [];
$.each(views, function(index, view) {
if (window[view]) {
deferreds.push($.get('tpl/' + view + '.html', function(data) {
window[view].prototype.template = _.template(data);
}, 'html'));
} else {
alert(view + " not found");
}
});
$.when.apply(null, deferreds).done(callback);
}
};
You initialize this with array of strings [views] and [callback] function.
My question is how window[view] (click above link to exact position in the code) can be checked if (as far as I see) wasn't be initialized previously? If I'm not precise please write this in comments.
If I've understood your question correctly, then when you call templateLoader.load you pass in 2 arguments; views and callback. We can assume that views is an array, since we then iterate over that array with the jQuery .each() method. The callback to .each() is passed the element of the views array that corresponds to the current iteration. That argument is named view.
So view is some arbitrary value that was stored in the views array. We then try to find a property of window with the identifier that matches the value of view. If view === "james" we are looking for window.james.
If you look at some of the views in that app you will see that they are defined like this:
window.ContactView = Backbone.View.extend({
// Some methods
});
So ContactView is a property of window, and we could call templateLoader.load like the following to load that template:
templateLoader.load(["ContactView"], someCallbackFn);
And you can see where that actually gets called in main.js.
So what's actually happening is a bunch of properties of window are defined in various other files, and then loaded by the template loader, by passing an array of identifiers to it.
Related
I am new to Javascript/backboneJS/RequireJS. In order to render my front end I have made one controller, one model and one view. Also, I have one dropdown in my html file.
So what I have done till now is in my html file at the END I have
require(['common'],function()
{
require(['jquery','fastclick','nprogress','charts','underscore','spinner'],function()
{
require(['firstDashboardController']);
});
});
So I am loading "firstDashboardController" and this controller loads all the modules accordingly and displays data in front end. So everything works fine.
Now I have a dropdown in the front end. When I select the dropdown, as per the id selected I want to retrieve the data. So I need to call "firstDashboardController" again so that everything gets rendered as per the new id that I have got.
So what am I suppose to do? Like do I need to UN-REQUIRE my "firstDashboardController" and then REQUIRE it again passing the new id. Because the controller is already loaded via Require beacuse I loaded it in my HTML file as mentioned above. But I need to load it again as per the new id selected it via dropdown. So how to go about it? Pleas help me. if any code snippet is required I can put that.
Code Snippet:
My Controller:
define(['backbone', 'firstSubViewModel','dropdownViewModel', 'dropdownModel'],
function(Backbone, firstSubViewModel, dropdownViewModel, dropdownModel) {
var ch = new dashboardModel.chart({});
if (localStorage.getItem('p_kt') && localStorage.getItem('p_acid') && localStorage.getItem('p_seid')) {
var data = {
tokenProp: localStorage.getItem('p_kt'),
accountIdProp: localStorage.getItem('p_acid'),
sessionIdProp: localStorage.getItem('p_seid')
};
$.when(
ch.fetch(data) // GETTING DATA FROM MODEL VIA AJAX CALL in my model.JS
).then(function() {
// Main Graph on Dashboard
new firstSubViewModel({
el: '#chartAnchor1',
model: ch
});
});});
I somehow need to call ch.fetch() again.
You aren't properly defining your controller. You currently have it as sort of a one-time setup method instead of something you can re-run later. Let's go step by step.
myLife.js:
define([], function() {
return "a complex series of failures";
});
By returning a value from define's callback, this defines that anytime I require "myLife", then it will provide "a complex series of failures" in the callback function. This is what Backbone and other AMD modules do to appear inside your code blocks. However, it only runs the contents once; and saves the result. So, this won't work:
incrementer.js:
var x = 0;
define([], function() {
x = x + 1;
return x;
});
(Trying to require incrementer would always give you "1".)
What you want to do is return a function - one you can re-run anytime.
incrementerV2.js:
define([], function() {
var x = 0;
return function() {
x = x + 1;
return x;
};
});
In any file, you can then have this:
require(['incrementerV2'], function(myIncr) {
myIncr(); // 1
myIncr(); // 2
});
...And, for the record, I would recommend having only one require statement in any given file whenever possible. You can add dependencies in that first-argument array if load order is important.
More commonly, people will have one module contain a self-defined object that has multiple functions in it, rather than the function I gave above. Returning and then using just one function is valid as well, depending on the use case. Any variable type works, but just remember it will always be the one and only same variable anytime you later need it.
Load Order
When the system retrieves myLife.js or incrementer.js above, there's an intermediate step before it actually runs the definition function we've defined. It will look at the first argument, the array of named dependencies, and figure out if there are still dependencies needed before it can run the function given. Example:
a.js: require(['b', 'c'], function(b, c) {
b.js: define(['c'], function(c) {
c.js: define([], function() {
a.js is requested first, but not run because it needs B and C. B loads next, but is ignored because C is not loaded. C runs, and then its return value is passed into A and B. This system is internally very smart, and should never request the same file twice or have conflicts if one file loads before another. You can use it much like imports in Java.
Also, let's say you only added 'c' in a.js so that b.js wouldn't crash, because it needs it loaded first - in that case, just take it out of A and it should work the same.
a.js: require(['b'], function(b) {
Just like A did, B will automatically load all its dependencies before it executes anything. A simple principle is to only refer a dependency if it's actually directly referenced in the file (or defines necessary global variables)
I was trying to add an additional url attribute as a function to my page-object while using nightwatchjs.
Like:
module.exports = {
url: function() {
return this.api.launchUrl + '/content/site1.xhtml';
},
cancelUrl: function() {
return this.api.launchUrl + '/content/cancel_site1.xhtml';
}
}
Anyhow nightwatch is not able to get that 2nd attribute cancelUrl, ie undefined.
Why is that so? Shouldn't nightwatch be able to access that attribute as it is nothing more than a function call returning a string or am I misunderstanding a javascript or special page-object concept?
--
I am aware that there should be a page-object for each site so there should not be a 2nd site. Anyhow I would like to understand why this is not working technically.
Not sure I can answer the "why" (other than to say that when nightwatch loads up your page objects as globally available it must be wrapping your js file and filtering on 'known' functions) but I can offer a solution: add a command to your page object with the desired function. For example:
let pageCommands = {
cancelUrl: function() {
return this.api.launchUrl + '/content/cancel_site1.xhtml';
}
};
module.exports = {
commands: [pageCommands],
...
}
It's not the typical use of page commands, but your test would then be able to access the cancelUrl function on the page object instance.
More on page commands here
I'm trying to create a custom component loader within knockout but I'm struggling with the view model. Essentially I want to remotely go grab both the HTML template and the JavaScript view model, but in this instance I don't want to use a traditional AMD module loader.
I've managed to get some of this working, specifically loading the HTML template but I can't figure out how to load the view model. Before I start here's my directory structure:
-- index.html
-- customerLoader.js
-- comps
-- myCustom.html
-- myCustom.js
So I've created my component loader like so. getConfig basically takes the name of the component and turns that into a path for the viewModel and the html template.
var customLoader = {
getConfig: function(name, callback) {
callback({ template: "comps/" + name + ".html", viewModel: "comps/" + name + ".js" });
},
loadTemplate: function(name, templateConfig, callback) {
console.log("loadTemplate", name, templateConfig);
$.get(templateConfig, function(data) {
callback(data);
});
},
loadViewModel: function(name, templateConfig, callback) {
console.log("loadViewModel", name, templateConfig);
$.getScript(templateConfig, function(data) {
callback(data);
});
}
};
ko.components.loaders.unshift(customLoader);
This successfully makes a request to load the template, which brings back some basic content. What I'm struggling with is the view model. I'm not sure what should be in the target of my JavaScript file?
I assumed that I'd want to return a function that would take some parameters, most likely a params object. However if I try and do this I get an error, telling me the JavaScript is invalid:
Uncaught SyntaxError: Illegal return statement
This is the current content I've got that is producing this error:
return function(params) {
console.log("myCustom.js", name, viewModelConfig);
// Add a computed value on
params.bookNum = ko.computed(function() {
switch(this.title()) {
case "A": return 1;
case "B": return 2;
case "C": return 3;
default: return -1;
}
});
//ko.components.defaultLoader.loadViewModel(name, viewModelConstructor, callback);
};
So ultimately I'm not sure how to achieve this, but I guess there are 3 basic questions that explain the gaps in my understanding:
What should my "view model" JavaScript file contain exactly? A function? An object? etc...
Do I need to call the ko.components.defaultLoader.loadViewModel at all?
Within my customLoader what should loadViewModel() be doing with the result of the jQuery callback? I'm not sure if I get back a JavaScript object, or just a string?
I'm open to achieve this in a different way if need be (e.g. not using jQuery but getting files a different way), but I don't want to use a module loader (e.g. require.js/curl.js in this instance).
First lets figure out what is happening...
From the docs:
This ($.getScript()) is a shorthand Ajax function, which is equivalent to:
$.ajax({
url: url,
dataType: "script",
success: success
});
And from jQuery.ajax():
...
dataType: ...
"script": Evaluates the response as JavaScript and returns it as plain text.
So your code is fetched, evaluated and then would have been returned as text, but evaluation first fails because you can't return if you're not within a function.
So what can be done? There are several options:
Use a module loader.
jQuery isn't a module loader, and as such it doesn't have the ability to parse fetched code and create a value / object from that code. A module loader is designed specifically for this task. It will take a script written in a specific pattern and "evaluate" it into a value (typically an object with 1 or more properties).
Change your script to a legal script
Because it's illegal to have a return statement in global code, your current code fails. You could however create a named function (or a variable with a function expression) and then use that name to reference that function. It could look like this:
function myCreateViewModel(param) {
// whatever
}
And the usage would be:
$.getScript(templateConfig, function() {
callback(myCreateViewModel);
});
The downside here is that if you ever go through that code path twice in the same page, your script will overwrite the old declaration. That might not ever be a problem, but it feels dirty.
Not use $.getScript(), use $.ajax() (or $.get()) with dataType: 'text' and evaluate yourself.
Remove the return from your code, and wrap it with an eval(). It will be evaluated as a function expression, the return value of the eval will be your function, and you could pass that directly to the callback:
$.get({
url: templateConfig,
dataType: 'text',
success: function(text) {
callback(eval(text));
}
});
This will work, but it will use the frowned upon eval(), which is exposing you to various risks.
I am trying to make MVC structure of application in canjs. For that I am using requireJS to separate the code in different file.
I have searched in google and i am following this tutorial but in that tutorail I dont find to access module variables in different modules. therefore I am following this method to do so.
But I cannot achieve that.
This is my code:
requirejsconfig.js file :
requirejs.config({
paths :{
enforceDefine: true,
waitSeconds : 0,
jquery : "https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min",
main : "view/main",
player : "view/player",
PlayerModel : "/models/PlayerModel",
hbs : "/models/view/js/handlebars",
fixture : "/models/view/js/can.fixture",
can : "/models/view/js/can.jquery"
}
});
main.js :
require(["player"],function(player){
player.PlayerModel();
});
I want to use that model methods in my controller.
player.js :
define(['PlayerModel'],function(){
function PlayerModel(){
var Player = PlayerModel.Player;
Players =can.Control({ defaults :{view:view/players.hbs' }},{
init: function(){
this.element.html(can.view(this.options.view, {
players: this.options.players
}));
}
$(document).ready(function(){
$.when(Player.findAll()).then(
function(playersResponse){
var players = playersResponse[0];
new Players('.players', {
players: players
});
});
});
}
});
PlayerModel.js:
define(function(){
var Player = can.Model({
findAll: 'GET /models/players.json',
findOne: 'GET /players.json/{id}'
});
return {
Player:Player
}
});
Every file is being loaded (saw in network tab-chrome devtools) but nothing is being populated in output.
Can anybody help me ?
Thanks in advance!
Carrying on with what #ekuusela said, restructure the code in Player.js in this format:
define(['PlayerModel'],function(){
function PlayerModel(){ ... }
return {
PlayerModel: PlayerModel
}
});
What's happening
Internally, two conventions are followed when defining modules. These relate to:
What the module is called (its label)
What this label represents.
Labeling Modules
The filename is taken as the module's name (unless shim is used, like you have). In other words, the define(['Module_Name'] ...), which is how I would normally read that line, can more accurately be read as a define(['Module_Path_Or_Shim_Symbol_Name' ...)
What Is This 'Module' Anyway
A module isn't magic - it is just a specially labeled map to a function. This map is maintained by RequireJS and probably looks similar to this:
var ModuleMap = {
'Player' : function (...) { ... },
'PlayerModel' : function (...) { ... }
};
Every time a module is accessed, through a require or define call, this map is accessed, and the relevant function found. However, that isn't enough - what we want is the stuff that is defined within the function - the fundamental concept of modules is that everything inside them has Module Function Scope, and is not exposed outside. So, to gain access to this "stuff", the RequireJS brain does the only thing it can do with a function - execute it.
var playerReference = require('Player');
Note that I've used the CommonJS notation of requiring modules, which is more readable for our current purpose.
So, in the code you posted, the module function has defined and declared PlayerModel as a function, but has not exposed it. Since the line player.PlayerModel() expects the module to return an object with a property named PlayerModel that refers to your function, the logical return value of the module is:
var exposedModuleReference = { PlayerModel: PlayerModel };
return exposedModuleReference;
Note that this means the name with which the function is exposed can be different from the function name itself. For example, the following code will also work without any changes anywhere else:
define(['PlayerModel'],function(){
function PlayerModelConstructor(){ ... }
return {
PlayerModel: PlayerModelConstructor
}
});
An Interesting Addition
So, executing a module function and assigning that return value to a reference is one part of what RequireJS's brain does. The other part is, it then updates this map so it now looks like this:
var ModuleMap = {
'Player' : { PlayerModel: PlayerModelConstructor },
'PlayerModel' : function (...) { ... }
};
This means that code written in module functions gets executed at most one time.
You define the function PlayerModel inside player.js and then require a module called PlayerModel there but don't assign the required module to any variable. You should first clean up your code, possibly rename some of your modules and move functions around.
Here, you try to access the function PlayerModel in the module player, but the module factory function in player.js doesn't return anything:
require(["player"],function(player){
player.PlayerModel();
});
What gets assigned to the function parameter player is only whatever you return from the function that defines the module. (If you would define a module as an object then that object would be the argument.)
Sorry I couldn't be anymore specific with the title.
I'm building a web-site (personal), which displays different content to the user depending on the query string that is used in the url.
e.g. page=home.html would display home.html
The websites Javascript is wrapped inside an object, with each value containing different data, some pseudo code:
(function(){
var wrapper = {
init: function(){
//Runs on document ready
this.foo();
this.nav.render();
},
foo: function(){
//Some functionality goes here for the website, e.g. Display something from an API
},
nav: {
//Functionality to handle the navigation, has different properties
config: {
//Contains the config for nav, e.g. page names + locations
dir: '/directory/to/content/',
pages: {
page_name: wrapper.nav.config.dir + 'page_value'
}
},
render: function(){
//some code
},
routes: function(){
//some code}
}
}
};
$(function(){
wrapper.init();
});
})();
My problem is that I'm trying to prepend the dir value to each of the page values (inside the object where the pages are defined), expecting to get the output of (in this pseudo code case) of directory/to/content/page_value, but instead dir is undefined when I'm trying to access it, I've tried the following to achieve what I want:
wrapper.nav.config.dir + 'page_value'
I've been playing around with the last 30 minutes trying to find out what I'm doing wrong, and even thought about hard-coding the URL in for each page.
The reasoning for wanting to do this is that my local development server and web host have different directory structures, so I don't want to re-write the URL's each time I want to develop + publish. As for why everything is wrapped inside an object, I thought it would be easier to maintain this way.
Hopefully the answer is simple and it's just an amateur mistake / lack of understanding.
The issue is that you can't refer to a variable that is being defined in that very definition.
So, inside the definition of wrapper, you can't refer to wrapper. And, inside the definition of config, you can't refer to config either and so on.
The usual design pattern for solving this is to initialize as much as you can in the declaration of your data structure and then do the rest in .init() when you can freely access all of it.
Change the first two lines to:
var wrapper = null;
(function(){
wrapper = {
Otherwise, the wrapper is a local variable to your anonymous function.
The problem is that you're still busy defining the wrapper when you ask for its value, which is why it's still undefined.
The code below fails too:
var x = {
y:"1",
z:x.y
}
Why not:
//...
init: function(){
//Runs on document ready
this.foo();
var config = this.nav.config;
for (var page in config.pages) {
config.pages[page] = config.dir + config.pages[page];
}
},
//...