I'm using require with backbone + backbone-forms. I'm currently using RequireJS to seperate code into multiple files. I have models stored in separate files and want to keep form validators separately.
However, I am unable to access variables defined in one files, in another file that depends on this one. What I get is Uncaught ReferenceError: isEmptyName is not defined. isEmptyName is defined in validators and used in model. Any feedback about RequireJS config is also appreciated.
My config:
requirejs.config({
//By default load any module IDs from js/lib
baseUrl: 'js',
paths: {
jquery: 'lib/jquery',
app: 'lib/app',
wizard: 'lib/jquery.bootstrap.wizard.min',
bootstrap: 'lib/bootstrap.min',
underscore: 'lib/underscore-min',
backbone: 'lib/backbone-min',
backboneForms: 'lib/backbone-forms.min',
langSwitcher: 'lib/lang',
cookie: 'lib/cookie',
datepicker: 'lib/bootstrap-datepicker',
mask: 'lib/jquery.maskedinput.min',
validators: 'modules/validators',
// models
personalData: 'models/personal-data',
addressData: 'models/address-data',
workData: 'models/work-data',
productsData: 'models/products-data',
statmentData: 'models/statment-data',
model: 'models/form',
collection: 'collections/form',
view: 'views/form',
setup: 'setup',
send: 'send',
},
shim: {
'underscore': {
deps: ['jquery'],
exports: '_'
},
'backbone': {
deps: ['underscore', 'jquery'],
exports: 'backbone'
},
// all model needs to go within one collection
'bootstrap' : ['jquery'],
'wizard': ['jquery'],
'backboneForms': ['backbone'],
'validators': ['backbone','mask'],
'personalData' : ['backbone','backboneForms','validators'],
'addressData': ['backbone','backboneForms'],
'workData': ['backbone','backboneForms'],
'statmentData': ['backbone','backboneForms'],
//'collection': ['backbone','backboneForms','personalData'],
//'view': ['backbone','backboneForms','personalData']
}
});
Beginning of validators.js
require(['backbone','backboneForms'], function(){
var lettersOnly = /^[A-Za-zęóąśłżźćńĘÓĄŚŁŻŹĆŃ]+$/;
var lettersOnlyDash = /^[A-Za-zęóąśłżźćńĘÓĄŚŁŻŹĆŃ\-]+$/;
var err = {};
var errCh = {};
var errFormat = {};
var isEmptyName = function(value){
err = { message: 'Wpisz imię.'};
if (value.length === 0) return err;
};
Beginning of model.js that needs the validators in validators.js
require(['backbone','backboneForms','mask','validators'], function(backbone,backboneForms,mask,validators){
var PersonalData = Backbone.Model.extend({
schema: {
first_name:{
title: 'Imię',
validators: [isEmptyName, isLetter, minCharCount] //Accessing validators.js members here...
}, ...
I think you're using require when what you really need is define. From When should I use require() and when to use define()?,
With define you register a module in require.js that you than can
depend on in other module definitions or require statements. With
require you "just" load/use a module or javascript file that can be
loaded by require.js.
So here, you have some variables that are defined in one file, but are required to be accessed in another file. Seems like a 'Module', doesn't it? So now, you have two ways of using this file as a module:
Conform to AMD-ness
Conform to chaotic javascript global variable-ness
Using the AMD Approach
validators.js is now a module. Anybody wishing to use 'validator functions' can depend on this module to provide it for them. That is,
define(['backbone','backboneForms'], function(){
var lettersOnly = /^[A-Za-zęóąśłżźćńĘÓĄŚŁŻŹĆŃ]+$/;
var isEmptyName = function(value){
err = { message: 'Wpisz imię.'};
if (value.length === 0) return err;
return {
someVariable: lettersOnly,
someFunction: isEmptyName
}
};
You'll notice that the require has been replaced with define. Now, when somebody (model) depends on validator.js, they can access their dependencies as follows
require(['backbone','backboneForms','mask','validators'],
function(backbone, backboneForms, mask, validators) {
var isEmptyNameReference = validators.someFunction;
...
Using shim
Check Requirejs why and when to use shim config, which references this link which says,
if we were to just add the backbone.js file to our project and list
Backbone as a dependency from one of our modules, it wouldn’t work.
RequireJS will load backbone.js, but nothing in backbone.js registers
itself as a module with RequireJS. RequireJS will throw up its hands
and say something like, “Well, I loaded the file, but I didn’t find
any module in there.”
So, you could have your validator.js populate a global Validator namespace, and still use it the way we used it in the example above.
function(){
var lettersOnly = /^[A-Za-zęóąśłżźćńĘÓĄŚŁŻŹĆŃ]+$/;
var isEmptyName = function(value){
err = { message: 'Wpisz imię.'};
if (value.length === 0) return err;
Globals.Validator = {
someVariable: lettersOnly,
someFunction: isEmptyName
}
}();
Your config.js would then be,
shim: {
'validator': {
deps: ['backbone','backboneForms'],
exports: 'Globals.Validator'
},
...
Note that you can alias the namespace as you wish, but the alias is just a reference to the existing global object/namespace. This is helpful if you have, say, Foo.Bar.Foobar as your namespace, but want to refer to it as FB. Shimming, hence, is a way for non-AMD libraries to adapt to AMD usage. In this case, option 1 should be sufficient.
Related
I actually have two questions concerning requirejs and singleton objects. I want to form a singleton object playing the role of my application core and pass it to modules as a parameter. How should this be done?
The other issue I have is related to a private object inside the application core. It's suppose to act as a container for modules but for some reason when I try to start the application, this container seems to have those modules but it can't be looped through. Any ideas why this is happening?
Here's an example code of the situation:
// applicationConfig.js
require.config({
baseUrl: 'js',
paths: {
jquery: 'jquery-3.1.1.min',
core: 'utils/applicationCore',
domReady: '../lib/domReady'
}
});
require(['modules']);
// modules.js
define(['core', 'domReady'], function(Core, domReady) {
require([
'modules/module1'
]);
domReady(function() {
var modules = Core.getModules();
var name = '';
for (name in modules) {
modules[name].creator(); // Start a module...
}
});
});
// applicationCore.js
define(function() {
return (function() {
var _modules = {};
return {
add: function(name, creator) {
_modules[name] = {
creator: creator
};
},
getModules: function() {
return _modules;
}
}
}());
});
// module1.js
define(['core', 'jquery'], function(Core, $) {
Core.add('module1', function() {
// Module constructor function.
});
});
I'm trying to write Unit Testing for my SPA project. Where we have used the Durandal (Framework), Knockout (Binding) with RequireJs.
I have installed the Chutzpah in Visual Studio 2012.
When i run my Test for the View Model, it throws me below error, even though the knockout js and other js are loaded correctly.
Uncaught ReferenceError: ko is not defined
My Json Config Code:
{
"Framework": "jasmine",
"TestHarnessReferenceMode": "AMD",
"TestHarnessLocationMode": "SettingsFileAdjacent",
"References" : [
{"Path" : "../Scripts/require.js" },
{"Path" : "config.js" }
],
"Tests" : [
{"Path": "tests"}
]
}
My Config Js Code:
require.config({
paths: {
'text': '../Scripts/text',
'durandal': '../Scripts/durandal',
'plugins': '../Scripts/durandal/plugins',
'jquery': '../Scripts/jquery-2.1.4',
'knockout': '../Scripts/knockout-3.3.0'
},
shim: {
}
});
My FirstTest.Js Code:
define(['project/modules/Settings/Subscriber/Viewmodels/Channels'],
function (nChannel) {
describe("Get Channels", function () {
it("will check the Get Channels call and result", function () {
var disp = nChannel.getChannels().then(function () {
var actualResult = ko.toJS(nChannel.Channels);
expect(actualResult.length).toEqual(3);
});
});
});
});
ViewModel Code:
define(['plugins/dialog'], function (dialog) {
var subscriberList = ko.observableArray(); //Getting Error here - while loading the Js for Unit Testing
var JsQ = $; //Getting JQUERY members here. // Works good.
//Other Logics goes here
return {
subscriberList : subscriberList,
JsQ : JsQ
};
});
The Configuration for the Jquery works perfect, since knockout also same as that. But gives error.
Any Idea's / Suggestion why the error?
Do i need to load the ko (knockout) separately?
Edit 1:
I have tried changing the knockout to ko it gives me the error Uncaught Error: Script error for: knockout.
Edit 2:
The problem i'm facing when i apply this solution, those existing code file needs the massive changes and the file counts are in hundreds. From Init.Js we have loaded the Jquery and Knockout. Like below.
requirejs.config({
paths: {
'text': '../Scripts/text',
'durandal': '../Scripts/durandal',
'plugins': '../Scripts/durandal/plugins'
}
});
define('jquery', [], function () {
return jQuery;
});
define('knockout', [], function () {
return ko;
});
So inside any viewmodel we can get the instance of knockout as ko, without declaring the require js stuff in each veiwmodel for the Knockout (As you suggested).
But when i try the same in Chutzpah declaration this is not working. Not sure why.
Hope you understand the problem.
In both modules you show in your question, you use ko but you do not list knockout in your list of dependencies. This is a sure way to get the error you got. Modify your modules to list knockout in the dependencies, and add a corresponding parameter to the callback you give to define. For instance,
define(['knockout', 'plugins/dialog'], function (ko, dialog) {
Im studying require and modular programming with backbone.
My question specifically is how can I access the models I have created in the View module as shown below from the main page, (example from the console), as is allways telling me undefined.
I understand that as its encapsulated in the view module, but is being hard to me to understand where I should create the instances of the model and collection as if I do it in init.js I get them to be undefined in the view module when i define collections or model.
If I instance them from model or collections modules I get a bunch of undefined errors
I have this init.js;
requirejs.config({
enforceDefine: true,
baseUrl: 'js',
urlArgs: "bust=" + (new Date()).getTime(),
shim: {
underscore: {
exports: '_'
},
backbone: {
deps: ['underscore', 'jquery'],
exports: 'Backbone'
}
},
paths: {
jquery: 'lib/jquery-1.10.2.min',
backbone: 'lib/backbone.min',
underscore: 'lib/underscore.min',
text: 'lib/require/text',
Plato: 'app/models/plato',
Carta: 'app/collections/carta',
MainView: 'app/views/mainView',
mainTemplate: 'app/templates/mainTemplate.html'
}
});
define(['jquery', 'underscore', 'backbone', 'MainView'], function($, _, Backbone, MainView) {
console.log(typeof $);
console.log(typeof _);
console.log(typeof Backbone);
var mainView = new MainView;
});
Then I have mainView.js as:
define(['backbone', 'text!mainTemplate', 'Plato', 'Carta'], function(Backbone, mainTemplate, Plato, Carta) {
var pizza = new Plato({precio:120, nombre:'pizza', foto:'n/a', ingredientes: 'harina, tomate, oregano', diabeticos:false}),
empanada = new Plato({precio:40, nombre:'empanada', foto:'n/a', ingredientes: 'harina, carne picada', diabeticos:false}),
lasagna = new Plato({precio:200, nombre:'lasagna', foto:'n/a', ingredientes: 'pasta, queso', diabeticos:false}),
carta = new Carta([pizza, empanada, lasagna]),
MainView = Backbone.View.extend({
tagName: 'div',
id: 'mainView',
events: {'click td': 'clickAction'},
collection: carta,
template: _.template(mainTemplate, this.collection),
initialize: function() {
this.render();
},
render: function() {
this.collection.each(function(item) {
console.log(item.toJSON() + item.get('nombre'));
this.$el.append( this.template( item.toJSON() ));
}, this);
$('#content').html(this.$el);
return this;
},
clickAction: function() {
alert(this);
}
});
return MainView;
});
I also have the model and collection modules if that helps you out to help me.
My main purpose would be to be able to access them and then put a listener or an on in the elements to be able to play with the data of those models.
Sorry If I am confused and mixing concepts with the variable scope of the modules using require.js and backbone, but I have read whatever I was able to find in the internet and Im still confused.
EDIT
Should I create and entire module just to instantiate them and then export the values as an object??
Should I create and entire module just to instantiate them and then
export the values as an object??
Yes. That's one way to accomplish what you are looking to do:
define(['backbone', 'text!mainTemplate', 'Plato', 'Carta', 'carta'],
function(Backbone, mainTemplate, Plato, Carta, carta) {
...
});
Where Carta is the collection module and carta is the object that contains the data.
I am facing a weird issue in a requirejs/backbonejs application. I have a Globals.js file which returns reusable utilities. It looks something like this.
define(
['newapp/routers/index', 'newapp/controllers/index', 'newapp/utilities'],
function(Router, Controller, Utilities) {
return {
router: new Router({controller: Controller}),
utilities: Utilities,
navigate: function(path, opts) {
this.router.navigate('app/' + path, opts);
}
}
})
When I require this module in modules that return Backbone Views, it is able to resolve Globals to an object and call methods on it. However, when I try to include it in a module that returns another object, it's resolved to undefined.
For example the code below is able to resolve Globals to the properties it exposes
define(
['marionette', 'templates', 'newapp/globals', 'newapp/views/Loader'],
function(M, T, Globals, mixins){
"use strict";
return M.ItemView.extend(
_.extend({}, mixins, {
template: T.brandPageInfo,
events: {
'click #getProductsForBrands': 'getProductsForBrands',
'click button[id^="goto__"]': 'actionOnGotoButtons'
},
onRender: function() {
this.flatIconsOnHover();
},
getProductsForBrands: function(e) {
e.preventDefault();
var searchQuery = this.model.get('name');
Globals.navigate('search?q=' + searchQuery, {trigger: true});
}
})
)
})
But the code below gives an error: Globals is undefined
define(
[
'newapp/collections/Boards', 'newapp/globals'
],
function(
BoardsCollection, Globals
) {
var boardsList;
return {
ensureBoardList: function() {
var defer = $.Deferred();
if (!boardsList || (boardsList && !boardsList.length)) {
boardsList = new BoardsCollection();
boardsList.fetch({
data: {_: (new Date()).getTime()},
success: function (boardsListCbData) {
boardsList = boardsListCbData;
defer.resolve(boardsList);
}
})
} else {
defer.resolve(boardsList);
}
return defer.done(function (boardsList) {
//make the boardsList usable for direct UI rendering by any view
return Globals.utilities.getFormattedBoardsCollection(boardsList);
});
}
}
})
How do I make Globals accessible in the second example?
Make sure you don't have any circular dependencies e.g.:
globals depends on newapp/controllers/index
newapp/controllers/index depends on the last module you displayed (we'll call it module M)
module M depends on global
Since each module depends on the other, the only thing RequireJS can do is set one of them to undefinedto "break the cycle" and get the other modules to load.
As far as I can tell, this is the most probable source of your problem, not the fact that you're returning another object.
I'd like to distribute a piece of code as an AMD module. The module depends on jQuery in a noConflict mode with two jQuery plugins.
I'd like the user to be able to use the module by simply requiring a single module file (module will be hosted on our servers), and let the dependencies be handled for them. However, for the dependencies to be loaded properly, I have to invoke require.config() and it seems to have the module paths relative to the web page, not to the invoking script. I could use the paths configuration to make all the paths absolute. That would solve the dependency problem, but would also make testing anywhere outside our production server a nightmare.
To be more specific, the module file looks roughly like this:
define (['./jquery-wrapper'], function ($) {
...
return module;
});
And the jquery-wrapper.js file in the same directory looks like this:
require.config ({
paths: {
'jquery-original': '//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min',
// ^ naturally, this one has to be absolute
},
shim: {
'jquery-original': {
exports: '$',
},
'../plugin/js/jquery.nouislider.min': {
// ^ this path is relative to the web page, not to the module
deps: ['jquery-original'],
},
'../plugin/js/jquery.ie.cors': {
// ^ this path is relative to the web page, not to the module
deps: ['jquery-original'],
},
},
});
define (['jquery-original', './jquery.nouislider.min', './jquery.ie.cors'], function ($, slider, cors) {
// ^ these paths are relative to the module
console.log ('types: ' + typeof slider + typeof $.noUiSlider + typeof cors);
return $.noConflict (true);
});
Is there any way I can use paths relative to the module everywhere?
I think you can use separate configs to get this to work:
file structure
The other/module path simulates the other server in this example.
¦ a.js
¦ b.js
¦ c.js
¦ test.html
¦
+---other
+---module
main.js
module-file-1.js
other/module/main.js
Has a dependency, using a relative module name.
define(["./module-file-1"], function (mf1) {
console.log("load main", "deps", mf1);
return "main";
});
a.js
Has a dependency, using a relative module name.
define(["./b"], function(b) {
console.log("load a", "deps", b);
return "a";
});
b.js and c.js
Uninteresting.
define(function () {
console.log("load b");
return "b";
});
other/module/module-file-1.js
Uninteresting.
define(function () {
console.log("load module-file-1");
return "module-file-1";
});
test.html
Set up two require contexts, use each to load their own modules, and then wait for both sets of modules to load:
<script src="http://requirejs.org/docs/release/2.1.8/comments/require.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script>
var localRequire = require({
context: "local"
});
var moduleRequire = require({
context: "module",
baseUrl: "http://localhost/other/module/"
});
function deferredRequire(require, deps) {
return $.Deferred(function(dfd) {
require(deps, function() {
dfd.resolve.apply(dfd, arguments);
});
}).promise();
}
$.when(deferredRequire(moduleRequire, ["main"]), deferredRequire(localRequire, ["a", "b", "c"])).then(function(deps1, deps2) {
// deps1 isn't an array as there's only one dependency
var main = deps1;
var a = deps2[0];
var b = deps2[1];
var c = deps2[2];
console.log("Finished", main, a, b, c);
});
</script>
Console
load b
load a deps b
load c
load module-file-1
load main deps module-file-1
Finished main a b c