How to use RequireJS with Knockout - javascript

My setup so far:
<script src="/common/js/require.configure.js"></script>
<script src="/common/js/lib/require.js" data-main="/common/js/app.js"></script>
require.configure.js
var require = {
baseUrl: '/',
paths: {
"jquery": "/common/js/lib/jquery",
"fastclick": "/common/js/lib/fastclick",
"knockout": "/common/js/lib/knockout",
"common": "/common/js/scripts/common"
}
};
The top three paths are obviously just libraries that I am using for my application. The last file "common" is a collection of functions that are global to my application such as opening the main menu, giving messages to the user, or binding handlers, etc.
app.js
define(["jquery", "knockout", "fastclick", "common"], function (){
});
I know that requireJS always needs a data-main file to run initially. But what does this above code really do? I trying to follow tutorials online but it is not helping. I'm guessing that by defining those strings in the array, it looks it up in the configuration file and is loading in those files, but how are those files then accessed or used? I'm guessing that I can simply then just "require" those same strings and they will be available to me in my functions?
common.js (simplified for Stack Overflow)
require(["knockout"], function (ko) {
var appViewModel = {};
appViewModel.loaded = ko.observable(false);
});
By wrapping everything in the require() I think that this is injecting the dependencies of needing knockout.
App's First Page - login.html (simplified for S.O.)
In the first page of the app, I define a <script> tag with the following
require(["jquery", "knockout", "fastclick", "common"], function ($, ko, FastClick)
{
$(function(){
appViewModel.loginData = {
email : ko.observable(),
password : ko.observable()
};
});
});
And the resulting error when trying to run is that
Uncaught ReferenceError: appViewModel is not defined
despite the fact that I have included "common" in the require([]).
What am I missing here? I think that I may be completely misunderstanding what "require" and "define" do in requireJS, so that would be a good basis of an answer for me.

i think you want to do something like that:
Modules that define global obj
require(["knockout"], function (ko) {
window.appViewModel = {};
window.appViewModel.loaded = ko.observable(false);
});
Modulw that popule the obj:
require(["jquery", "knockout", "fastclick", "common"], function ($, ko, FastClick)
{
window.appViewModel.loginData = {
email : ko.observable(),
password : ko.observable()
});

Related

RequireJS - config; paths and shims not working

I have a common.js that defines the config for RequireJS:
(function(requirejs) {
"use strict";
requirejs.config({
baseUrl: "/js",
paths: {
"jsRoutes": "http://localhost:8080/app/jsroutes"
},
shim: {
"jsRoutes": {
exports: "jsRoutes"
}
}
});
requirejs.onError = function(err) {
console.log(err);
};
})(requirejs);
I then have a main.js file that I try to use the jsRoutes path that I created:
require(["./common", "jsRoutes"], function (common, routes) {
// do something interesting
});
but I do not load the resource at http://localhost:8080/app/jsroutes instead it tries to load http://localhost:8080/js/jsRoutes.js when the main.js is executed. But this resouce doesn't exist and I get a 404.
How do I get the jsRoutes path to work correctly? Also do I need the shim (I'm not 100% sure)?
I can debug into the common.js file, so the paths should be being set, right?
Update 1
I believe that the paths should work as I have them defined shouldn't they?
Excerpt from http://requirejs.org/docs/api.html
There may be times when you do want to reference a script directly and not conform to the "baseUrl + paths" rules for finding it. If a module ID has one of the following characteristics, the ID will not be passed through the "baseUrl + paths" configuration, and just be treated like a regular URL that is relative to the document:
Ends in ".js".
Starts with a "/".
Contains an URL protocol, like "http:" or "https:".
Update 2
I may have misread the docs, I can solve the issue by defining the main.js like so:
require(["./common", "http://localhost:8080/app/jsroutes"], function (common, routes) {
// do something interesting
});
I was rather hoping not to have to pass round this rather unwieldy URL though.
Update 3
Further investigation of the docs revealed the following snippet:
requirejs.config({
enforceDefine: true,
paths: {
jquery: 'http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min'
}
});
//Later
require(['jquery'], function ($) {
//Do something with $ here
}, function (err) {
//The errback, error callback
//The error has a list of modules that failed
var failedId = err.requireModules && err.requireModules[0];
if (failedId === 'jquery') {
//undef is function only on the global requirejs object.
//Use it to clear internal knowledge of jQuery. Any modules
//that were dependent on jQuery and in the middle of loading
//will not be loaded yet, they will wait until a valid jQuery
//does load.
requirejs.undef(failedId);
//Set the path to jQuery to local path
requirejs.config({
paths: {
jquery: 'local/jquery'
}
});
//Try again. Note that the above require callback
//with the "Do something with $ here" comment will
//be called if this new attempt to load jQuery succeeds.
require(['jquery'], function () {});
} else {
//Some other error. Maybe show message to the user.
}
});
It would seem here that the jquery path is working with a full URL
I'm fairly certain your path should be relative to your baseUrl. So giving it the domain & port is screwing it up.
EDIT: My standard require js config... it might help?
require.config({
baseUrl : "./",
paths: {
// Bower Components
respond: 'assets/bower_components/respond/dest/respond.min',
// Libraries & Polyfills
polyfillGCS: 'assets/js/lib/polyfill-getComputedStyle',
polyfillRAF: 'assets/js/lib/polyfill-requestAnimationFrame',
polyfillPro: 'assets/js/lib/polyfill-promise',
easing: 'assets/js/lib/easing',
signalsui: 'assets/js/lib/Signals.ui',
signalsjs: 'assets/js/lib/Signals',
domReady: 'assets/js/lib/domReady', // TODO: Still needed?
// Modules
app: 'assets/js/es5/app'
},
shim: {
app: {
deps: ['signalsjs']
},
signalsjs: {
deps: ['easing', 'polyfillGCS', 'polyfillRAF']
},
signalsui: {
deps: ['signalsjs']
}
}
});
// Load the app
require(['app']);
Ok I realised what I was doing wrong. It was simple really.
I had dependencies for ./common and jsRoutes being passed to the same module so jsRoutes was being required before it had been defined by the config.
I moved the dependency from the main.js file to where it was actually needed and things worked as I expected.
I had the same problem but I fixed it by changing my code like your original code:
require(["./common", "jsRoutes"], function (common, routes) {
// do something interesting
});
to this:
require(["./common"], function (common) {
require(["jsRoutes"], function (routes) {
// do something interesting
});
});
I'm guessing that, in the original code, RequireJS attempts to load the "jsRoutes" dependency before the configuration changes made in "common" are applied. Nesting the require calls effectively ensures that the second dependency is loaded only after the first is evaluated.

Using knockout.simpleGrid.3.0.js with Require.js

I am using require.js with knockout on a website and would like to use the simpleGrid example from this link http://knockoutjs.com/examples/grid.html however I cannot include kncokout.simpleGrid.3.0.js with Require.
I have tried wrapping the plugin with
define(['jQuery', 'knockout'], // Require knockout
function($, ko) {
});
This does not work it seems the problem occurs with the templates.
Any help appreciated
In your require config, you should create a path to the simpleGrid library and use the shim to tell it that it depends on Knockout so that your libraries are loaded in the correct order. Here's an example:
var require = {
paths: {
'jquery': 'lib/vendor/jquery-2.0.3',
'ko': 'lib/vendor/knockout-3.0.0',
'koSimpleGrid': 'lib/vendor/knockout.simpleGrid.3.0'
},
shim: {
'koSimpleGrid': {
deps: ['ko']
},
}
};
And then you could copy paste the view model code from the example inside of a define like this:
define(['jquery', 'ko', 'koSimpleGrid'], function ($, ko) {
// VIEW MODEL GOES HERE
});
I agree the problem seems to be with the code that writes the grid template. Essentially, because requirejs loads modules asynchronously, document.write() can't be used - writing of the document has finished by the time a module executes. This StackOverflow answer explains it well I think.
I got round it by instead creating and appending the required script tag templates using dom methods:
templateEngine.addTemplate = function(templateName, templateMarkup) {
var scriptTag = document.createElement('script');
scriptTag.type = 'text/html';
scriptTag.id = templateName;
scriptTag.text = templateMarkup;
document.getElementsByTagName('body')[0].appendChild(scriptTag);
};
My amd version is in this gist.

Dojo AMD loader executing define callbacks with empty / missing dependencies

I'm new to Dojo (1.7) and totally willing to accept I'm being an idiot (I just hope not). I am more comfortable using require.js for AMD but I'm using a 3rd-party (ESRI) mapping API that forces Dojo on me and uses its AMD, meaning I get nasty errors if I try and use require.js as well.
I have defined a module with dependencies on Backbone and Underscore (I might ultimately go with Dojo's MVC but I don't think this problem is specific to Backbone so I want to get it figured out). Bizarrely it seems that Dojo is executing the callback within my define when the module is loaded, and at this point the dependencies (Underscore and Backbone) are empty objects {}. An error occurs within my callback's return Backbone.View.extend... because Backbone's View property doesn't exist.
I know that Backbone is dependent on Underscore, and so far I have no idea how to ensure Underscore is loaded first without using a hacky-looking require({async:0},['test1.js','test2.js'.... However, in this case Underscore is also an empty object, so the define's callback is executed before either dependency is loaded???
EDIT I see both Underscore and Backbone HTTP requests and 200 responses in the console before this error occurs, so I assume there are no problems in their references.
Something concrete...
index.html:
<script type="text/javascript">
var dojoConfig = {
tlmSiblingOfDojo: false,
packages: [
{name: 'app', location: '/js'},
{name: 'lib', location: '/js/lib'}
],
aliases: [
['Backbone', 'lib/backbone-0.9.2.min'],
['_', 'lib/underscore-1.3.3.min'],
['$', 'lib/jquery-1.8.0.min'],
['ready', 'dojo/domReady']
]
};
</script>
<script type="text/javascript" src="http://serverapi.arcgisonline.com/jsapi/arcgis/?v=3.1"></script>
<script type="text/javascript">
require([ 'app/app' ], function(App) {
App.initialize();
});
</script>
app.js:
define(['app/views/main-view'], function(MainView) {
return {
initialize : function() {
new MainView();
}
};
});
main-view.js:
define(['_', 'Backbone', 'ready!'], function(_, Backbone) {
// *** ERROR THROWN HERE, Backbone = {}, _ = {} ***
return Backbone.View.extend({
el: 'main',
initialise: function() {
console.log('main view initialising');
this.render();
},
render: function() {
console.log('main view rendering');
}
});
});
Can anybody (please) tell me what's going on here? Also any alternate suggestions on loading Underscore before Backbone would be really helpful!
Just change the order of dependencies:
define(['_', 'Backbone', 'ready!'], function(_, Backbone) { /*...*/});
Because your have ready! plugin in _ variable and _.js in Backbone variable.
Edit: You can nest it:
define(["_", "require"], function(_, require) {
require(["Backbone"], function(Backbone) {
// your code here
})
})
Also, if underscore or Backbone are not AMD modules, local function variables will cover them.

What's the correct way to expose a requireJS module to the global namespace?

I want to expose a Javascript API as a standalone library without polluting their global namespace. I've created the wrapper so I don't pollute their own requireJS according to http://requirejs.org/docs/faq-advanced.html. I've simplified what I have so far as below, but I'm not sure if this is the correct way or if I should be doing it some other way.
var MyApi = MyApi || {};
var MyApiRequireJS = (function() {
// require.js pasted here
return {requirejs: requirejs, require: require, define: define};
})();
(function(require, define, requirejs) {
require.config({
baseUrl: 'js/scripts',
waitSeconds: 30,
});
define( 'myapi', ['jquery', 'underscore'],
function($, _) {
$.noConflict(true);
_.noConflict();
function api(method, args, callback) {
// do stuff here
}
return {api: api};
}
);
require( ['myapi'], function( myapi ) {
MyApi = myapi;
});
}(MyApiRequireJS.require, MyApiRequireJS.define, MyApiRequireJS.requirejs));
Sites using this library would include a script tag referencing the above code and then call the api using
MyApi.api('some_remote_method', {foo: 'bar'}, function(result) {
// handle the result
});
I think you're trying to anticipate someone else's problem by making it your problem, but I don't think you can really reasonably do that. The page that you link to is designed to let people who already have Javascript globals named "require" or "define" rename the RequireJS globals to something different. It's not intended to create two separate RequireJS instances that independently resolve dependencies.
That said, if you are really trying to minimize the namespace pollution, then you should expose exactly one name -- MyApi. Write one monster closure that includes your private copy of RequireJS as well as your API code, and have it return only the methods you want to expose on your API.
It's probably much friendlier/simpler to deliver your API in two versions, one that defines a requireJS module, and one that has no requireJS dependency.

Unable to Create Backbone View

I'm continually getting index.js:7 Uncaught TypeError: Cannot read property 'View' of null, which indicates that Backbone is not loaded/present, however, when I review the loaded resources on the page backbone-min.js is present.
Since there are no 404 errors, I believe the issue is with the script itself. Does anyone see any issues with the script below?
NOTE: For convenience I uploaded my code here. The zip file contains all the relevant js files. If you scroll to the bottom of the webpage, you should see a "slow
download" button, once you click it you'll be prompted to enter a
captcha code. After entering the code, the actual download button
(under the "slow download" button) will appear within a few seconds.
View: index.js
define([
"jQuery",
"Underscore",
"Backbone"
// I've tried using the modules above as well as direct loading using order! as seen in the following lines.
//"order!libs/jquery/jquery-min",
//"order!libs/underscore/underscore-min",
//"order!libs/backbone/backbone-min",
],
function($, _, Backbone){
console.log(_) // prints "undefined"
console.log(Backbone) // prints Object
var IndexView = Backbone.View.extend({ // At this line I now get: Uncaught TypeError: Cannot call method 'extend' of undefined
render: function(){
$(this.el).html("<h1>Welcome Dan!</h1>");
$("body").html(this.el);
}
});
return new IndexView();
});
So the key to this issue is changes in underscore.js. Specifically the fact it now supports AMD (Asynchronous Module Definition). The fact that underscore no longer attaches itself to the global namespace when require is detected is breaking the scheme used to allow a standard asynchronous require syntax but still maintain synchronous loading.
Now that JQuery, Underscore & Backbone (0.5.3 does not register itself, you need a this) support async loading, you are able to abandon those hacked libraries in favor of the standard ones and require the names those libraries register themselves with. Like this:
Main.js
require.config({
baseUrl: "js",
paths: {
jquery: "libs/jquery/jquery",
underscore: "libs/underscore/underscore",
backbone: "libs/backbone/backbone"
},
waitSeconds: 10
});
require([
"app"
],
function(App){
App.initialize();
console.log("Main initialized...");
});
index.js
define([
"jquery",
"underscore",
"backbone"
],
function($, _, Backbone){
console.log(_);
console.log(Backbone);
var IndexView = Backbone.View.extend({
render: function(){
var username = getCookie("username");
var data = {username: username};
var compiled = _.template("<h1>Welcome <%= username %></h1>", data);
$(this.el).html(compiled);
$("#lt-col").html(this.el);
}
});
return new IndexView();
});
Other define's were changed to reflect the new lowercase alias's.
Pull the fixed code here
Even though Backbone 0.5.3 registers itself as an AMD module, it doesn't return anything. (Same thing for underscore) If you change your line:
function($, _, Backbone){
To
function($){
It will work. For a more requirejs-ish solution, create a module for backbone that looks like:
define(
[
'order!libraries/underscore',
'order!libraries/backbone.0.5.3'
],
function () {
return Backbone;
}
);
--UPDATE-- additional info
<head>
<title>Index2</title>
<script src="../../scripts/libraries/require.js" type="text/javascript"></script>
<script type="text/javascript"">
require({
baseUrl: 'scripts'
}, [
'order!libraries/jquery',
'order!libraries/underscore',
'order!libraries/backbone.0.5.3'
], function ($) {
console.log(Backbone);
});
</script>
</head>

Categories

Resources