The question
I have an itemview which receives a model through its initializer.
When the itemview gets rendered, attributes are added to the view's element for each model property.
Why does this happen, and how can I prevent this?
Example scenario
A simplified example of the code I have looks as follows:
var MyView = Marionette.ItemView.extend({
tagName: 'div',
id: 'myView',
className: 'myClass',
template: someTemplate,
initialize: function(model) {
this.model = model;
}
});
var myView = new MyView(someBackboneModel);
myView.render();
Assuming the model has an 'id' attribute of value '124', this results in an element like this:
<div id="124" foo="foo" bar="bar" class="myClass">
<!-- blabla -->
</div>
The problem
As you can see the id value set in the MyView class gets replaced by the id property of the model.
I have other ItemViews in my app which are rendered by a Backbone.Marionette.CollectionView which renders ItemView elements which do not exhibit this behavior and don't add their model attributes to their $el at all.
Any ideas?
I've tried digging around in the Marionette Source and the Backbone source, but so far I can't find why this happens.
Addendum
At request of Derick Bailey, here's the full view and template which is causing this problem for me:
The View:
var Admin_Applications_ApplicationReader_Application_View = Marionette.ItemView.extend({
tagName: "div",
id: "Admin-Applications-ApplicationReader-Application-View",
template: ApplicationTemplate,
templateHelpers: {
moment: Moment
},
initialize: function(model) {
this.model = model;
this.bindTo(this, 'layout', this.onLayout);
},
// Callbacks
//----------
onRender: function() {
var that = this;
setTimeout(function() {
that.onLayout();
var $scrollContainer = that.$el.find('#Admin-Applications-ApplicationReader-Body'),
scrollPane = new ScrollPane($scrollContainer, {
maxHeightProperty: 'maxHeight',
scrollUpButton: false,
scrollDownButton: false
});
}, 0);
},
// Layout
//-------
onLayout: function() {
var css;
// Calculate desktop styles
if ($(window).width() > 767) {
css = this.calculateBodyStyle();
}
else {
css = {
applicationBody: { height: 'auto' }
}
}
// apply styles
this.$el.find('#Admin-Applications-ApplicationReader-Body').css(css.applicationBody);
// re-initialize scroll-pane
this.$el.find('.scroll-container').trigger('scroll-pane:new-content');
},
calculateBodyStyle: function() {
var $title = this.$el.find('#Admin-Applications-ApplicationReader-Title'),
viewHeight = this.$el.height(),
titleHeight = $title.outerHeight(true),
bodyHeight = viewHeight - titleHeight,
css = {
applicationBody: { maxHeight: bodyHeight },
};
return css;
}
});
The template:
<div id="Admin-Applications-ApplicationReader-Title" class="page-header no-top-margin">
<h2>Application
<span class="no-wrap"><small>Package: <%= package %>, </small></span>
<span class="no-wrap"><small><%= moment(submission_timestamp, 'YYYY-MM-DD HH:mm:ss').fromNow() %></small></span>
</h2>
</div>
<div id="Admin-Applications-ApplicationReader-Body" class="container-white scroll-container">
<ul class="application unstyled scroll-content">
<li class="section summary-section">
<div class="section-header">
<h3>Summary</h3>
</div>
<table class="section-body no-border table-condensed">
<tbody>
<tr>
<th>Package:</th>
<td><%= package %></td>
</tr>
<tr>
<th>Date of last flight:</th>
<td><%= date_of_last_flight %></td>
</tr>
<tr>
<th>Date of renewel:</th>
<td><%= date_of_ir_renewel %></td>
</tr>
</tbody>
</table>
</li>
<!-- /.summary-section -->
<li class="section credentials-section">
<div class="section-header">
<h3>Credentials</h3>
</div>
<table class="section-body no-border table-condensed">
<tbody>
<tr>
<th>Pilot Training Institute:</th>
<td><%= pilot_training_institute %></td>
</tr>
<tr>
<th>Date of exam:</th>
<td><%= moment(date_of_exam).format('DD-MM-YYYY') %></td>
</tr>
<tr>
<th>Date of last flight:</th>
<td><%= moment(date_of_last_flight).format('DD-MM-YYYY') %></td>
</tr>
<tr>
<th>Date of MCC Certificate:</th>
<td><%= moment(date_of_mcc_certificate).format('DD-MM-YYYY') %></td>
</tr>
<tr>
<th>Date of renewel:</th>
<td><%= moment(date_of_ir_renewel).format('DD-MM-YYYY') %></td>
</tr>
</tbody>
</table>
</li>
<!-- /.credentials-section -->
<li class="section personal-information-section">
<div class="section-header">
<h3>Personal information</h3>
</div>
<table class="section-body no-border table-condensed">
<tbody>
<tr>
<th>Name:</th>
<td><%= [first_name, last_name].join(' ') %></td>
</tr>
<tr>
<th>Adress:</th>
<td><%= address %></td>
</tr>
<tr>
<th>Zip code:</th>
<td><%= zip_code %></td>
</tr>
<tr>
<th>City:</th>
<td><%= city %></td>
</tr>
<tr>
<th>Country:</th>
<td><%= country %></td>
</tr>
<tr>
<th>Language:</th>
<td><%= language %></td>
</tr>
<tr>
<th>Telephone:</th>
<td><%= telephone %></td>
</tr>
<tr>
<th>Celĺphone:</th>
<td><%= cell_phone %></td>
</tr>
<tr>
<th>E-mail:</th>
<td><%= email %></td>
</tr>
<tr>
<th>Date of birth:</th>
<td><%= moment(date_of_birth).format('DD-MM-YYYY') %></td>
</tr>
</tbody>
</table>
</li>
<!-- /.personal-information-section -->
</ul>
<!-- /.application.scroll-content -->
</div>
The problem is how you're passing the model into the view upon instantiation. You should instantiate the view like this:
var myView = new MyView({model: someBackboneModel});
And you can remove the this.model = model code from the initialize method -- Backbone sets this for you when you specify a model option in the constructor.
The reason your view element has additional attributes is that model.attributes is being set as the optional attributes parameter of your view. The attributes are added when your element is rendered. To properly use this, you would instantiate like:
var myView = new MyView({
model: someBackboneModel,
attributes: { id: ..., class: ..., data-foo: bar }
})
Related
I have an specific requirement where the json data comes like this:
[{Id : "a", Name : "John", age : 50},
{Id : "b", Name : "Bob", age : 40}]
I want to show it in a table using ng-repeat, but in a way where headers come in the first column, as below:
<table>
<tr>
<td>Id</td>
<td>a</td>
<td>b</td>
</tr>
<tr>
<td>Name</td>
<td>John</td>
<td>Bob</td>
</tr>
<tr>
<td>Age</td>
<td>50</td>
<td>40</td>
</tr>
</table>
Is there a way to achieve this using angularjs?
Thanks
Provided you have a controller:
angular.module('MyApp', [])
.controller('MyController', function($scope) {
$scope.data = [
{Id : "a", Name : "John", age : 50},
{Id : "b", Name : "Bob", age : 40}
];
});
Your markup would then be as follows. If the data isn't going to change after it is displayed:
<table>
<tr>
<td>Id</td>
<td ng-repeat="item in ::data">{{::item.Id}}</td>
</tr>
<tr>
<td>Name</td>
<td ng-repeat="item in ::data">{{::item.Name}}</td>
</tr>
<tr>
<td>Age</td>
<td ng-repeat="item in ::data">{{::item.age}}</td>
</tr>
</table>
If the data is going to change after it is displayed, and you want the view to update accordingly, then:
<table>
<tr>
<td>Id</td>
<td ng-repeat="item in data track by $index">{{item.Id}}</td>
</tr>
<tr>
<td>Name</td>
<td ng-repeat="item in data track by $index">{{item.Name}}</td>
</tr>
<tr>
<td>Age</td>
<td ng-repeat="item in data track by $index">{{item.age}}</td>
</tr>
</table>
You can convert your array in an object, then you can use nested ng-repeats in view, as below:
(function() {
"use strict";
angular.module('app', [])
.controller('mainCtrl', function($scope) {
var array = [
{
"Id":"a",
"Name":"John",
"age":50
},
{
"Id":"b",
"Name":"Bob",
"age":40
}
];
// If you're sure that the properties are always these:
$scope.mainObj = {
"Id": [],
"Name": [],
"age": []
};
// If you're unsure what are the properties:
/*
$scope.mainObj = {};
Object.keys(array[0]).forEach(function(value) {
$scope.mainObj[value] = [];
});
*/
// Iterates over its properties and fills the arrays
Object.keys($scope.mainObj).forEach(function(key) {
array.map(function(value) {
$scope.mainObj[key].push(value[key]);
})
});
});
})();
<!DOCTYPE html>
<html ng-app="app">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular.min.js"></script>
</head>
<body ng-controller="mainCtrl">
<table>
<tr ng-repeat="(key, values) in mainObj track by $index">
<td ng-bind="key"></td>
<td ng-repeat="value in values track by $index" ng-bind="value"></td>
</tr>
</table>
</body>
</html>
I hope it helps!
I am new to angular 2 & trying to create select/unselect all checkboxes. I have a table like this:
<table>
<thead>
<tr>
<th><input type="checkbox" name="selectAll"></th>
<th>Name</th>
<th>Number</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="checkbox"></td>
<td>abc</td>
<td>9876373773</td>
</tr>
<tr>
<td><input type="checkbox"></td>
<td>abc</td>
<td>9876373773</td>
</tr>
<tr>
<td><input type="checkbox"></td>
<td>abc</td>
<td>9876373773</td>
</tr>
</tbody>
</table>
So if user clicks on selectAll checkbox other checkboxes should get check to. Data is coming dynamically, here is my api call:
this.contactService.getName(this.pageNumber, this.pageSize)
.subscribe(
data => {
this.contactlist = data.results;
},
err => console.error(err),
() => console.log('Contact list fetched')
);
With data-driven table you could achieve this by updating row-model's "checked" flag. For example you could do something like this:
<table>
<thead>
<tr>
<th><input type="checkbox" name="selectAll" [checked]="toggle" (change)="toggleAll()"></th>
<th>Name</th>
<th>Number</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of items">
<td><input type="checkbox" [checked]="item.checked" (change)="toggleItem(item)"></td>
<td>{{ item.name }}</td>
<td>{{ item.number }}</td>
</tr>
</tbody>
</table>
And then in component:
toggle = false
items = [
{ name: 'aaa', number: '3452342343' },
{ name: 'aaa', number: '3452342343' }
]
toggleItem(item) {
item.checked = !item.checked
this.toggle = this.items.every(item => item.checked)
}
toggleAll() {
this.toggle = !this.toggle
this.items.forEach(item => item.checked = this.toggle)
}
Note that this is important that you also handle cases when you check all checkboxes manually and it should automatically check checkAll checkbox. And vise versa.
Demo: http://plnkr.co/edit/Nw8Wk0kXSbQLkAE7yW0e?p=info
In Angular2 the preferred way is to bind HTML to a model, update the model and let Angular update the DOM like:
#Component({
selector: 'my-comp',
template: `
<table>
<thead>
<tr>
<th><input type="checkbox" name="selectAll" (click)="toggleAll($event.target.checked)"></th>
<th>Name</th>
<th>Number</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of contactlists let i=index">
<td><input type="checkbox" [(ngModel)]="checkedItems[i]"></td>
<td>{{item.name}}</td>
<td>{{item.value}}</td>
</tr>
</tbody>
</table>
`)}
export class MyComponent {
constructor() {
this.contactService.getName(this.pageNumber, this.pageSize)
.subscribe(
data => {
this.contactlist = data.results;
this.checkedItems = new Array(this.contactlist?.length).fill(false);
},
err => console.error(err),
() => console.log('Contact list fetched')
);
}
/* data set dynamically from `contactService` instead according to updated question
items = [
{name: abc, number: 9876373771, checked: false},
{name: def, number: 9876373772, checked: false},
{name: ghi, number: 9876373773, checked: false},
]; */
toggleAll(checked) {
this.items.forEach((item) => item.checked = checked);
}
}
I want to do a search of a table with multiple textboxes. The user should be able to enter address in the address box and search the table my address, and enter city in the city search box and search the table by city. I can't get it to work, I'm getting the error message: Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
Here's my html:
<table>
<thead>
<tr>
<th>
Address
</th>
<th>
City
</th>
</tr>
<tr>
<td>
<input ng-model="vm.search_address" />
</td>
<td>
<input ng-model="vm.search_city" />
</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="search in searchesFound = ( vm.searches | filter: {address: search_address, city: search_city})">
<td>
{{ search.address }}
</td>
<td>
{{ search.city }}
</td>
</tr>
</tbody>
</table>
ANd here's my controller:
(function () {
angular.module('crm.ma')
.controller('AdvancedSearchCtrl', function () {
var vm = this;
vm.search_address = "";
vm.search_city = "";
vm.searchs = [
{
address: '202 This St',
city : 'Columbus'
},
{
address: '205 That St',
city: 'Dayton'
}
]
});
})();
Any clues on what I'm doing wrong?
You forgot the vm object. In your filter vm.search_city and vm.search_address should be used:
<tr ng-repeat="search in vm.searchs | filter: {address: vm.search_address, city: vm.search_city}">
Look this:
var app = angular.module('ngApp', []);
app.controller('MainCtrl', ['$scope', function ($scope) {
$scope.smartphones = [
{brand: 'Apple', model: 'iPhone 4S', price: '999'},
{brand: 'Samsung', model: 'SIII', price: '888' },
{brand: 'LG', model: 'Optimus', price: '777'},
{brand: 'htc', model: 'Desire', price: '666'},
{brand: 'Nokia', model: 'N9', price: '555'}
];
$scope.search = function(){
angular.forEach($scope.smartphones, function(value, key) {
var brand = ($scope.queryBrand? $scope.queryBrand.toLowerCase():' ');
var model = ($scope.queryModel? $scope.queryModel.toLowerCase():' ');
value.show = (value.brand.toLowerCase().indexOf(brand) > -1 ||
value.model.toLowerCase().indexOf(model) > -1 || (brand == ' ' && model == ' '));
console.log(!brand && !model)
});
}
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.2.0/css/bootstrap-combined.min.css" rel="stylesheet">
<div ng-app="ngApp" ng-controller="MainCtrl" class="container">
<input class="span4 pull-right" type="text" placeholder="Filter by brand" ng-model="queryBrand" ng-change="search()">
<input class="span4 pull-right" type="text" placeholder="Filter by model" ng-model="queryModel" ng-change="search()">
<div class="row">
<table id="results" class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th>Brand</th>
<th>Model</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="smartphone in smartphones" ng-init="smartphone.show=true" ng-show="smartphone.show">
<td id="brand">{{smartphone.brand}}</td>
<td id="model">{{smartphone.model}}</td>
<td id="price">{{smartphone.price | currency}}</td>
</tr>
</tbody>
</table>
</div>
</div>
Does anyone have a clue how to create a loading text before angular loads data in to a list?
I have this code for getting info from different subwebs (in sharepoint). But before (or while) the list is loaded I want to display a text "Loading...":
context.executeQueryAsync(Function.createDelegate(this, function () {
var webs = this._webs.getEnumerator();
while (webs.moveNext()) {
var subWeb = webs.get_current();
$scope.sites.push({
title: webs.get_title(),
});
$scope.$apply();
}
}
}),
HTML:
<div ng-controller="MainCtrl">
<table>
<thead>
<tr>
<th>
Site
</th>
<th>
Created
</th>
</tr>
</thead>
<tbody ng-repeat="site in sites">
<tr>
<td>
{{site.title}}
</td>
<td>
{{site.created | date:dateFormat}}
</td>
</tr>
</tbody>
</table>
<tbody ng-if="sites.length == 0">
<tr>
<td>
<img src="loading.gif">
</td>
</tr>
</tbody>
<tbody ng-repeat="site in sites" ng-if="sites.length > 0">
<tr>
<td>
{{site.title}}
</td>
<td>
{{site.created | date:dateFormat}}
</td>
</tr>
</tbody>
This produce 2 watchers but it will help.
some links of loading indicator : angular-spinner or angular-sham-spinner
also BLOG detailing on how the spinner works with angularjs
and in case you want to implement it yourself, below code will get you started...
app.directive("spinner", function(){
return: {
restrict: 'E',
scope: {enable:"="},
template: <div class="spinner" ng-show="enable"><img src="content/spinner.gif"></div>
}
});
warning : i havent tested the code but directive wont be more complex than this...
Here is an ItemView template called activatetwoitem
<!-- <tr>-->
<td><%= gatewayJson.serial %></td>
<td>In Inventory</td>
<td><%= gatewayJson.macaddress %></td>
<!-- </tr>-->
Here is the CompositeView template called activatetwo
<table class="table table-hover">
<thead>
<tr>
<th>Gateway ID</th>
<th>In Group</th>
<th>MAC Address</th>
</tr>
</thead>
<tbody></tbody></table>
Here is the ItemView
Step2View = Backbone.Marionette.ItemView.extend
template: activatetwoitem
tagName: 'tr'
Here is the compositeView
Step2Views = Backbone.Marionette.CompositeView.extend
itemView: Step2View
template: activatetwo
itemViewContainer: "tbody"
Now I am getting this...
<table class="table table-hover">
<thead>
<tr>
<th>Gateway ID</th>
<th>In Group</th>
<th>MAC Address</th>
</tr>
</thead>
<tbody>
<tr>
<!-- <tr>-->
<tr>
<td>AX1OD</td>
<td>In Inventory</td>
<td>secret mac address</td>
<!-- </tr>-->
</tr>
</tr>
<tr>
<!-- <tr>-->
<tr>
<td>12RUM</td>
<td>In Inventory</td>
<td>secret mac address</td>
<!-- </tr>-->
</tr>
</tr>
</tbody>
Why am I getting that? If I take out the tagName then the somehow still shows up (with a div of course instead of the 2nd tr) any thoughts?
I didn't look at why, but when I took out the commented html from the template, the extra wrapper went away. Too bad, because I like the commented lines in the template to remind me about what is supposed to go there.