Backbone - better understanding of templates [duplicate] - backbone.js

This question already has answers here:
Underscore rendering [object HTMLDivElement]
(3 answers)
Closed 8 years ago.
I use backbone inside Rails. I got a very strange behaviour and I try to understand why it happens.
My model has an optional attribute avatar_url. I use it to generate an img tag. I got the following html code:
<img src="[object HTMLInputElement]" class="avatar-bubble">
When I looked at my code, I realize that I also have this static code:
<input id="avatar_url" type="text" class="form-control" />
My conclusion is that from some reason backbone takes also existing DOm objects in account when calculating the value of a given variable.
I wanted to dig inside the template parser and debug it in order to have a better idea of the login it has but couldn't find how to.
My question is what is the logic that uses backbone in order to assign a value to the template variables.
EDIT:
In the view I have implemented a render method which does the following:
render: function() {
var viewData = this.model.toJSON();
this.$el.html( this.template( viewData ) );
return this;
}

There isn't any Backbone "magic" to handle and render templates. The render method of a view is a no-op by default, and having a template method or property is just a recommended convention - again, nothing implemented for you, no magic.
There are frameworks built on top of Backbone, like Marionette, which add that kind of automatic functionality. Without those, you'd have to dig into your Rails code to figure out what's going on. Because Backbone views don't actually add anything to the DOM, or parse templates, until you write the code for it.
(The only thing a Backbone view provides out of the box is a container element, the el, which is generated automatically on instantiation but not attached to the DOM).

Related

Angularjs dynamically added ng-model is not accessible by $scope, shows undefined

I am creating form, where few fields are dynamic, ng-model is added dynamically.
Ex.:
form.append("<input type='hidden' name='paymillToken' value='" + token + "' data-ng-model = 'formdata.token'/>");
This fields shows undefined while I try to access using $scope.formdata.token
Following is another scenario where I am adding fields via ajax.
angular.forEach(data.data, function(obj, key) {
list+='<div class="items text-center"><img src="assets/uploads/discs/'+obj.image+'" class="img-circle"><br><input type="radio" id="chkDisc'+obj.id+'" name="disc_id" value="'+obj.id+'" required data-ng-model="formdata.disc_id" /></div>';
});
$scope.discslist = $sce.trustAsHtml(list);
This model disk_id is not accessible too.
Okay, to expand on my comment and a bit more on what everyone else here is saying to you, the main issue you're having is inherent in your approach. The way you're trying to manipulate the DOM is very un-AngularJS.
In AngularJS, when you want to change what is displayed to the user (the view), you make changes to your model (your controller scope). That means, you have to set up your view to be able to respond to those changes. We do that with directives and expressions in Angular.
You're probably already using directives to respond to changes in your model whether you realize it or not. ngRepeat, ngModel, ngShow, ngIf, ngInclude, are a handful you're probably familiar with, and even forms and form elements like inputs are actually directives in Angular. When you use these, a change in your model (such as loading data into the controller scope) signals to Angular that it should check whether that change affects any of the directives in your view, and if so, respond to it by updating the view.
In order to do this, Angular needs to know which parts of the model are connected to which parts of the view. It makes these connections when it compiles the html elements that are added to the page. This compile process happens automatically when you load an Angular app. After that, it's up to us to tell Angular when to compile html that is added to the page.
More often than not, we do this without even realizing it. For example, when you use the ngView directive, it will compile the template for each route that it loads, so that all of the directives in your template are properly linked with their associated model.
I know this is a long explanation, but there are two very important points here that are essential to learning AngularJS:
To change the view, you change your model and let the directives (and expressions) on your page respond to those changes.
When you add html elements to the page, if you want AngularJS to be able to use them in your view, they must be compiled first. The compile process should be done via a directive (either a built in one or a custom one).
So, how does all of this apply to your question?
First, I'm guessing that you're breaking both rules by trying to manipulate the DOM via a controller. Even if it is possible to use $compile in a controller, using a controller to change the DOM is bad practice and simply wrong to do (read the part in that link to the doc that specifically states: Do not use controllers to: Manipulate DOM...). A good rule to remember when you're learning AngularJS is that the only time you should ever be using JQuery or JQLite inside Angular is when you are creating a custom directive.
Okay, so how do you solve your question? Use a directive. It looks like you've got a case where you're trying to iterate over an object called data and add some inputs that correspond to the data.data property. This sounds like a job for ngRepeat.
The first thing you need to do is add your data to your controller and make sure it is accessible to the view. The easiest way to do this is by injecting $scope into your controller and setting the data on a scope variable. In its simplest form, that might look something like this:
angular.module('MyApp', [])
.controller('MyController', ['$scope', function($scope){
$http.get('/some/url/that/returns/the/data').
success(function(data) {
$scope.data = data;
});
}]);
Now that we have the data somewhere that we can access from the view, we can use it with the ngRepeat directive in our html, something like this:
<div ng-controller="MyController">
<div class="items text-center" ng-repeat="disc in data.data">
<img ng-src="assets/uploads/discs/{{disc.image}}" class="img-circle"><br>
<input type="radio" id="{{'chkDisc' + disc.id}}" name="{{disc.disc_id}}" value="{{disc.disc.id}}" required data-ng-model="formdata[disc.disc_id]" />
</div>
</div>
This is a common issue. By updating the value and not the model angular has no idea that the value in the field has been updated. As the first commenter said updating in this manner is completely unnecessary when using ng-model.

Few questions regarding Angular directives

I have few questions regarding angular directives
I'm basing my questions based on the following block of codes
<ul class='parent'>
<li class='child1'></li>
<li class='child2 active'></li>
<li class='child3'></li>
</ul>
So based on the above
How can i create a directive based on the above (assuming this comes from an Angular plugin, and there is no angular directive attribute defined within the tag)?
Assuming the above is from an Angular plugin, and i don't intend to edit the plugin, how can i use the directive to see which list is currently active?
How can i do operations through the code above using Angular? With Jquery, i can easily do it as
For checking the child length
$('.parent').children().length
For iterating through the child elements
$.each($('.parent').children(), function(){ console.log(this); });
But when i tried to do it with Angular way
angular.element(document.querySelectorAll('.parents')).children().length
Somehow this part always returned me the wrong value. I guess my Jquery mindset is the reason why i couldn't get the value but still, i read somewhere that angular.element object is the equivalent of the jquery $() object, thus both should be able to use the same functions.
If i want to do event binding on the code above i should do it inside the link function correct? If i want to watch and apply changes for the code above, then i should use the directive's scope and do it inside the link function too right?
Is there easy-to-chew guide on how Angular's directives? Somehow most of those that i read are pretty much as confusing as the directive guide in Angularjs documentation itself.

How can I integrate Angular with an existing JavaScript architecture that makes heavy use of clone?

I'm working on a legacy web application (well, I say legacy, it's from about a year ago and programmed in Knockout and JQuery), and I'd like to program a new interface in Angular, with a view to replacing all other portions of the application with Angular as we go on as this is a very complicated user interface that isn't horrible enough to warrant a complete rewrite.
I would like to integrate Angular code with the existing codebase, and for my first controller I'd like to program a pop-up box which allows the user to modify some data. I created a div with a data-ng-controller of "MyController" and it all works well, but I don't seem to be able to create many of them.
If we have multiple controllers, i.e:
<div data-ng-controller="MyController">
</div>
<div data-ng-controller="MyController">
</div>
Both instances of MyController would have their own scope which is definitely want, and it works totally fine for my app this way. But unfortunately, my system works on cloning a div for every instance of the pop-up box:
<div data-ng-controller="MyController" id="myWindow">
</div>
<script>
function showWindow()
{
var dialog = $("myWindow").clone();
dialog.show();
)
</script>
When showWIndow() is called, the reference to MyController seems to travel along with it, so I don't get two instance of MyController.
I can't seem to find how to create multiple instances of the same controller and attach it though.
For example, I thought this might work:
<div id="myWindow">
</div>
<script>
function showWindow()
{
var dialog = $("myWindow").clone().setupDialog();
dialog.attr("data-ng-controller", "MyController");
dialog.show();
)
</script>
But unfortunately, the breakpoint in MyController never gets hit. I suspected that this might be due to the order of attaching, but reversing the call to attr() and show had no effect:
var dialog = $("myWindow").clone().setupDialog();
dialog.show();
dialog.attr("data-ng-controller", "MyController");
I assume that there's some method in the angular library somewhere that allows me to instantiate a controller and attach it to an element. About an hour of googling has yielded nothing but I can't believe that something like this doesn't exist somewhere.
The HTML must be compiled to make angular directives work.
Usually the compile steps are done by angular behind the scenes (ng-app, bootstrap). Ng-repeat does it for example when creating new templates throug iterations.
But here, angularJS doesn't "know" you have DOM nodes to compile and you must do it manually.
You must learn about compile on angular, here are the docs:
reference api for $compile (the last exemple on this page is the one that helped me the most)
reference guide for compiling on angular
Here's what you might have to do (not sure because I haven't a deep knowledge on this):
create a scope for your duplicated widget
compile the template (where angular look dom markup for directives and expressions)
link the compiled template to the created scope
I think I've found it, but I don't know if this is necessarily the correct way to do it.
I firstly removed the data-ng-app property from my <body> element. I then applied the data-ng-controller programatically and called angular.bootstrap to specify the module I wanted to link to the element:
function showWindow()
{
var dialog = $("myWindow")
.clone()
.attr("data-ng-controller", "MyController")
.setupDialog();
angular.bootstrap(dialog, ["MyModule"]);
dialog.show();
)
It certainly works for me, although I'm not sure if there are side-effects to doing this that won't be apparent until later.

Making data templated in available to ng-model

When using server-side templating and client-side angularjs, I'm not able to get angularjs to recognize values I've templated in on the server.
For example (or on jsfiddle):
<div ng-app>
<div ng-controller="Ctrl">
<textarea ng-model="data" placeholder="Enter a name here">Templated in</textarea>
{{data}}
</div>
</div>
Angularjs will always replace the value in the text area with the value of $scope.data (which is null). What I want is for the value of $scope.data to take on "Templated in", on app bootstrap, then to continue normally from there.
How can I template in from the server a value, then have angularjs model bind that value once on the client?
Use ng-init
<textarea ng-model="data" placeholder="Enter a name here"
ng-init="data='Templated in'"></textarea>
See also AngularJS - Value attribute on an input text box is ignored when there is a ng-model used? and
rails + angularjs loading values into textfields on edit
I came across this answer while trying to figure this out for myself, but never liked that I had to move information from the part of the field where the HTML spec wants it. That just didn't feel like the right approach to me.
For a while, I ended up taking advantage of the fact that Angular sits on top of jQuery (or the embedded jqLite subset if the full jQuery isn't available) to solve this without moving the content, or even touching ng-init. Specifically, you can access the contents of the textarea using (the Angular/jqLite version of) standard jQuery methods during the controller initialization phase.
So I just did
var doc = angular.element(document.documentElement);
$scope.data = doc.find('textarea').eq(0).val();
in my controller, exactly where I would initialize any other scope variable. (Alternately, here's the modified jsFiddle...)
With the full jQuery library available, the code is even simpler, since you have access to full jQuery selectors at that point and can jump straight to $('textarea').eq(0).val() (or even add an id to the field and select on that: $('#data-textarea').val()).
The nice thing about this approach is that it will work for any form elements, though since most of those are <input> tags, and jqLite doesn't support selectors, finding the exact one you want can be a tad tricky. I would simply include the full jQuery library at that point and take advantage of the selectors, but that's me.
This approach also has the major drawback of placing DOM-aware code in the controller, which completely breaks both Angular conventions (The Angular Way (TM)) and the MVC/MVVM programming paradigm. Not the best solution.
UPDATE: So I eventually realized I would need a more long-term solution, one which didn't violate so many best practices. The answer actually comes from the most essential element of Angular, without which none of the rest of it would work (you could argue that for other components, but it's simply more true for this one): Directives. More specifically:
app.directive('input', ['$parse', function ($parse) {
return {
restrict: 'E',
require: '?ngModel',
link: function (scope, element, attrs) {
if(attrs.value) {
$parse(attrs.ngModel).assign(attrs.value, scope);
}
}
};
}]);
This has a number of advantages. First, it doesn't break Angular conventions that help reinforce MVC/MVVM. Second, it doesn't even touch jqLite/jQuery, nor the underlying DOM functions. Third, it has the desired effect of preserving HTML conventions for defining default values, allowing (or at least simplifying) the use of Angular with other existing technologies, such as server-side templating engines.
Why doesn't Angular do this by default? Well, I don't know the actual answer without a bit more research, but a likely answer is that HTML conventions favor static page content, and Angular is designed for dynamic page content. That means breaking out of HTML conventions in many places, not letting it limit what is possible with Angular apps. Since controllers are expected to carry the responsibility of initializing models (and in most cases such an expectation is the correct one), the Angular team would have incentive to ignore the contents of a value attribute (and its analogs among the other form tags).
Of course, with this approach, for any applicable elements that already exist before the controller is initialized, the controller init can override the value attribute. This behavior is not consistent, however, across the entire application, because new elements will trigger the directive evaluation, but not the controller init phase. (Partials with their own controllers hold this behavior as well - elements in place prior to the partial's controller init can be overridden by said init, but other elements added after that won't re-trigger the init.)
There are a number of other ways to write such a directive, and it can be extended to do any number of other things as well, but hopefully this approach helps someone else.

AngularJS - ng-bind-html-unsafe and ng-model Problems

I have the following line in my html:
<div ng-bind-html-unsafe="departmentConfig" class="control-group"></div>
and I use a $resource get to retrieve the HTML, assign the HTML to $scope.departmentConfig, and then the view is updated perfectly. The HTML that is assigned to $scope.departmentConfig contains form elements, with ng-model attributes in it, but when I change the values in these input elements, they don't update the $scope model at all.
This is what I have tried, based on a lot of other internet posts, and it isn't working:
$resource('resources/sources/departments/:mappedName', {
mappedName:departmentKey
}).get(function(departmentConfig) {
// This will call another function that will build a chunk of HTML
$scope.departmentConfig = $scope.buildDepartmentConfigHtml(departmentConfig);
// This is my feeble attempt to access the element, and bootstrap it to include the items in the $scope model.
var $departmentConfigContainer = $('#departmentConfig');
angular.bootstrap($departmentConfigContainer, ['sourcemanager']);
I have even seen some jsFiddle examples where this appears to be working, but mine isn't. Am I calling bootstrap too soon? I also tried putting a $watch on $scope.departmentConfig like this:
$scope.$watch('departmentConfig', function() {
var $departmentConfigContainer = $('#departmentConfig');
angular.bootstrap($departmentConfigContainer);
});
but it didn't work either. I bet there is an easy explanation to this, I just can't seem to get the input elements with ng-model that are loaded after page compile to get bound to the model. Any help is appreciated, this is the last piece of functionality I need to get working on my page. Let me know if you need more information about my configuration as well.
So, simply put, how can I force a section of the DOM to recompile after I know it has been loaded?
UPDATE
Here is a jsfiddle outlining what I would like to do: http://jsfiddle.net/j_snyder/ctyfg/. You will notice that property two and three don't update the model, and I am calling bootstrap on the outer div, hoping that will include those in the model binding. This is the first time I have posted to jsfiddle, please let me know if you can't see the example.
ng-bind-html is made for regular HTML, not compiling new angular elements.
You will have use the $compile service.
Here is how you would edit your current example to work: http://jsfiddle.net/andytjoslin/ctyfg/21/. But this way ends up being bad, because you have to do DOM manipulation in your controller.
You just need to create a directive that will basically do what you wanted ng-bind-html to do: http://jsfiddle.net/andytjoslin/ctyfg/22/

Resources