I am trying to get a piece of code to work where I don't have total control over half of the code. In short, there is 1 main controller that has an object that is then obtained from a second controller. When main controller 1 updates the object, controller 2 never sees it. I think this is because the 2 controllers aren't watching the object/properties (copies?). If you notice, the Angular Binding to {{Title}}, this is where the issue is visible as the "Title" never gets updated in the second controller.
Here is some sample code that shows the problem. Currently, the code does a 3 seconds loop to get the object again, and reassign it to the second controller.
The code here I can't really touch. I have source, but it spaghetti and I am just generalizing what is here.
// html I can't really change, outside my world.
<div id="mainApp" ng-app="MainApp" ng-controller="mainController">
</div>
// code I can't "really" change, non-angular (can't use $http).
$ajax(get...)
.success(function (result) {
$('#element').html(result);
})
The code below is fairly separated and I can tinker with it. The HTML is returned from a service called by the $ajax call above.
// code I can change (the "result", or html returned from the service)
// containerController.js
var containerController = function ($scope, $timeout) {
$scope.models = {
item: null;
}
$scope.getItem = function() {
var mainAppScope = angular.element($('#mainApp')).scope();
$scope.models.item = mainAppScope.GetItem();
}
$scope.getItem();
// HACK WORK AROUND
// Get the item from the mainController.
var itemSync = setInterval(function () {
$scope.getItem();
$scope.$apply();
}, 3000);
}
The HTML returned from the service (it's really an ASP.NET MVC Partial View)
// HTML
<div id="container" ng-app="containerApp" ng-controller="containerController">
<!-- This will bind the first time, but won't syncronize when other controller updates -->
<!-- the controller is currently doing a loop to do so, not good. -->
<div>{{models.item.Title}}</div>
</div>
<script type="text/javascript" src="~/Scripts/controllers/containerController.js"></script>
<script type="text/javascript">
// This will inject the controller and new app into angular.
var container = document.getElementById('container');
var containerApp = angular.module('containerApp', []);
containerApp.controller('containerController', ['$scope', containerController]);
angular.bootstrap(angular.element(container), ['containerApp']);
</script>
You could change hacky part to use $interval which will manage to run digest cycle after each interval
var itemSync = $interval(function () {
$scope.getItem();
}, 3000);
To sync object from parent controller to child controller you could use object structure of model, that will is nothing but Javascript prototypal & will update data.
$scope.pageData = {}; //declare this in parent controller
$scope.pageData.title = 'Title 1' //use this where you want to change in child controller
Related
Given the dart code
class LandingController {
bool hideDiv = false;
void doit() {
new JsObject(context['loginControls']).callMethod('fadeLogin', [() {
print(hideDiv);
hideDiv = true;
print(hideDiv);
print("WTF");
}]);
}
}
which calls the JS:
var loginControls = function() {
this.fadeLogin = function(func) {
$('#landingHeader').animate({ opacity: 0 }, 500, func);
}
};
which should affect the view:
<button ng-click="doit();">Click</button>
<div id="landingHeader">Hide me after button click</div>
<div ng-if="ctrl.hideDiv == false"><img src="assets/img/ajax-loader-small.gif">Waiting for div to disappear...</div>
After a button click and a 500 millisecond delay I see in my console a "WTF" print correctly. The div, however, is still visible. Once a user action occurs, in my case a mouse click, the div magically disappears. It seems as though the controller's value is changed, but the browser doesn't receive the change to hide the div, as I've printed the controller's value in the anonymous callback.
There is a work around, but it involves setting Dart timer's to the same fade times that you use in the javascript after the JsObject call and setting your controller's values in those Timer callbacks - gross but it works.
I think you need to call scope.apply(). I think Angular just can't recognize the value change in hideDiv when doit() is called from another zone (like JS).
You usually don't need to call scope.apply() in Angular.dart but I think this is one of the exceptions.
Is the animate function the only reason you use jQuery? It might be easier to do this with Angular.darts animation features.
I have two lists which are rendered by my directive. The requirement is that user can move an item from one list to another. I have a simplified implementation of this below:-
http://jsfiddle.net/yK7Lt/
The above shows a demo of how it should behave. Notice in this I manipulate the model and the DOM auto-syncs with it.
However, the problem is I am using jquery-ui-sortable plugin. So, the user can drag and drop the item from one list to another. Since jQuery is unaware of AngularJs so it modified the DOM. Now in my directive I have placed the code to sync the underlying model with the changed DOM.
The below jsfiddle code is a simplified version of my code.
http://jsfiddle.net/5Xuz2/1/
The relevant code snippet is:-
$('#btn').on('click', function () {
var li = $('#left li').first().detach();
$('#right').prepend(li);
console.log('moved top DOM to right list');
angular.element('#left').scope().$apply(function () {
// The moment this code runs, the DOM related to i is
// marked with $$NG_REMOVED, and is removed from page.
// Also somehow the DOM related to item D too is removed.
i = itemsl.shift(); // i is global variable.
});
angular.element('#right').scope().$apply(function () {
itemsr.unshift(i);
console.log('synced data with DOM');
});
});
The problem I am facing with my implementation is that the right list empties out as soon as I sync my left list model.
What is wrong with my implementation?
Is there a better approach?
the problem here is you are manipulating DOM with both Angular and jQuery... if you remove this piece of code
var li = $('#left li').first().detach();
$('#right').prepend(li);
it is working as expected
btw. I suggest trying angular-UI instead of jQueryUI
edit: OR you can try to refactor your code to something like this
var itemsl, itemsr, i, move;
function Model(name) {
this.name = name;
}
function Ctrl($scope) {
itemsl = $scope.itemsl = [new Model('A'), new Model('B'), new Model('C')];
itemsr = $scope.itemsr = [new Model('D')];
move = function() {
$scope.$apply(function() {
i = itemsl.slice(0,1);
itemsl.splice(0,1);
itemsr.unshift(i[0]);
i = null;
});
}
}
$(function () {
$('#btn').on('click', function () {
console.log('moved top DOM to right list');
move();
});
});
Im trying to use pass my car value to another function, which i have no idea how. i tried to place the whole function btn-info-add into .span8. But this it will execute twice on the 2nd time.
$(".span8").on("click", "table #trID", function() {
var car = ($(this).closest("tr").children("td").eq(1).html());
$('#myModal1').modal('show');
});
$("#btn-info-add").click(function() //button inside the modal
selectCourse(car); //execute ajax
});
var car; //car must be declared out of your function to be available for both functions
$(".span8").on("click", "table #trID", function() {
car = ($(this).closest("tr").children("td").eq(1).html());
$('#myModal1').modal('show');
});
$("#btn-info-add").click(function() //button inside the modal
selectCourse(car); //execute ajax
});
You can create a hidden element inside your dialog (input would be great) and assign it your desire value.
<div id="dialog" title="Basic dialog">
<p>This is the default dialog which is useful for displaying information. The dialog window can be moved, resized and closed with the 'x' icon.</p>
<input id="carvalue" type="hidden" value=""/>
</div>
Note that I created an input element (hidden, of course) which is going to store the value that I want to access later. After that, you can modify your code like this:
$(".span8").on("click", "table #trID", function() {
var car = ($(this).closest("tr").children("td").eq(1).html());
$("#carvalue").val(car); // Store value into hidden input
$('#myModal1').modal('show');
});
$("#btn-info-add").click(function() //button inside the modal
var car = $("#carvalue").val(); // retrieve value from input hidden
if(car != ""){
selectCourse(car);
}
});
This technique is commonly used in forms to pass additional information on AJAX calls. Your user will not notice its presence and you can keep working. Happy coding!
EDIT:
JQuery has a method called jQuery.data to store information into JQuery elements. So your values are going to be stored on the element itself. Your code will look like this:
$(".span8").on("click", "table #trID", function() {
var car = ($(this).closest("tr").children("td").eq(1).html());
jQuery.data("#btn-info-add", "car", car); // store data inside jQuery element
$('#myModal1').modal('show');
});
$("#btn-info-add").click(function() //button inside the modal
selectCourse(jQuery.data("#btn-info-add", "car")); //execute ajax
});
I hope it helps you. Happy coding!
I'm stuck on one particular part of my project which consists of the components mentioned in the title.
I currently have a proof of concept that works the way I want it to work:
Sammy is integrated into the knockout viewmodels (as per the tutorial
on the knockout site)
the views are loaded on demand by a controller
(so I don't have to define every single view on the application page)
In my current situation I instance the viewmodels when the application starts (if I don't instance them, Sammy will not handle the routing). The problem is where the view is loaded and swapped by Sammy. I have to make a call to ko.applyBindings for KO to bind to the view. But its bad practice to repeatedly call applybingings.
My question, how do I bind to my views that are loaded on demand? I can't call ko.applybindings since that would create a memoryleak when the view is loaded more than once.
Here is an example VM with the offending ko.applyBindings:
function serviceInfoVm() {
var self = this;
self.ObjectKey = ko.observable();
self.Service = ko.observable();
self.LoadService = function () {
$.get('ServiceData/Detail', { serviceId: self.ObjectKey() }, function (data) {
self.Service(data);
});
};
$.sammy('#content', function () {
this.get('#/service/:id', function (context) {
var ctx = context;
self.ObjectKey(this.params['id']);
self.LoadService();
$.get('Content/ServiceInfo', function (view) {
ctx.app.swap(view);
ko.applyBindings(self);
});
});
}).run();
};
Anyone with some pointers and/or solutions to this problem?
You have the Sammy code in the viewmodel, which can work great if that viewmodel will be present and you want sub viewmodels and views to be loaded. So I assume that is what you are trying to do. Food for thought ... separate the sammy code into its own module (I call mine router in router.js) and let it manage the navigation separate from any viewmodel.
But back to your code ... you could set up your subviews and subviewmodels and use applybindings on them prior to the sammy.get being called. Basically, you are registering your routes in advance. Then the sammy.get just navigates to the new view, which is already data bound.
Not a solution but another approach:
Ended up abandoning the idea of loading the views dynamically.
Now my views are always present in the page and the visibility is triggered by this code:
var app = function () {
var self = this;
self.State = ko.observable('home');
self.Home = ko.observable(new homepageVm());
self.User = ko.observable(new userInfoVm());
$.sammy(function () {
this.get('#/', function (context) {
self.State('home');
});
this.get('#/info/:username', function (context) {
self.State('user');
self.User().UserName(context.params['username']);
self.User().LoadInfo();
});
}).run();
};
And the div visibility is triggered this way:
<div id="homeView" data-bind="with: Home, visible: State() === 'home'">
This way the ko.applyBindings only needs to be called once when the app starts.
The viewmodel above is bound to our shell page.
More on this here
Calling applyBindings on the specific element in the returned template is an option:
ko.applyBindings(viewModel, htmlNode)
Also see this question with regard to lazy loading templates: knockout.js - lazy loading of templates
And docs here for applyBindings: http://knockoutjs.com/documentation/observables.html
I am new to Backbone and started by working through the Todos example. After that I created a new version of the example, for Contacts rather than Todos, that uses a Ruby on Rails web app and and it's associated REST API rather than localstorage for persistence. After making the modifications I am able to successfully have the Backbone app update the Rails app but I cannot get the Backbone views to render the data that the Backbone app receives from the Rails REST API. I have stepped through the code in the debugger and can see that:
the events that call the functions to populate the views are being bound to the collection of models
when I fetch the model data the collection is getting updated with the data from the server
however, the reset event bound to the collection does not fire
Can anybody point me to what might be causing the reset event to not fire? My code is below:
Collection:
var ContactsList = Backbone.Collection.extend({
model: Contact,
url: 'http://localhost:3000/contacts.json',
});
var Contacts = new ContactsList;
AppView:
var AppView = Backbone.View.extend({
el: $("#contactapp"),
events: {
"keypress #new-contact": "createOnEnter"
},
initialize: function() {
this.input = this.$("#new-contact");
Contacts.bind('add', this.addOne, this);
Contacts.bind('reset', this.addAll, this);
Contacts.bind('all', this.render, this);
Contacts.fetch();
},
addOne: function(contact) {
var view = new ContactView({model: contact});
this.$("#contact-list").append(view.render().el);
},
addAll: function() {
Contacts.each(this.addOne);
},
createOnEnter: function(e) {
if (e.keyCode != 13) return;
if (!this.input.val()) return;
Contacts.create({first_name: this.input.val()});
this.input.val('');
},
});
var App = new AppView;
You're probably getting an empty jQuery selector returned by the el: $("#contactsapp") configuration in your view.
http://lostechies.com/derickbailey/2011/11/09/backbone-js-object-literals-views-events-jquery-and-el/
don't use jQuery selectors as object literal values. with Backbone's view, you can just provide the selector string:
el: "#contactsapp"
but this is a bad idea anyways. you should let the view render it's own el, and use other code to populate the "#contactsapp" element with the view:
$(function(){
view = new AppView();
view.render();
$("#contacts").html(view.el);
});
See the above link for more info.
After much debugging I found that the bound reset event was not being fired because I was using an old version of backbone.js. In the version I was using the refresh event was being fired not the reset event. Once I upgraded to the newer version of backbone.js the reset event fired