underscore template is removed from the DOM so can't be re-used - backbone.js

I'm trying to use a simple inline underscore.js template with backbone, and pulling the template from a <script> tag via jQuery's html() method.
The page is supposed to render multiple Recipe callouts for each returned by the Recipe Model. It works on the first thumbnail, but on the second, it seems the <script> tag has been removed from the DOM, so underscore fails to render it and bails with str is null on line 913
For the template, I have
<script type="text/html" id="user-recipe-rated-template">
<div class="user-recipe-rated">
<a class="thumb" href="<%= href %>"></a>
<p><%= title %></p>
</div>
</script>
And the backbone view looks like:
var UserRecipeView = Backbone.View.extend({
initialize : function() {
this.template = _.template($("#user-recipe-rated-template").html());
},
render : function()
{
this.el = this.template({
title: this.model.get("Title"),
href: '#'+this.model.get('UserContentId'),
image_src: this.model.get('ThumbnailSrc')
});
return this;
}
});
So, what I am seeing is that $("#user-recipe-rated-template") exists on the first thumbnail and all is well. On the second, it returns an empty array and underscore can't proceed.
I've trying memoizing the string of html and such, and that might work, but it seems there should be a cleaner way of doing this. What am I doing wrong?
(I'd like to use templates inlined as opposed to external JST for now to keep things simple - it seems nicer to have the template embedded in the page near where it will be loaded when the data comes in)

Related

TooltipHtmlUnsafe with content fetched from another div

I'm trying to show a tooltip with html content and the html I would like to be fetched from a children div which has angular markup in it.
Before I switched to ui-bootstrap I used the default bootstrap tooltip, which I created in a directive filling the content with ('.my-tooltip-content',element).html()
Now with ui-bootstrap I tried using the same directive/logic on the ui-tooltip except that now I tried setting attribute variables. The problem is, that I don't know how to get the html from my .my-tooltip-content div rendered inside the tooltip, without loosing the bindings. If I use $interpolate, I get the html of my div properly rendered, but the output is fixed then of course (doesn't update anymore), $compile I have never used before, but I thought this would be the right place for such a use, maybe I don't know how to use it, but $compile gives me an exception about circular structure.
This is a shortened version of what my app looks like:
http://plnkr.co/edit/46NsEPArtm4hph0ROlPS?p=preview
Excerpt:
<div class="hover" tooltip-html-unsafe="" tooltip-trigger="mouseenter" tooltip-placement="bottom">
<div>
<p>Hover me {{booking.name}} !</p>
<!-- about 20 more lines -->
</div>
<div class="my-tooltip-content"><!-- hidden by default -->
<p class="booker-name">{{booking.name}}</p>
<p>Does the name in this tooltip update?</p>
<!-- about 10 more complex lines -->
</div>
</div>
anApp.directive('hover', ['$compile','$interpolate', function($compile,$interpolate){
return{
restrict: 'C',
link: function(scope,element,attrs){
var html1,html2,html3;
var content = $(element).find('.my-tooltip-content');
// This works, but no binding
html1 = $interpolate(content.html())(scope);
// This I'd like to work, but I get "Can't interpolate: {{tt_content}} TypeError: Converting circular structure to JSON"
html2 = $compile(content.html())(scope);
// This brings my html into the tooltip but curlybraces are curlybraces (not compiled)
html3 = content.html();
attrs.tooltipHtmlUnsafe = html1;
}
}
}])
Is there an easy way to to get the html of .my-tooltip-content with all it's angular markup/bindings injected into the tooltip-content variable ?
PS: I know that I could squeeze all the html just directly into the tooltip-html-unsafe attribute, but I have so many lines in my real-world my-tooltip-content that this just wouldn't work (would make the tooltip-content html unreadable and unchangeable for humans).
If you need to use a child div, one solution use a function to pass the interpolated contents of the div to the tooltip. For example:
anApp.directive('hover', ['$compile','$interpolate', function($compile,$interpolate){
return{
restrict: 'C',
link: function(scope,element,attrs){
scope.myTooltip = function() {
var content = $(element).find('.tooltip-content');
return $interpolate(content.html())(scope);
};
}
}
}])
And your html would would need to be updated to use the new scope value for the tooltip:
<div class="hover" tooltip-html-unsafe="{{myTooltip()}}" tooltip-trigger="mouseenter" tooltip-placement="bottom">
Here's the updated plnkr

How do I load an external HTML file and have the scope variables renderred properly in angularJS?

What I am trying to do is after clicking on a buddy in the buddy list, load a chat dialog template HTML file without disturbing other elements in current DOM just like chatting in facebook.
My problem is that after loading the html template file the scope variables such as {{contact.jid}} are not properly rendered, and the controller for the dialog is not even called.
How can I force a rerender or a call on the controller so that those variables are properly renderred? Or should I not use the jQuery.load function to do this? I can't figure out any other way.
Thank you.
Code of the controllers:
// Controller for the chat dialog
ctrl.controller('ChatCtrl',function($scope){
$scope.test = "Test"
});
// Controller for the buddy list
// $scope.toggleDialog is called when a buddy is clicked
ctrl.controller('ContactCtrl',function($scope){
$scope.contacts = window.contactList;
$scope.toggleDialog = function(to){
$.("#chatarea").load("chatdialog.html")
};
});
The controller function of chat dialog is not called after loading chatdialog.html which has an attribute of ng-controller, so the {{test}} variable is not set.
You need to wrap your content that will be compiled inside a directive.
This directive receives the variables and the HTML that will be compiled.
You can wrap this directive in the element that you like:
http://plnkr.co/edit/RYVCrlDmgVXT3LszQ9vn?p=preview
For example, given the following html and variables:
// In the controller
$scope.controllerHtml = '<span>{{variables.myVariable}}</span>';
$scope.controllerVariables = {myVariable: 'hello world'};
<!--In the HTML-->
<div compile-me html-to-bind="controllerHtml" variables="controllerVariables"></div>
You will get the following:
<div>
<span>hello world</span>
</div>
You are loading the external HTML via jQuery and Angular has no way of knowing how to use it. There are two ways to solve this issue:
use ngInclude to load the template from the server, angular will load it from the server and compile it for you to use.
continue to use jQuery load the HTML from the server and use the $compile service to teach angular how to use it
I would highly suggest using method #1 to load your external template.
I suppose the:
$.("#chatarea").load("chatdialog.html")
is the jQuery .load, or something similar. I would get the template via ngInclude, checking if test is setted or not; html:
<div id="chatarea" ng-if="test">
<div ng-include="'chatdialog.html'"/>
</div>
controller:
ctrl.controller('ContactCtrl',function($scope){
$scope.contacts = window.contactList;
$scope.test = '';
var callbackFunction = function(data) {
$scope.test = data.test;
};
$scope.toggleDialog = function(to){
AjaxRequestToGetBuddyInfoAndMessages(to, callbackFunction);
};
});
Obviously test will be a more complex object, so the ngIf test will be different (and you will need to take into account the fact that:
$scope.test = data.test
if they are objects, they will lose the reference and have an unwanted behaviour).

Rendering dynamic HTML(angularjs content) content after ajax call in AngularJS

I am new to Angular getting stuck after making ajax call. How do I render/compile the html content once you inject in DOM so that I can still use the AngularJs functions.
Due to the way my backend is set up I have to get content via ajax ($http). And I am making the app without jQuery. I tried $compile and $apply but didn't work. What am I missing here.
I have the code set up at http://jsfiddle.net/rexonms/RB7FQ/3/ . I want the second div content to have the same properties as the first div.
HTML
<div ng-controller="MyCtrl" class="section">
<input ng-model="contentA">
<div>
And the input is: {{contentA}}
</div>
</div>
<div ng-controller="MyAjax" class="section">
<div id="dumpAjax">
{{ajaxData}}
</div>
<button ng-click=getajax()> Get Ajax</button>
</div>
SCRIPT
var myApp = angular.module('myApp',[]);
//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});
function MyCtrl($scope) {
}
function MyAjax($scope){
var data = '<input ng-model="contentB">{{contentB}}';
$scope.getajax = function(){
$scope.ajaxData = data;
}
}
Thanks in advance.
ng-bind-html-unsafe is not available 1.2 and later verison of angular...
so you should use ng-bind-html which creates a binding that will innerHTML the result of evaluating the expression into the current element in a secure way.
using $scope variable in your string make it unsafe, so you should use $sce.trustAsHtml but this time variables in your string cannot be bind because they will be not compiled...
basically you should compile your string in order to bind your variables. Here comes custom directives you can create a directive which can replace with ng-html-bind...
Writing a custom directive which extends ng-bind-html with some extra functions can be a solution...
here is my PLUNKER
and here is your updated JSFIDDLE with my solution...
Instead of {{ajaxData}}, you should use something like:
<div ng-bind-html-unsafe="ajaxData"></div>
However, you'd still need to set the proper model to bind the contentB and get it working.

Backbone Underscore Template

So I'm checking out the changes related to the latest backbone/underscore version. Prior I have a project running with BB 0.5.2 and underscore 1.1.7. I'm noticing something strange with regards to defining a template property within a view in the new version, which gives me reservations in going forward with upgrading.
In my current version I would define a view as such:
var MyView = Backbone.View.extend({
template: _.template($('#exampleTemplate').html()),
initialize: function() {...},
render: function() { $(this.el).html(this.template(someObjectParam)); },
});
However, if I attempt to work in the same manner, using a simplified todo clone attempt as an example, I setup an html with an inline script template as such:
<script>
$(document).ready(function() {
app.init();
});
</script>
<script type="text/template" id="itemViewTemplate">
<div class="item">
<input type="checkbox" name="isComplete" value="<%= item.value %>"/>
<span class="description"><%= item.description %></span>
</div>
</script>
In my included JS file I have:
var ItemView = Backbone.View.extend({
el: 'body',
// Below causes error in underscore template, as the jquery object .html() call
// returns null. Commenting will allow script to work as expected.
templateProp: _.template($('#itemViewTemplate').html()),
initialize: function() {
// Same call to retrieve template html, returns expected template content.
console.log($('#itemViewTemplate').html());
// Defining view template property inside here and calling it,
// properly renders.
this.template = _.template($('#itemViewTemplate').html());
this.$el.html(this.template({item: {value: 1, description: 'Something'}}));
},
});
var app = {
init: function() {
console.log('Fire up the app');
var itemView = new ItemView();
}
}
So I'm left confused as to why defining the template property directly causes the call to retrieve the template html to return a null value, thus breaking the attempt to define an underscore template object (mouthful). However, if the definition is done within the initialize function, the call to retrieve the template html properly finds the template so its contents can be passed to the underscore template. Anyone see what I'm potentially missing?
Thanks in advance!
If this:
var ItemView = Backbone.View.extend({
//...
templateProp: _.template($('#itemViewTemplate').html()),
//...
});
is failing because $('#itemViewTemplate').html() is null, then you have a simple timing problem: you're trying to read the content of #itemViewTemplate before it exists. Your old version should suffer from exactly the same problem.
Either make sure everything is loaded in the right order (i.e. your views after your template <script>s) or compile the template in your view's initialize. You can check for the templateProp in your view's prototype and only compile it on first use if you want:
initialize: function() {
if(!this.constructor.prototype.template)
this.constructor.prototype.template = _.template($('#itemViewTemplate').html());
//...
}
Demo: http://jsfiddle.net/ambiguous/HmP8U/

Backbone, not "this.el" wrapping

I do an extensive use of templates, and I like to use full contained templates. I mean that I want to see in the template code all the DOM elements including the root one, like this:
<script type="text/template" id="template-card">
<div class="card box" id="card-<%= id %>">
<h2><%= title %></h2>
<div><%= name %></div>
</div>
</script>
But what Backbone likes is having a template like this:
<script type="text/template" id="template-card">
<h2><%= title %></h2>
<div><%= name %></div>
</script>
And defining the root element and its attributes in the JS code. What I think is ugly and confusing.
So, any good way to avoiding my Backbone View to wrapper my template with an extra DOM element?
I have been checking this issue thread: https://github.com/documentcloud/backbone/issues/546 and I understand there is not any official way to do it.. but maybe you can recommend me a non official way.
You can take advantage of view.setElement to render a complete template and use it as the view element.
setElement view.setElement(element)
If you'd like to apply a Backbone view to a different DOM element, use setElement, which will
also create the cached $el reference and move the view's delegated
events from the old element to the new one
Two points you have to account for:
setElement calls undelegateEvents, taking care of the view events, but be careful to remove all other events you might have set yourself.
setElement doesn't inject the element into the DOM, you have to handle that yourself.
That said, your view could look like this
var FullTemplateView = Backbone.View.extend({
render: function () {
var html, $oldel = this.$el, $newel;
html = /**however you build your html : by a template, hardcoded, ... **/;
$newel = $(html);
// rebind and replace the element in the view
this.setElement($newel);
// reinject the element in the DOM
$oldel.replaceWith($newel);
return this;
}
});
And a working example to play with http://jsfiddle.net/gNBLV/7/
Now you can also define a view's tagName as a function and create a class like this:
var MyView = Backbone.View.extend({
template: '#my-template',
tagName: function() {
// inspect the template to retrieve the tag name
},
render: function() {
// render the template and append its contents to the current element
}
});
Here's a working example
Backbone.Decarative.Views provides you with an alternative way to do this, without having to rely on setElement. For more, check out my answer here.

Resources