I have just started using backbone.js for a project that I am working on.
I currently have everything set up the way I want it as far as the models,collections and views. I am getting data from a REST application on my server.
Clients are able to log into the app; currently I am feeding that information into an underscore.js template but I want the template to be dynamic. Some options are going to be different depending on the client.
My feeling is that having the template make specific ajax calls to get dynamic information would defeat the purpose of using backbone.js altogether. Is it possible to have backbone and underscore load a template from an xhr request? Or is there an even better way to do this?
Thanks for the help in advance.
The template is just a string as far as Underscore is concerned so you can get that string from anywhere you want. So you could do this:
render: function() {
var that = this;
$.get('/some_template', function(tmpl) {
that.$el.html(_.template(tmpl, that.model.toJSON()));
});
return this;
}
In real life you'd probably want to hide that behind a simple caching object that only fetches a particular template from the server once.
Or, you could let your server code figure out which set of templates are needed and embed them in <script> elements:
<script id="tmpl1" type="text/template">
Some template code...
</script>
<script id="tmpl2" type="text/template">
Some template code...
</script>
...
and pull the templates out of the <script>s:
render: function() {
var tmpl = _.template($('#tmpl1').html());
this.$el.html(tmpl(this.model.toJSON()));
return this;
}
And again you might want to cache the compiled template, tmpl, somewhere or even compile it while defining the view class (assuming that the DOM is ready enough of course):
var V = Backbone.View.extend({
template: _.template($('#tmpl1').html()),
//...
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
Related
In jQuery, it is possible to load the specific selector element in another HTML using the classes and id on load function
For example: $('#id').load("http://www.thisiswhyimbroke.com li:first .someclass")
Is there any way to do the same in AngularJS include i.e ng-include?
try this..
<div class="include-example" ng-class="template.class" ng-include="template.url">
Yes! I figured this out today. In my case I'm loading from the same domain, but loading in the entire page (angular, bootstrap, etc) just wasn't going to work.
In short you're going to need to make an interceptor catch the response and process it.
First, point your ng-include to your url.
<div ng-include="currentView.content.url"></div>
Second, we'll need to intercept the response. In my case I know I will always be loading in #articleAnswer.
app.factory('templateInterceptor', function($q) {
return {
response: function( response ) {
var html = response.data.toString();
if (html.indexOf('articleAnswer') !== -1) {
var parser = new DOMParser();
var doc = parser.parseFromString(html,'text/html');
var article = doc.querySelectorAll('#articleAnswer');
response.data = article;
}
return response;
}
};
});
Third, add the factory you just made to your $httpProvider interceptors.
app.config(function ($httpProvider) {
$httpProvider.interceptors.push('templateInterceptor');
});
Fourth, you'll probably also want this bit in place to allow you to load in trusted html.
app.config(function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist(['self']);
});
I'm sure someone has a more elegant way of doing this, but hope this helps!
yes angular have directive for include templates in another html
please check these examples
http://jsfiddle.net/mrajcok/mfha6/
`https://jsfiddle.net/mrajcok/9krVn/3/`
both examples are fruitfull for ng-include and also get help from this question What is the correct syntax of ng-include?
I'm rather new to angular and I'm trying to integrate np-autocomplete in my application (https://github.com/ng-pros/np-autocomplete). However I can only get it to work when I'm passing a html string as a template inside the $scope.options and it doesn't work when I want to load it from a separate html.
the Code for my app looks as follows:
var eventsApp = angular.module('eventsApp',['ng-pros.directive.autocomplete'])
eventsApp.run(function($templateCache, $http) {
$http.get('test.html', {
cache: $templateCache
});
console.log($templateCache.get('test.html')) // --> returns undefined
setTimeout(function() {
console.log($templateCache.get('test.html')) // --> works fine
}, 1000);
//$templateCache.put('test.html', 'html string') //Would solve my issue in the controller,
//but I would rather prefer to load it from a separate html as I'm trying above
Inside my controller I am setting the options for autocomplete as follows:
controllers.createNewEventController = function ($scope) {
$scope.options = {
url: 'https://api.github.com/search/repositories',
delay: 300,
dataHolder: 'items',
searchParam: 'q',
itemTemplateUrl: 'test.html', // <-- Does not work
};
//other stuff...
}
however, it seems that test.html is undefined by the time np-autocomplete wants to use it (as it is also in first console.log above).
So my intuition tells me that the test.html is probably accessed in the controller before it is loaded in eventsApp.run(...). However I am not sure how to solve that?
Any help would be highly appreciated.
You are most likely correct in your assumption.
The call by $http is asynchronous, but the run block will not wait for it to finish. It will continue to execute and the execution will hit the controller etc before the template has been retrieved and cached.
One solution is to first retrieve all templates that you need then manually bootstrap your application.
Another way that should work is to defer the execution of the np-autocomplete directive until the template has been retrieved.
To prevent np-autocomplete from running too early you can use ng-if:
<div np-autocomplete="options" ng-if="viewModel.isReady"></div>
When the template has been retrieved you can fire an event:
$http.get('test.html', {
cache: $templateCache
}).success(function() {
$rootScope.$broadcast('templateIsReady');
});
In your controller listen for the event and react:
$scope.$on('templateIsReady', function () {
$scope.viewModel.isReady = true;
});
If you want you can stop listening immediately since the event should only fire once anyway:
var stopListening = $scope.$on('templateIsReady', function() {
$scope.viewModel.isReady = true;
stopListening();
});
Is there a way in angualrjs to get a rendered partial view as a string in a controller (or service or whatever).
something like
var str = renderPartial('partials/foo.html', {/*scopeObj*/});
$scope.showMe = str;
Yes, you can do that, in your app run function, you can put the template cache to $templateCache:
angular.module("youApp", []).run(["$templateCache", function($templateCache) {
$templateCache.put("url/to/your/template1.html",
"<span>Template</span>"); // 'url/to/your/template1.html' is used to location the cache in angular
}]);
Then in your view or some where else, you can use the url(this wouldn't trigger http request now) to load the template you put in your $templateCache.
e.g. in html:
<div ng-include="url/to/your/template1"></div>
Here is $templateCache document.
By the way, this is how angular-ui-bootstrap.js(at the bottom of this lib) manager their template in lib. : )
Have you tried using $compile service:
function someFunction($compile) {
var element = $compile('<p>{{total}}</p>')({/*scopeObj*/});
}
And with $templateCache it could be just like what you want
function someFunction($compile $templateCache) {
var element = $compile($templateCache.get('partials/foo.html'))({/*scopeObj*/});
}
I am just learning Angular and I have some questions regarding the architecture of my app.
The project I will be working on will be using allot of external libraries: jQuery, jQuery.ui, jsPlumb and so on, with different loading times.
I know that each code related to these libraries will have to be handled inside directives.
I worked with Backbone which uses Require JS effectively - on each view, I could set what libraries I need and the view would be loaded as soon as those libraries are available.
Now, on angular side - what would be the correct way of handling this issue?
From the top of my head, I am thinking of:
Place checks inside the router - checking if the desired libraries for a certain route are loaded.
Place checks inside each directive - for example if one directive uses jsPlumb, place a check inside and return the directives content when that library is loaded - I believe this could generate problems when interacting with other directives on the same view, which require multiple libraries with different loading times.
Load angular only after every other library is loaded - that would lead to long loading times.
What's the best way to handle all those issues?
You can create a factory to load the external library you need. Return a deferred object for the library's script after it loads. Here is one I used for d3 library:
var d3js = angular.module('d3', []);
d3js.factory('d3Service', ['$document', '$q', '$rootScope', '$window',
function($document, $q, $rootScope, $window) {
var d = $q.defer();
function onScriptLoad() {
// Load client in the browser
$rootScope.$apply(function() { d.resolve($window.d3); });
}
// Create a script tag with d3 as the source
// and call our onScriptLoad callback when it
// has been loaded
var scriptTag = $document[0].createElement('script');
scriptTag.type = 'text/javascript';
scriptTag.async = true;
scriptTag.src = 'lib/d3.v3.js';
scriptTag.onreadystatechange = function () {
if (this.readyState == 'complete') onScriptLoad();
}
scriptTag.onload = onScriptLoad;
var s = $document[0].getElementsByTagName('body')[0];
s.appendChild(scriptTag);
return {
d3: function() { return d.promise; }
};
}]);
then in your directive, use then function of the deferred to wait until it's ready
d3Service.d3().then(function(d3) {
// place code using d3 library here
}
If your directive is needing access to multiple libraries, you can chain the deferreds.
d3Service.d3().then(function(d3) {
someOtherLibSvc.getLib().then(function(lib){
// place code using d3 library and someOtherLibrary here
}
}
To avoid this chaining check out bluebird and use Promise.join, Angular comes with $q automatically so I just used that here.
Note: if you just load JQuery before angular, then angular.element will already reference JQuery. So you don't need to do this for JQuery, just load it in your main html page before Angular
In short go
http://slides.com/thomasburleson/using-requirejs-with-angularjs#/
route.
However now suffer having to put the defines and requires everywhere. For a large application this may get messy if not planned carefully. Use grunt requirejs plugin for builds because without this things would be wash.
I'm not sure this is the "correct" way to do this, but here is perhaps a simpler way to handle external libraries (in this case d3.js) within Angular 1.x code.
This is the same basic idea as #aarosil's answer (use a factory), but using fewer dependencies on other services - for what that's worth.
var app = angular.module('SomeApp', []);
app.factory('LibraryFactory', function () {
var factory = {};
factory.getD3 = function(callback) {
if(!window.d3) {
var script = document.createElement("script");
script.src = "https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.9/d3.min.js";
document.head.appendChild(script);
script.onload = function() {
callback(window.d3);
};
} else {
callback(window.d3);
}
};
//factory.getSomeOtherLibrary = ...ditto
return factory;
});
And to use the factory (eg, in a controller):
app.controller('SomeTrialWithD3Ctrl', function ($scope, LibraryFactory) {
LibraryFactory.getD3(function main(d3) {
// place code using d3 library here
});
});
And of course, the callbacks can be chained if you need multiple libraries at once.
I'm trying to load my fixtures in spec/category_keyword/categories.html. The content of the fixture is
My test
$(function() {
beforeEach(function(){
keywordsListView = new KeywordsListView({
el: $("#keywords_list")
})
})
it("should ...", function() {
expect(keywordsListView.el.attr("id")).toEqual("keywords_list");
})
})
However, keywordsListView.el is undefined which suggests to me that the fixture is somehow not loaded. I viewed the jasmine suite in firebug and didn't see anything related to fixture. I'm using the jasmine gem. Do I have to enable something?
Looking at this, I don't think the test has access to your keywordsListView variable. Declare the keywordsListView variable above the beforeEach function and populate it in the beforeEach as you currently have it.
$(function() {
var keywordsListView;
beforeEach(function(){
keywordsListView = new KeywordsListView({
el: $("#keywords_list")
})
})
it("should ...", function() {
expect(keywordsListView.el.attr("id")).toEqual("keywords_list");
})
})
Jasmine itself doesn't include any fixture loading, the html reporter gives you a div#jasmine_content that jasmine will not touch and is yours to write html to if needed. If you need to be able to load more complicated fixtures, there are other plugins that will do that for you. The only one I've ever used is jasmine-jquery. With this you can do something like:
$(function() {
beforeEach(function(){
loadFixtures('category_keyword/categories.html');
keywordsListView = new KeywordsListView({
el: $("#keywords_list")
});
});
it("should ...", function() {
expect(keywordsListView.el.attr("id")).toEqual("keywords_list");
});
});
Note that jasmine-jquery expects your fixtures to be in spec/javascripts/fixtures unless otherwise configured by setting jasmine.getFixtures().fixturesPath = <something>
The other thing you can do is let backbone create the element for you by not passing one in. Of course for this, your view will need to render itself instead of relying on whatever markup is generated by the server. This makes your view more self-contained as well.
After loading your fixtures, you have to call your backbone functions / method without making spies on for your models.
And then you have find with your loaded HTML content.
Like:
loadFixtures("/path/to/fixtures.html");
SpyOnObj.functionname();
expect($('body')).toHaveId("{ELEMENT_ID}");
I hope this will helps to you.