handling null model attributes in a backbone / underscore template - backbone.js

I have a model whose data is displayed in a backbone view/underscore template.
I setup the template in my view like this:
return Backbone.View.extend({
className: 'officeAlerts',
template: _.template(OfficeAlertsTmpl, null, { variable: 'm' }),
And in my template, I have lines like this to display the model data:
<span class="textForEmployer">{%- m.officeName %} has no alerts.</span>
When all the data is there, everything works fine. The problem I have is with nulls. If a model attribute happens to be null, the whole page doesn't load and I get a null reference error in the browser console.
Is there a way to check/catch nulls so that it doesn't stop the whole page from loading?
Thanks!

You can simply add a condition like this:
<span class ="textForEmployer"> <%= m ? m.officeName: "" %> has no alerts.</span>

Related

Backbone Marionette Slow Composite Views (200+ collections)

When showing a dropdown composite view collection of around 200 countries my application gets far too slow.
What is the best way to increase performance when dealing with large collections in marionette composite views?
Here is the function in the controller that is very slow to load. It is fast with only the following lines removed:
#layout.shippingCountryRegion.show shippingCountryView
#layout.billingCountryRegion.show billingCountryView
So it appears to be a very slow rendering issue.
Show.Controller =
showProfile: ->
#layout = #getLayoutView()
#layout.on "show", =>
headerView = #getHeaderView()
#layout.headerRegion.show headerView
accessView = #getAccessView()
#layout.accessRegion.show accessView
billingReadmeView = #getBillingReadmeView()
#layout.billingReadmeRegion.show billingReadmeView
billingFieldsView = #getBillingFieldsView()
#layout.billingFieldRegion.show billingFieldsView
shippingReadmeView = #getShippingReadmeView()
#layout.shippingReadmeRegion.show shippingReadmeView
shippingFieldsView = #getShippingFieldsView()
#layout.shippingFieldRegion.show shippingFieldsView
MyApp.request "location:get_countries", (countries) =>
billingCountryView = #getBillingCountryView(countries)
#layout.billingCountryRegion.show billingCountryView
MyApp.request "location:get_states", MyApp.activeCustomer.get('billing_country_id'), (states) =>
billingStateView = #getBillingStatesView(states)
#layout.billingStateRegion.show billingStateView
MyApp.request "location:get_countries", (countries) =>
shippingCountryView = #getShippingCountryView(countries)
#layout.shippingCountryRegion.show shippingCountryView
MyApp.request "location:get_states", MyApp.activeCustomer.get('shipping_country_id'), (states) =>
shippingStateView = #getShippingStatesView(states)
#layout.shippingStateRegion.show shippingStateView
MyApp.mainRegion.show #layout
The billing country view:
class View.BillingCountryDropdownItem extends MyApp.Views.ItemView
template: billingCountryItemTpl
tagName: "option"
onRender: ->
this.$el.attr('value', this.model.get('id'));
if MyApp.activeCustomer.get('billing_country_id') == this.model.get('id')
this.$el.attr('selected', 'selected');
class View.BillingCountryDropdown extends MyApp.Views.CompositeView
template: billingCountryTpl
itemView: View.BillingCountryDropdownItem
itemViewContainer: "select"
The template, simply:
<label>Country
<select id="billing_country_id" name="billing_country_id">
<%- name %>
</select>
</label>
Your code can be optimized. Just move content of onRender method to the ItemView attributes.
class View.BillingCountryDropdownItem extends MyApp.Views.ItemView
template: billingCountryItemTpl
tagName: "option"
attributes: ->
var id = this.model.get('id');
var attributes = { 'value': id };
if MyApp.activeCustomer.get('billing_country_id') == this.model.get('id')
attributes['selected'] = 'selected';
return attributes
The difference between this method and onRender case is that, on render will execute when collection already rendered and 200+ operations will be done with DOM nodes, which will bring performance issues.
In case of attributes method, it executes upon view creation.
There are few advices you can follow:
1) Ask your self do you really need to render all items at once? Maybe you can render part of collection and render other items on scroll or use pagination or use 'virtual scrioll' with SlickGrid or Webix for example.
2) Checkout how often you re-render your view. Try to minify num of events cause re-render
3) Try to minify num of event listeners of ItemView. Its good practice to delegate context events to CollectionView
4) You can use setTimeout to render collection by parts. For example you divide you coll in 4 parts by 50 items and raise 4 timeouts to render it.
5) You can optimize underscore templating and get rid of with {} operator. http://underscorejs.org/#template
What's in your 'billingCountryItemTpl' varible? If it's just string with template ID then you could precompile your template using Marionette.TemplateCache.
So you'll have:
template: Marionette.TemplateCache.get(billingCountryItemTpl)

filter and unfilter on ng-click with buttons

hello i have a couple of buttons i want to filter data with, but i can't figure out how to make it unfilter when i click the button again. I use the normal filtering expression like:
<tr ng-repeat='ledata in datas | filter:thecondition'></tr>
where the thecondition may vary depending on the button clicked, the button's code is the following
ng-click="thecondition = {type: 1}"
also how can i mark it with ng-class? make the button look pressed? thanks.
My recommendation is that try to implementing your own custom filter.
Something close to:
custom filter:
angular.module('myApp', []).filter('myFilter', function() {
return function(items, param1, param2) {
return items.filter(function(item){ /* your custom conditions */ });
};
});
and in your html
<li ng-repeat="item in items | myFilter:'car': 2">
Of course you could extend and perform the actions that are more suited to your app needs.
The reason why I recommend the custom filter is because it will allow you a better separation from the view and the business logic; which is more aligned with the MVVM design pattern behind angular.
For toggling the filter, try this:
ng-click="thecondition = thecondition ? '' : {type: 1}"
The ternary operator will set thecondition to an empty string if it has a value, and to the object with type : 1 if it's empty.
To set the class with ng-class, try this:
ng-class="{depressed_button: thecondition}"
When thecondition is truthy, the class depressed_button will be applied. It's up to you to define the depressed_button class in a stylesheet or elsewhere.

AngularJS <select> ng-options selected value is not properly set

I have this pseudo code angularjs app. You can select a hobby for each person, save it and load it again.
However when I load the data, the select boxes dont have the correct option selected. person.hobby exists and is the correct object, but it looks like ng-model isn't set correctly.
Am I missing something?
<div ng-repeat="person in people">
<p>{{ person.fullname }}</p>
<select ng-model="person.hobby"
ng-options="h.hobby_name group by h.category for h in hobbies">
</select>
</div>
<script>
//... controller...
$http.get('/gethobbies/').succes(data) {
hobbies = data;
};
$http.get('/getpeople/').succes(data) {
people = data;
// looks like this: [{'fullname': 'bill gates', 'hobby': {'hobby_name': 'programming', 'category': 'computers'}, ...]
};
</script>
ng-model needs to be set to the exact same object as the one in the ng-options array that you want to be selected. Angular uses object references to figure out which one should be active so having a "hobby-object" with the same "hobby_name" as one of the objects in "hobbies" is not enough. It needs to be a reference to the same object.
See documentation for select for details

Deleted items from collection persist in local storage

I have a user model in backbone as follows
var User = Backbone.Model.extend({
initialize: function(){
},
defaults: {
firstname: '<FirstName>',
lastname: '<LastName>',
email: '<EmailAddress>'
}
});
and collection:
UserCollection=Backbone.Collection.extend({
model : User,
localStorage:new Backbone.LocalStorage("UsersCollection")
});
When I delete the model, I use the following code
var model=this.collection.get(cid);
this.collection.remove(model);
cId comes from the view, which i have not shown here. The model is removed from teh collection as I can render it just fine and the model is not there. (cid) is an attribute on the delete button.
Anyways, when I refresh the page, the deleted item reappearrs. Editing works fine. After refresign, the item isn't there anymore. Also, if I try to do Collection.sync(), I get an error
Cannot read property 'localStorage' of undefined
model.destroy() gives an error too: A "url" property or function must be specified
How do I remove the item from local storage
You should always define either an url of collection or an url of model.
Even if you are using only local storage.
Just try to specify url of collection as "/".

How to access an attribute in a template, besides by its name?

In an underscore template, is there any other way to access an attribute besides by its name? I've got one called "2a" and I cannot reference it directly, due to its first character being a number. For example, this doesn't work:
<input type="checkbox" name="6a" <%= 6a ? "checked" : "" %>>
Thanks!
You have a few options other than renaming the offending attribute.
Underscore's _.template has a variable option:
By default, template places the values from your data in the local scope via the with statement. However, you can specify a single variable name with the variable setting.
So you could do this:
<input type="checkbox" name="6a" <%= v['6a'] ? "checked" : "" %>>
and this:
var t = _.template($('#whatever').html(), null, { variable: 'v' });
var h = t({ '6a': true });​
Demo: http://jsfiddle.net/ambiguous/hBhfu/
You could also wrap it manually when you call the template function:
t({ v: { '6a': true }});
You'd use the same template as above in this case.
Demo: http://jsfiddle.net/ambiguous/8AZKw/

Resources