Backbone.js: convert this coffeescript into jquery - backbone.js

I'm trying to learn how to use backbone.js. Problem is that a tutorial I'm following (the Railscast) is using coffeescript, and I'd rather use jquery. I know how to change a function in coffeescript to jquery, but I didn't know what to change about this definition of a route
routes:
'': 'index'
Am I supposed to put brackets around it? The alert's not working, so I'm assuming something's wrong with the definition of the route.
This was the original coffeescript
Backboner.Routers.Entries = Backbone.Router.extend
routes:
'': 'index'
index: ->
alert "homepage"
javascript/jquery
Backboner.Routers.Entries = Backbone.Router.extend({
routes:
'': 'index'
index: function(){
alert("homepage");
}
});
Update: I made the changes that were suggested but the alert's not appearing.
Javascripts/Backboner.js
window.Backboner = {
Models: {},
Collections: {},
Views: {},
Routers: {},
init: function() {
new Backboner.Routers.Entries();
Backbone.history.start();
}
};
Javascripts/routers/entries.js
Backboner.Routers.Entries = Backbone.Router.extend({
routes: {
'': 'index'
},
index: function(){
return alert('homepage');
}
});
Second update:
Putting document ready into file
window.Backboner = {
Models: {},
Collections: {},
Views: {},
Routers: {},
init: function() {
new Backboner.Routers.Entries();
Backbone.history.start();
}
};
$(document).ready(function() {
return Backboner.init();
});
third update
the manifest file
//= require jquery
//= require jquery_ujs
//= require underscore
//= require backbone
//= require backboner
//= require_tree ../templates/
//= require_tree .//models
//= require_tree .//collections
//= require_tree .//views
//= require_tree .//routers
//= require_tree .

The short answer is yes, you are supposed to put curly braces around it. In the future, you can convert from CoffeeScript to Javascript right on CoffeeScript.org by clicking "Try CoffeeScript". Paste in the CoffeeScript and you will see the Javascript output.
Keep in mind, CoffeeScript has significant white space, so your original question was incorrectly formatted. It should have been:
Backbone.Routers.Entries = Backbone.Router.extend
routes:
'': 'index'
index: ->
alert "homepage"
which using the CoffeeScript website gives:
Backbone.Routers.Entries = Backbone.Router.extend({
routes: {
'': 'index'
},
index: function() {
return alert("homepage");
}
});

Sometimes when I want to quickly go back and forth between the two for a confusing situation I will use this tool... It at least makes an attempt at automated Javascript -> CoffeeScript conversion.
Use js2coffee.org at your own risk :)
Of course for Coffeescript -> Javascript you can just use the coffeescript website.

Related

Using ui-router and ocLazyLoad to load a controller and set the partial to it

I'm a complete Angular noob and trying to do some fancy stuff quickly, so forgive me if this is a dumb question.
I've created a website that uses routing, and I'm using ui-router for the routing instead of the standard Angular router. The theory is still the same - I have an index.html page in the root of my website which is the "master" or "host" page, and loginView.htm, which is a partial, exists in a separate directory.
The mainController for the project is loaded in the index.html page. Referencing this controller does NOT cause an error or problem.
What I'd like to do, in order to keep code manageable and small, is have the custom controller for a partial page lazy load when I load the partial, and then associate that partial page with the newly loaded controller. Makes sense, right? I don't want to load all the controllers by default, because that's a waste of time and space.
So my structure looks like this (if it matters to anyone):
Root
--app/
----admin/
------login/
--------loginView.html
--------loginController.js
--mainController.js
index.html
This is my loginController code. For testing purposes, I have made the mainController code match this exactly.
var loginController = function ($scope, $translate) {
$scope.changeLanguage = function (key) {$translate.use(key); };
};
angular.module('app').controller('loginController', loginController);
Finally, here is my routing code:
function config($stateProvider, $urlRouterProvider, $ocLazyLoadProvider) {
$urlRouterProvider.otherwise("/admin/login");
$stateProvider
.state('login', {
url: "/admin/login",
templateUrl: "app/admin/login/loginView.html",
controller: loginController,
resolve: {
loadPlugin: function ($ocLazyLoad) {
return $ocLazyLoad.load([
{
name: 'loginController',
files: ['app/admin/login/loginController.js']
}
]);
}
}
})
;
}
angular
.module('app')
.config(config)
.run(function ($rootScope, $state) {
$rootScope.$state = $state;
});
Now - if I remove the whole "resolve" section, and change the controller to "mainController", everything works. The page loads, the buttons work, they call the "changeLanguage" function and everything is wonderful.
But I want the "changeLanguage" feature to reside in the loginController because that's the only page that uses it. So when the code looks like it does above, an error fires("Uncaught Error: [$injector:modulerr]") and the partial page fails to load.
I don't understand what I'm doing wrong, and I'm not finding what I need via Google (maybe I just don't know the right question to ask).
Help?
Looking through the docs I cannot find the name property for ocLazyLoad#load.
Try the following:
resolve: {
loadPlugin: function ($ocLazyLoad) {
return $ocLazyLoad.load(['app/admin/login/loginController.js']);
}
}
Or, pre configure it in a config block:
app.config(function ($ocLazyLoadProvider) {
$ocLazyLoadProvider.config({
modules: [{
name: 'loginController',
files: ['app/admin/login/loginController.js']
}]
});
});
// then load it as:
$ocLazyLoad.load('loginController');

Angular "state already defined" error after running concat/uglify

I am developing an AngularJS application. To ship the code in production, I'm using this Grunt configuration/task:
grunt.registerTask( 'compile', [
'sass:compile', 'copy:compile_assets', 'ngAnnotate', 'concat:compile_js', 'uglify', 'index:compile'
]);
It's really hard to debug, and it's kind of a question to people who already ran into such problems and can point to some direction.
My main module is including those submodules:
angular
.module('controlcenter', [
'ui.router',
'ui.bootstrap',
'templates-app',
'templates-common',
'authentication',
'api',
'reports',
'interceptors',
'controlcenter.websites',
'controlcenter.users',
'controlcenter.campaigns',
'controlcenter.reports',
'controlcenter.login'
])
.run(run);
The error I get is following:
Uncaught Error: [$injector:modulerr] Failed to instantiate module controlcenter due to:
Error: [$injector:modulerr] Failed to instantiate module controlcenter.websites due to:
Error: State 'websites'' is already defined
If I remove the websites module, I get the same error for
controlcenter.users.
I am using the ui-router to handle routing inside the app.
After my build process (for integration testing), everything works just fine:
grunt.registerTask( 'build', [
'clean', 'html2js', 'jshint', 'sass:build',
'concat:build_css', 'copy:build_app_assets', 'copy:build_vendor_assets',
'copy:build_appjs', 'copy:build_vendorjs', 'copy:build_vendorcss', 'index:build', 'karmaconfig',
'karma:continuous'
]);
So maybe ngAnnotate or or concat/uglify are doing weird things here?
UPDATE 1:
It has something to do with my configuration of the modules. Here is the code:
angular
.module('controlcenter.websites',
[
'ui.router'
]
)
.config(config);
config.$inject = ['$stateProvider'];
function config($stateProvider) {
$stateProvider.state( 'websites', {
url: '/websites',
views: {
"main": {
controller: 'WebsitesController',
templateUrl: 'websites/websites.tpl.html'
}
}
});
}
When I change the name of the state to websites_2, I get an error
with 'websites_2 is already defined'.
When I remove the module completely, the next one hast the same problem inside the config file. So is the structure wrong?
Update 2:
The problem seems concat related.
It takes every JS file and adds it one after another to one, bigger file. All of my modules are at the end. The last module always has the problem with 'state already defined'. So it's not just the order of the modules appending to each other, it's something elsse...
Update 3:
I placed my code (I've excluded every Controller-Code and functions, just the scaffold) in a gist. This is the outcome after my compile process, without uglifying it.
Issue:
You have multiple files that contains a config function to configure your module, like this:
angular
.module('controlcenter.websites', [])
.config(config);
function config() {
// ...
}
The problem is that after you concatenate all files you end up with a big file with multiple declarations of config. Because of JavaScript's variable hoisting, all declarations are moved to the top and only the very last of them is evaluated, and this one is:
function config($stateProvider) {
$stateProvider.state( 'websites', {
url: '/websites',
views: {
"main": {
controller: 'WebsitesController',
templateUrl: 'websites/overview/websites.tpl.html'
}
},
data : {requiresLogin : true }
});
}
Hence, each time you .config(config) a module, you are telling Angular to configure your module with that particular configuration function, which means that it executes multiple times and tries to define the state websites more than once.
Solution:
Wrap each JavaScript file code with a closure. This way you will avoid declaring a variable/function more than once:
(function (angular) {
'use strict';
angular
.module('controlcenter.website.details', ['ui.router'])
.config(config);
config.$inject = ['$stateProvider'];
function config($stateProvider) {
$stateProvider
.state( 'websiteDetails', {
url: '/websiteDetails/:id',
views: {
"main": {
controller: 'WebsiteDetailsController',
templateUrl: 'websites/details/website.details.tpl.html'
}
},
data : {requiresLogin : true }
})
.state( 'websiteDetails.categories', {
url: '/categories',
views: {
"detailsContent": {
templateUrl: 'websites/details/website.details.categories.tpl.html'
}
},
data : {requiresLogin : true }
})
;
}
})(window.angular);
Edit:
I strongly recommend you wrap your files into closures. However, if you still don't want to do that, you can name your functions according to their respective modules. This way your configuration function for controlcenter.website.details would become controlcenterWebsiteDetailsConfig. Another option is to wrap your code during build phase with grunt-wrap.
window.angular and closures: This is a technique I like to use on my code when I'm going to uglify it. By wrapping your code into a closure and giving it a parameter called angular with the actual value of window.angular you are actually creating a variable that can be uglified. This code, for instance:
(function (angular) {
// You could also declare a variable, instead of a closure parameter:
// var angular = window.angular;
angular.module('app', ['controllers']);
angular.module('controllers', []);
// ...
})(window.angular);
Could be easily uglified to this (notice that every reference to angular is replaced by a):
!function(a){a.module("app",["controllers"]),a.module("controllers",[])}(window.angular);
On the other side, an unwrapped code snippet like this:
angular.module('app', ['controllers']);
angular.module('controllers', []);
Would become:
angular.module("app",["controllers"]),angular.module("controllers",[]);
For more on closures, check this post and this post.
If you check it in the concatenated file, do you have the states defined twice? Can it be that you are copying the files twice? Check the temporary folders from where you are taking the files (also in grunt config, what you are copying and what you are deleting...).
So I had the same problem but with the following setup:
yeoman angular-fullstack (using typescript)
Webstorm
With the angular-fullstack configuration, the closures were already implemented (as Danilo Valente suggests) so I struggled quite a bit until I found out that in Webstorm, I had the typescript compiler enabled which compiled all of my *.ts files to *.js. But since Webstorm is so 'smart', it does not show these compiled files in the working tree. Grunt however concatenated of course all files regardless if it is typescript of JS. That's why - in the end- all of my states were defined twice.
So the obvious fix: Disabled typescript compiler of webstorm and deleted all the generated *.js files and it works.

ui-router with customer controller and ng-strict-di

so, following best practice I've started using ng-strict-di. It's worked well so far, but I have hit the following problem using ui-router
// nested list with custom controller
.state('dashboard.list', {
url: '/list',
templateUrl: 'partials/dashboard-list.html',
controller: function($scope) {
$scope.dogs = ['Bernese', 'Husky', 'Goldendoodle'];
}
})
this causes angular to barf with the "Error: error:strictdi
Explicit annotation required" error.
I know that I should be using the inline bracket notation, or $inject, but obviously can't put it in this code as is.
I was thinking that I could declare the controller in another part of the script, with $inject and then just reference it in the code ?
function GoodController1($scope) {
}
GoodController1.$inject = ["$scope"];
and then
// nested list with custom controller
.state('dashboard.list', {
url: '/list',
templateUrl: 'partials/dashboard-list.html',
controller: GoodController1
})
would this work ? Are there any problems with this approach ?
There are no problems, with this approach. I am using typescript, and the generated syntax of controlelr class is almost the same as yours.
Here is a working plunker
...
// the contoller funciton to be instantiated
// by angular using new
var GoodController1 = function($scope){
$scope.title = "good title";
};
// set of dependencies
// (in typescript that would be a static property)
GoodController1.$inject = ["$scope"];
// before angular 2.0, this is the must
// we still have to register controller in the module
app
.controller('GoodController1', GoodController1)
...
and later in state:
.state('good', {
url: "/good",
templateUrl: 'tpl.html',
controller: "GoodController1",
})
check it here

AngularJS : UI-router setting configurations using directives

I understand the configuration settings for UI-router and it is working fine. Now I am trying to move the following configuration to directives. So that, the length of code will be reduced in js file. May be, it is poor design but I want to achieve this :)
Current Configuration (As per UI-router design)
//Router Configuration
angular.module('myApp', ['ui.router']).config(function($stateProvider) {
$stateProvider.state('addCustomer', {
url: "/addCustomer",
templateUrl: 'views/customer/add_customer.html',
controller: 'addCustomerController'
});
...No of configuration, list is very big...
});
//In Template
<a ui-sref="addCustomer">Add Customer</a>
What I am trying to change
//Router Configuration
angular.module('myApp', ['ui.router']).config(function($stateProvider) {
});
//In Template
<a ui-sref="addCustomer" router-info="{'url':'/addCustomer', 'templateUrl':'views/customer/add_customer.html', 'controller':'addCustomerController'}">Add Customer</a>
//Directive- Dynamic routing
angular.module('myApp').directive('routerInfo', function(){
var directive = {};
directive.restrict = 'A';
directive.compile = function(element, attributes) {
var linkFunction = function($scope, element, attributes) {
//Here I understand, I can not inject stateprovider. So kindly suggest any other way to do this
//$stateProvider.state(attributes.uiSref, attributes.routerInfo);
}
return linkFunction;
}
return directive;
});
How to add UI-router configuration from directives? Is there any API available to set? or any other better way to handle this... My intention is to reduce code in config block.
If you're trying to avoid having one giant config block, simply use multiple config blocks and put each one in its own file. There's no reason not to put configuration code in a config block, it just sounds like you need to approach it a better way, so split it up into smaller blocks.
Example:
// config/routes/user.routes.js
angular.module('yourModule')
.config([
'$stateProvider',
function($stateProvider) {
$stateProvider
.state('user', {
url: '/users/:userName',
abstract: true,
// etc
})
.state('user.main', {
url: '',
//etc
})
.state('user.stuff', {
url: '/stuff',
// etc
})
;
}
])
;
And repeat for each set of routes:
// config/routes/more.routes.js
angular.module('yourModule')
.config([
'$stateProvider',
function($stateProvider) {
$stateProvider
.state('more', {
url: '/more-routes',
//etc
})
;
}
])
;

Requirejs2: How to handle own files?

I have configured requirejs to load the core libs (jquery, underscore, backbone).
Now I would like to add my backbone models, controllers, views, etc to be loaded asyncronly
I found a lots of tutorials to this topic and lots of "ready" boilerplates unfortunatly I mentioned that most approaches are depreceated or rather complicated (even there are better approaches).
One example is how I configured requirejs for the main libs:
https://stackoverflow.com/a/10914666/1309847
So how do I load Backbone Views, Models, Collections, Routers, Controllers and Templates with a simple and valid Requirejs configuration?
I followed youre advice but get some strange error
main.js
require.config({
paths: {
jquery: 'vendors/jquery/jquery',
underscore: 'vendors/underscore/underscore',
backbone: 'vendors/backbone/backbone'
},
shim: {
underscore: {
exports: '_'
},
backbone: {
deps: ['underscore', 'jquery'],
exports: 'Backbone'
}
}
});
require(['app'], function(app){
});
app.js
define(['jquery', 'underscore', 'backbone'], function($, _, Backbone){
var Message = new Backbone.Model.extend({
//idAttribute: '_id',
//defaults: { body: '' }
//url: function(){ return this.id ? '/messages/' + this.id : '/messages'; }
});
var newMessage = new Message({ body: 'hi' });
newMessage.save();
});
The error occours in app.js:
Uncaught TypeError: Object [object Object] has no method 'apply'
When I comment the new Backbone.Model.extend part I don't get any error anymore.
in my experience, the best way to bootstrap your application is by creating a Backbone.Router. So you can associate urls with your application functionality.
If you are using RequireJS+Backbone, you probably have a main.js where RequireJS is configured (paths, shims, etc). The first call to "require" is used to load a initial script in order to bootstrap the whole app.
For example:
/**
* main.js - RequireJS bootstrap
*/
require.config({
paths: {
//your paths
},
shim: {
//your shims
}
});
require(
[
'app' //app.js is at the same directory as main.js
],
function(app) {
app.init();
}
);
then in app.js you can create a new Router instance, or you can just start creating Views and Models.
For further reference: http://addyosmani.github.com/backbone-fundamentals/
So as I have now understood right: You have to wrap a requirejs function around youre own custom js file.
The function is called define. The first parameter is an array of the dependencies which you have defined in the main.js file or a relative path to another custom js from you.
The second parameter is the callback which holds the original file. Important is that you return the object, function, array or variable which you want to share.
The whole thing looks like this:
define(
['underscore', 'backbone'], // the dependencies (either relative paths or shortcuts defined in main.js
function(_, Backbone){ // the return statement of the deps mapped to a var
var MessageModel = Backbone.Model.extend({ // the original code, file
defaults: { body: '' },
initialize: function(){}
});
return MessageModel; // the return statement, sharing the "final result", sometimes you return the initialize parameter
});
The same for a collection wrapping the models:
define(
['jquery', 'underscore', 'backbone', 'models/message_model'], // deps and the last one is the relative path
function($, _, Backbone,MessageModel){ // same as above explained
var MessageCollection = Backbone.Collection.extend({
model: MessageModel,
initialize: function(){}
});
return MessageCollection;
});
I now only have to figure out how I can bootstrap to whole application. But I think I need more knowledge of backbone to do this :)

Resources