I have a page where everything works fine. However, my test for this page's controller FeeRuleCtrl, after it tests the code of said controller, goes on and starts testing the controller of a different state. Here's my app.js:
$stateProvider
.state('root', {
url: "/",
templateUrl: "<%= Rails.application.routes.url_helpers.client_side_path('admin/fee_suites/root') %>",
controller: 'RootCtrl',
resolve: {
feeSuites: function(FeeSuiteCrud, FeeSuite){
console.log('here');
var feeCrud = new FeeSuiteCrud(FeeSuite);
var promise = feeCrud.query();
return promise.then(function(response){
return response;
});
}
}
})
.state('fee-rule', {
abstract: true,
controller: 'FeeRuleCtrl',
template: "<ui-view/>",
resolve: {
feeTypes: function(FeeSuiteCrud, FeeType){
var feeCrud = new FeeSuiteCrud(FeeType)
var promise = feeCrud.query();
return promise.then(function(response){
return response;
})
},
feeSuites: function(FeeSuiteCrud, FeeSuite){
var feeCrud = new FeeSuiteCrud(FeeSuite);
var promise = feeCrud.query();
return promise.then(function(response){
return response;
});
}
}
})
.state('fee-rule.new', {
url: '/new',
controller: 'NewCtrl',
templateUrl: "<%= Rails.application.routes.url_helpers.client_side_path('admin/fee_suites/feeRule.html') %>",
data: { title: 'Add a New Fee Rule' }
})
.state('fee-rule.edit', {
url: "/edit/:id",
controller: 'EditCtrl',
templateUrl: "<%= Rails.application.routes.url_helpers.client_side_path('admin/fee_suites/feeRule.html') %>",
data: { title: 'Edit Fee Rule' },
resolve: {
feeRule: function(FeeSuiteCrud, FeeRule, $stateParams){
var feeCrud = new FeeSuiteCrud(FeeRule);
var promise = feeCrud.get($stateParams.id)
return promise.then(function(response){
return response;
});
}
}
});
I have an abstract state, fee-rule, because both the new and edit states share most of the same functionality.
When I go to the page's address, <host>/admin/fee_suites/new, I inspect the network tab and there are 4 server calls made:
api/v3/fee_types
api/v3/fee_suites
api/v3/fee_suites/8?association=fee_rules
api/v3/fee_types/9?association=fee_parameters
The first 2 are resolves in the fee-rule state. I take care of this like so in the test:
beforeEach(function(){
module(function($provide){
$provide.factory('feeSuites', function(FeeSuite){
feeSuite = new FeeSuite({
id: 8,
site_id: 9,
active: true
});
return [feeSuite];
});
$provide.factory('feeTypes', function(FeeType){
feeType = new FeeType({
id: 9,
name: 'Carrier Quotes',
value: 'carrier_quotes'
});
return [feeType];
});
});
inject(function($injector){
$rootScope = $injector.get('$rootScope');
$controller = $injector.get('$controller');
$httpBackend = $injector.get('$httpBackend');
scope = $rootScope.$new();
$controller("FeeRuleCtrl", {
'$scope': scope
});
});
});
The last 2 server calls are made inside FeeRuleCtrl. I test them like so:
beforeEach(function(){
var JSONResponse = {"master":[{"id":29,"fee_suite_id":8,"fee_parameter_id":1,"name":"American Express Fee","multiplier":0.045,"addend":0.0,"order":1,"order_readonly":true,"created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":30,"fee_suite_id":8,"fee_parameter_id":2,"name":"Discover Fee","multiplier":0.045,"addend":0.0,"order":1,"order_readonly":true,"created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":31,"fee_suite_id":8,"fee_parameter_id":3,"name":"MasterCard Fee","multiplier":0.045,"addend":0.0,"order":1,"order_readonly":true,"created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":32,"fee_suite_id":8,"fee_parameter_id":4,"name":"Visa Fee","multiplier":0.045,"addend":0.0,"order":1,"order_readonly":true,"created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"}]};
$httpBackend.expectGET('/api/v3/fee_suites/8?association=fee_rules').respond(JSONResponse);
JSONResponse = {"master":[{"id":25,"fee_type_id":9,"name":"UPS Published Quote","value":"ups_published_quote","parameter_type":"currency","created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":26,"fee_type_id":9,"name":"FedEx Published Quote","value":"fedex_published_quote","parameter_type":"currency","created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":27,"fee_type_id":9,"name":"UPS Negotiated Quote","value":"ups_negotiated_quote","parameter_type":"currency","created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":28,"fee_type_id":9,"name":"FedEx Negotiated Quote","value":"fedex_negotiated_quote","parameter_type":"currency","created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"}]};
$httpBackend.expectGET('/api/v3/fee_types/9?association=fee_parameters').respond(JSONResponse);
$httpBackend.flush();
});
it('should set currentFeeRuleNum', function(){
expect(scope.FeeSuite.currentFeeRuleNum).toEqual(4);
});
When I run my test I get the following error:
Error: Unexpected request: GET /api/v3/fee_suites/
I know it is coming from root state's resolve function feeSuites because the test also prints to the console log the word 'here'.
I cannot figure out why it seems like the test doesn't stop and starts testing the RootCtrl in the root state. Could it have anything to do with the fact that state fee-rule is abstract? Also NewCtrl is defined but it is empty.
After some more googling with different keywords, turns out in my test I need to mock the $state variable inside FeeRuleCtrl. That fixed the problem.
It works when the first bind occurs,I want the list rebind data when a select input(id is 'checkin') changes,what should I do?
var m = angular.module('users', []);
m.controller('UserList', function($scope) {
$scope.getdata = function() {
var u = "http://www.example.com/admin?queryusers";
var userlist = this;
var body = {
activityid: "10",
checkin: $('#checkin').val()
};
$.ajax({
url: u,
type: "post",
async: false,
data: body,
success: function(data) {
userlist.users = data;
}
});
return userlist.users;
};
this.users=$scope.getdata();
$('#checkin').change(function() {
this.users=$scope.getdata();
$scope.$apply();
});
});
Your Problem is you are using jQuery in angularjs that is missing with
angular digest cycle thats why binding is not updating on html.
You shouldn't use $.ajax inside angular, that will messed up with angular digest cycle, you need to use $http call that will do ajax call safely, & update the binding after ajax gets completed.
Also you shouldn't use jquery event on element inside angular, you have to replace that change event to use ng-change then there will not need to worry about digest cycle.
Markup
<input type="checkbox" id="checkin" ng-model="myCheckbox" ng-change="getdata()"/>
Controller
var m = angular.module('users', []);
m.controller('UserList', function($scope, $http) {
$scope.getdata = function() {
var u = "http://www.example.com/admin?queryusers";
var userlist = this;
var body = {
activityid: "10",
checkin: $('#checkin').val()
};
$http({
url: u,
method: "post",
//async: false,
data: body,
}).success(function(data) {
userlist.users = data;
});
};
this.users = $scope.getdata();
//below code will call getdata directly from html on ng-change
//$('#checkin').change(function() {
// this.users = $scope.getdata();
// $scope.$apply();
//});
});
I have my application set up in the following way. It does not allow me to trigger any "java-script routes"- after loading the page-- after I navigate to this page with a sub-domain extension to the url I enter.
//Create App
App = new Backbone.Marionette.Application();
//APP Regions
App.addRegions({
displayRegion: "#displayRegion"
});
//Routing controller
someController = {
usersarea: function () {
App.displayRegion.show(userList_ITEM);
alert('Users');
},
login: function () {
App.displayRegion.show(login_view);
alert('Login View');
}
};
//Router
MyRouter = new Marionette.AppRouter({
controller: someController,
appRoutes: {
"users": "usersarea",
"login": "login",
}
});//MyRouter
// Application Views
userList_ITEM_proto = Backbone.Marionette.ItemView.extend({
template: "#userList_ITEM"
});
login_view_proto = Backbone.Marionette.ItemView.extend({
template: "#login_view"
});
//Before STARTS
App.on('initialize:before', function () {
if (!Backbone.History.started) Backbone.history.start();
alert('It works');
login_view = new login_view_proto;
userList_ITEM = new userList_ITEM_proto;
});
//After START
App.on('initialize:after', function (options) {
console.log('Initialization Finished');
});
//At Start
App.on('start', function (options) {
alert('It works');
});
App.start();
You're trying to use view instances in someController before you've instantiated them.
http://jsfiddle.net/ddL4n/28/
You have a number of dependency issues in this script and should consider using Marionette's modules or Require.js to manage them.
I have an application using backbone but whenever I call the fetch() method for the collection it returns undefined:
// App
(function () {
window.app = {};
app.collections = {};
app.models = {};
app.views = {};
$(function () {
app.collections.complaintTypes = new app.collections.ComplaintTypesCollection();
app.views.complaintTypesView = new app.views.ComplaintTypesView({ collection: app.collections.complaintTypes });
});
})();
// Collections
(function (collections, model) {
collections.ComplaintTypesCollection = Backbone.Collection.extend({
initialize: function () {
this.fetch();
},
model: model,
url: '/api/ComplaintTypes'
});
})(app.collections, app.models.ComplaintType);
// Models
(function (models) {
models.ComplaintType = Backbone.Model.extend({
idAttribute: 'ComplaintTypeId'
});
})(app.models);
// Views
(function (views) {
views.ComplaintTypesView = Backbone.View.extend({
initialize: function () {
this.collection.on('reset', this.render, this);
},
render: function () {
console.log(this.collection);
}
});
})(app.views);
But this doesn't return anything? If I use fiddler and go to my URL: /api/ComplaintTypes I do retrieve data back so I'm not sure what Im doing wrong here?
Deleting "model" line in the collection file worked for me.
Fetch is async. If you need to get the results, you need to pass a "success" handler, i.e.:
function myHandler(models) {
}
...
this.fetch({success: myHandler});
I think the problem is that you create the view while the collection has not been fetched yet (because of JS's asynchronous behavior). Try this way:
$(function () {
app.collections.complaintTypes = new app.collections.ComplaintTypesCollection();
app.collections.complaintTypes.fetch({success:function(){
app.views.complaintTypesView = new app.views.ComplaintTypesView({ collection: app.collections.complaintTypes });
}});
});
And remove the initialize function from your collection.
I have Backbone view. It load content (component on backbone with AJAX):
render: function () {
var self = this;
$.get('/Blog', request, function (data) {
self.$el.html(data);
model.trigger('blogContainerLoaded');
});
}
Component code:
window.App = {
init: function (options) {
var BlogModel = Backbone.Model.extend({
});
var model = new BlogModel();
var BlogController = Backbone.Router.extend({
routes: {
"": "posts",
"posts": "posts",
"posts/:postId": "post",
"posts/:postId/": "post",
},
posts: function (anchor) {
window.App.views.posts.render(anchor);
},
post: function (postId, commentId) {
window.App.views.post.render(postId, commentId);
},
});
var controller = new BlogController();
if (notLoaded) {
var views = {
posts: new PostListView({ model: model, controller: controller }),
post: new PostView({ model: model, controller: controller }),
};
this.views = views;
Backbone.history.start();
}
}
};
var PostListView = Backbone.View.extend({
el: $(".posts"),
events: {
"click .js-edit-message": "editMessage",
"click .js-open-post": "navigateToPost",
"click .js-move-post": "move"
},
editMessage: function () {
debugger;
},
navigateToPost: function () {
debugger;
},
move: function () {
debugger;
},
All html code loads with ajax.
It works, but events (clicks and other) not firing!
When I load Blog component without AJAX - events working. Please Help!
Most likely your view is instantiated before your ajax call is complete. Because ajax is asynchronous, while it does load (eventually) your code to initialize the view runs first. Thus, when your view is looking to attach the events to your DOM, the necessary elements are not present.
This would also explain why when you eliminate the ajax portion of it, the code works (it's now synchronous and the DOM is present before you initialize your view code.)
Try putting your initialization code inside the ajax callback like this: (if possible)
render: function () {
var self = this;
$.get('/Blog', request, function (data) {
self.$el.html(data);
self.blogContainer = new BlogContainerView(); // Or however you do it
});
}
Another possibility is to keep your trigger.('blogContentLoaded') then attach the events at that time. Backbone.View's delegateEvents() method might be useful in your case.
Or something of that nature. Now you're guaranteed that the DOM is available for your View object to attach events to.