creating sub views(table inside another table) using backbone JS - backbone.js

Trying to create a table inside a table with backbone but could not able to find out a way. Can anybody help me out with an example to achieve this?
My Collection:
this.collection = new Backbone.Collection([
{ first: 'John', last: 'Doe', desig: 'E1', location: 'C' },
{ first: 'Mary', last: 'Jane', desig: 'E3', location: 'C' },
{ first: 'Billy', last: 'Bob', desig: 'E2', location: 'C' },
{ first: 'Dexter', last: 'Morgan', desig: 'E1', location: 'P' },
{ first: 'Walter', last: 'White', desig: 'E2', location: 'P' },
{ first: 'Billy', last: 'Bobby', desig: 'E1', location: 'B' }
]);
Normal View: Achieved this using a table view. refer code here
first last desig location
----------------------------------
Billy Bobby E1 B
Walter White E2 P
Dexter Morgan E1 P
Billy Bob E2 C
Marry Jane E3 C
John Doe E1 C
Want to group by location then want to render as new view like below
location first last desig
----------------------------------
C Billy Bob E2
Marry Jane E3
John Doe E1
P Walter White E2
Dexter Morgan E1
B Billy Bobby E1
Using underscore we can do grouping but after that, I am struggling to render that object in the above view
_.groupby(this.collection, "location");
is giving me an object which has the required result.

Every row in the table should be represented in a Backbone.View.
The grouping you want is mostly a rowspan feature of standard HTML tables.
See the snippet:
var collection = new Backbone.Collection([{
first: 'John',
last: 'Doe',
desig: 'E1',
location: 'Chennai'
}, {
first: 'Mary',
last: 'Jane',
desig: 'E3',
location: 'Chennai'
}, {
first: 'Billy',
last: 'Bob',
desig: 'E2',
location: 'Chennai'
}, {
first: 'Dexter',
last: 'Morgan',
desig: 'E1',
location: 'Pune'
}, {
first: 'Walter',
last: 'White',
desig: 'E2',
location: 'Pune'
}, {
first: 'Billy',
last: 'Bobby',
desig: 'E1',
location: 'Bangalore'
}]);
var GroupRowView = Backbone.View.extend({
tagName: 'tr',
initialize: function(options) {
this.groupBy = options.groupBy;
this.index = options.index;
this.total = options.total;
},
render: function() {
this.$el.empty();
if (this.index == 0) {
this.$el.append('<td rowspan="' + this.total + '">' + this.model.get(this.groupBy) + '</td>');
}
_.each(this.model.omit(this.groupBy), function(value, key) {
this.$el.append('<td>' + value + '</td>');
}, this);
return this;
}
});
var SimpleRowView = Backbone.View.extend({
tagName: 'tr',
render: function() {
this.$el.empty();
//this.$el.append('<td>' + this.model.get('location') + '</td>')
_.each(this.model.values(), function(value) {
this.$el.append('<td>' + value + '</td>');
}, this);
return this;
}
})
var TableView = Backbone.View.extend({
tagName: 'table',
render: function() {
/*var self = this;
self.$el.empty();
self.collection.each(function(rowModel) {
self.$el.append(_.template('<tr><td><%= location %></td><td><%= first %></td><td><%= last %></td><td><%= desig %></td></tr>')(rowModel.attributes))
});*/
var self = this;
self.$el.empty();
self.collection.each(function(model) {
var row = new SimpleRowView({
model: model
});
self.$el.append(row.render().el);
});
return this;
},
groupCollection: function() {
var self = this;
var groups = self.collection.groupBy('location');
self.$el.empty();
_.each(groups, function(group) {
var length = group.length;
_.each(group, function(model, i) {
var row = new GroupRowView({
model: model,
groupBy: 'location',
index: i,
total: length
});
self.$el.append(row.render().el);
})
})
}
});
var table = new TableView({
collection: collection
});
$('#table-container').append(table.render().el);
$('#sortBtn').click(function(e) {
table.groupCollection();
})
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
<p>What the table should be:</p>
<table border="1">
<thead>
<tr>
<th>Location</th>
<th>First</th>
<th>Last</th>
<th>Desig</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="3">C</td>
<td>Billy</td>
<td>Bob</td>
<td>E2</td>
</tr>
<tr>
<td>Marry</td>
<td>Jane</td>
<td>E3</td>
</tr>
<tr>
<td>John</td>
<td>Doe</td>
<td>E1</td>
</tr>
<tr>
<td rowspan="2">P</td>
<td>Walter</td>
<td>White</td>
<td>E2</td>
</tr>
<tr>
<td>Dexter</td>
<td>Morgan</td>
<td>E1</td>
</tr>
<tr>
<td rowspan="1">B</td>
<td>Billy</td>
<td>Bobby</td>
<td>E1</td>
</tr>
</tbody>
</table>
<p>What the resulting table is:</p>
<button id="sortBtn">Sort!</button>
<div id="table-container">
</div>

Updated fiddle with 2 different views using user input
jsfiddle.net/WebDev81/ddbf9ckv/10
Let me know anybody has a better approach to achieve this.

Related

How to render Marionette 3 CollectionView into a table column?

I'm currently working on a legacy program with symfony 1.4 and a bunch of outdated technologies. Recently we decided to add Backbone/Marionette to project in order to make things a bit easier.
The problem I'm facing is rendering a collection view into a table column. The table already exists and is functional with more than 2k LOC behind it, which makes it impossible for me to rewrite it ATM.
<table>
<thead>
<tr>
<th>Item</th>
<th>Quantity</th>
<th>Price</th>
<th>...</th>
<th></th> <!-- This is the column -->
<th>Total price</th>
</tr>
</thead>
<tbody>
...
</tbody>
</table>
All other columns, except the one specified by comment, are already rendered into the page. I've followed the documentations to Marionette attachHtml and attachBuffer but couldn't implement a working solution.
https://jsfiddle.net/cbzs67ar/
var collection = new Backbone.Collection([
{ id: 1, text: 'one' },
{ id: 2, text: 'two' },
{ id: 3, text: 'three' },
{ id: 4, text: 'four' },
{ id: 5, text: 'five' },
{ id: 6, text: 'six' },
{ id: 7, text: 'seven' }
])
var MyChildView = Mn.View.extend({
initialize() {
this.render();
},
template: _.template(': <%- text %>')
});
var CollectionView = Mn.CollectionView.extend({
el: '.tbody-hook',
childView: MyChildView,
buildChildView(model, ChildView) {
var view = new ChildView({
el: '.td-' + model.id,
model: model
});
return view;
},
attachHtml: _.noop,
attachBuffer: _.noop
});
var myRegion = new Marionette.Region({ el: '#some-region' });
myRegion.show(new CollectionView({ collection: collection }));
Use a collectionview to manage the pre-rendered child by a column selector. You'll need to disable the collectionview from attaching the children to it's own container and you'll need to force the children to render themselves after they're instantiated.

React - populate dropdown from array inside object and filter data

everyone. I have a React App that is supposed to filter planetarium shows by different criteria. One of the criteria is supposed to have multiple values:
var shows = [
{ id: 1, name: 'Black Holes', showType: 'Full Dome', age: 'All Ages', grade: ['Late Elementary', 'High School'], cover:'http://www.starsatnight.org/sciencetheater/assets/File/200x300_blackholes.png'},
{ id: 2, name: 'Astronaut', showType: 'Full Dome', age: 'All Ages', grade: ['Early Elementary,',' Late Elementary', 'High School'], cover:'http://www.starsatnight.org/sciencetheater/assets/File/200x300_astronaut.png'},
{ id: 3, name: 'Laser Holidays', showType: 'Laser', age: '18+', grade: ['Late Elementary', 'High School', 'College'], cover:'http://www.starsatnight.org/sciencetheater/assets/File/200x300_laserholidays.png'},
{ id: 4, name: 'The Gruffalo', showType: 'Flat Screen', age: 'All Ages', grade: ['Pre-K', 'Kinder'], cover:'http://www.starsatnight.org/sciencetheater/assets/File/200x300_gruffalo.png'},
{ id: 5, name: 'Laser iPOP', showType: 'Laser', age: 'All Ages', grade: ['Late Elementary', 'High School', 'College'], cover:'http://www.starsatnight.org/sciencetheater/assets/File/200x300_ipop.png'}
];
The "grade" property of the "shows" object can have multiple values, and I decided to put them in an array.
I need two things:
1 - I need to populate the "grade" dropdown with all the possible values, without repeated values;
2 - I need to be able filter shows according to what the user select in that dropdown, similar to what the "Show Type" and "Age" dropdowns. Any idea on how to do this? Thanks.
var shows = [
{ id: 1, name: 'Black Holes', showType: 'Full Dome', age: 'All Ages', grade: ['Late Elementary', 'High School'], cover:'http://www.starsatnight.org/sciencetheater/assets/File/200x300_blackholes.png'},
{ id: 2, name: 'Astronaut', showType: 'Full Dome', age: 'All Ages', grade: ['Early Elementary,',' Late Elementary', 'High School'], cover:'http://www.starsatnight.org/sciencetheater/assets/File/200x300_astronaut.png'},
{ id: 3, name: 'Laser Holidays', showType: 'Laser', age: '18+', grade: ['Late Elementary', 'High School', 'College'], cover:'http://www.starsatnight.org/sciencetheater/assets/File/200x300_laserholidays.png'},
{ id: 4, name: 'The Gruffalo', showType: 'Flat Screen', age: 'All Ages', grade: ['Pre-K', 'Kinder'], cover:'http://www.starsatnight.org/sciencetheater/assets/File/200x300_gruffalo.png'},
{ id: 5, name: 'Laser iPOP', showType: 'Laser', age: 'All Ages', grade: ['Late Elementary', 'High School', 'College'], cover:'http://www.starsatnight.org/sciencetheater/assets/File/200x300_ipop.png'}
];
// FilterForm React Class
var FilterForm = React.createClass({
getInitialState: function() {
return {
data: this.props.data,
showType: '',
age: '',
grade: '',
}
},
filterItems: function(val, type){
switch (type) {
case 'showType':
this.setState({showType: val});
break;
case 'age':
this.setState({age: val});
break;
case 'grade':
this.setState({grade: val});
break;
default:
break;
}
},
render: function() {
var filteredItems = this.props.data;
var state = this.state;
["showType", "age", "grade"].forEach(function(filterBy) {
var filterValue = state[filterBy];
if (filterValue){
filteredItems = filteredItems.filter(function(item){
return item[filterBy] === filterValue;
});
}
});
var showTypeArray = this.props.data.map(function(item) {return item.showType});
var ageArray = this.props.data.map(function(item) {return item.age});
// This array gets once instance of all grade options since one show can be good for several grades
var gradeArray = this.props.data.map(function(item){
return item.grade;
});
showTypeArray.unshift("");
ageArray.unshift("");
gradeArray.unshift("");
return(
<div className="container">
<FilterOptions
data={this.state.data}
showTypeOptions={showTypeArray}
ageOptions={ageArray}
gradeOptions={gradeArray}
changeOption={this.filterItems} />
<div className="filter-form">
<FilterItems data={filteredItems} />
</div>
</div>
)
}
});
// FilterOptions React Class
var FilterOptions = React.createClass({
changeOption: function(type, e) {
var val = e.target.value;
this.props.changeOption(val, type);
},
render: function(){
return (
<div className="filter-options">
<div className="filter-option">
<label>Show Type</label>
<select id="showType" value={this.props.showType} onChange={this.changeOption.bind(this, 'showType')}>
{this.props.showTypeOptions.map(function(option) {
return ( <option key={option} value={option}>{option}</option>)
})}
</select>
<label>Age</label>
<select id="age" value={this.props.age} onChange={this.changeOption.bind(this, 'age')}>
{this.props.ageOptions.map(function(option) {
return ( <option key={option} value={option}>{option}</option>)
})}
</select>
<label>Grade</label>
<select id="grade" value={this.props.grade} onChange={this.changeOption.bind(this, 'grade')}>
{this.props.gradeOptions.map(function(option) {
return ( <option key={option} value={option}>{option}</option>)
})}
</select>
</div>
</div>
);
}
});
// FilterItems React Class
var FilterItems = React.createClass({
render: function(){
return(
<div className="filter-items">
<br />
{this.props.data.map(function(item){
return(
<div className="filter-item">{item.id} - {item.name} - {item.showType} - {item.age}</div>
);
})}
</div>
)
}
});
ReactDOM.render(
<FilterForm data={shows}/>,
document.getElementById('show-catalog')
);
<script src="https://unpkg.com/react#15/dist/react.js"></script>
<script src="https://unpkg.com/react-dom#15/dist/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
<div id="show-catalog"></div>
1) You can get a set of unique values from an array using Set. For example,
var gradeArray = this.props.data.map(function(item){
return item.grade;
});
var uniqueGrades = Array.from(new Set(gradeArray));
2) What is not working about your current code?

ngTable select all filtered data

I'm trying to select only the rows from an ng-table with filtered data. Using the following code filters the table rows but even the rows that aren't shown are being selected:
controller:
let sampleData = [{id: 1, name: 'John Doe', gender: 'Male'},
{id: 2, name: 'Jane Doe', gender: 'Female'},
{id: 3, name: 'Mary Jane', gender: 'Female'},
{id: 4, name: 'Mary Poppins', gender: 'Female'}];
$scope.tableParams = new NgTableParams({
}, {
dataset: sampleData
});
$scope.checkboxes = {
checked: false,
items: {}
};
$scope.$watch(() => {
return $scope.checkboxes.checked;
}, (value) => {
sampleData.map((item) => {
$scope.checkboxes.items.id = value;
});
});
$scope.$watch(() => {
return $scope.checkboxes.items;
}, () => {
let checked = 0;
let unchecked = 0;
let total = sampleData.length;
sampleData.map((item) => {
if ($scope.checkboxes.items.id) {
checked++;
} else {
unchecked++;
}
});
if (unchecked === 0 || checked === 0) {
$scope.checkboxes.checked = checked === total;
}
angular.element($element[0].getElementsByClassName('select-all')).prop('indeterminate', (checked != 0 && unchecked != 0));
});
html:
<script type="text/ng-template" id="checkbox.html">
<input type="checkbox" ng-model="checkboxes.checked" class="select-all" value="">
</script>
<table class="table" ng-table="tableParams" show-filter="true">
<tr ng-repeat="item in $data track by $index">
<td header="'checkbox.html'">
<input type="checkbox" ng-model="checkboxes.items[item.id]">
</td>
<td data-title="'Name'" filter="{'name': 'text'}">
{{item.name}}
</td>
<td data-title="'Gender'" filter="{'gender': 'text'}">
{{item.gender}}
</td>
</tr>
</table>
When the table is filtered via name or gender, the table rerenders with the filtered data. When you click on the select all checkbox while the table is filtered, the filtered rows are selected. Unfortunately, when you clear the filter, the previously filtered out rows are also selected. The same is true when selecting all the filtered rows and then triggering an action that's supposed to get the selected items. (All ids are selected for the action.)
How can I only select the filtered rows? Thank you.
Okay, I got it to work. I just added a tableData object to the $scope so I can store the filtered data there. That's what I used for checking the selected items.
let sampleData = [{id: 1, name: 'John Doe', gender: 'Male'},
{id: 2, name: 'Jane Doe', gender: 'Female'},
{id: 3, name: 'Mary Jane', gender: 'Female'},
{id: 4, name: 'Mary Poppins', gender: 'Female'}];
$scope.tableData = {
filteredData: [],
checkboxes: {
checked: false,
items: {}
}
};
$scope.tableParams = new NgTableParams({
page: 1,
count: 10
}, {
total: data.length;
getData: ($defer, params) => {
let filter = params.filter();
let count = params.count();
let page = params.page();
let filteredData = filter ? $filter('filter')(data, filter) : data;
params.total(filteredData.length);
$scope.tableData.filteredData = filteredData;
$defer.resolve(filteredData.slice((page - 1) * count, page * count));
}
});
$scope.$watch(() => {
return $scope.tableData.checkboxes.checked;
}, (value) => {
$scope.tableData.filteredData.map((item) => {
$scope.tableData.checkboxes.items[item].id = value;
});
});
$scope.$watch(() => {
return $scope.tableData.checkboxes.items;
}, () => {
let checked = 0;
let unchecked = 0;
let data = $scope.tableData.filteredData;
let total = data.length;
let checkboxes = $scope.tableData.checkboxes;
data.map((item) => {
if (checkboxes.items[item].id) {
checked++;
} else {
unchecked++;
}
});
if (unchecked === 0 || checked === 0) {
checkboxes.checked = checked === total;
}
angular.element($element[0].getElementsByClassName('select-all')).prop('indeterminate', (checked != 0 && unchecked != 0));
});
Not really sure if this is the best way to go about it. Also, this doesn't change the select all checkbox's state to indeterminate when you filter > select all > clear filter.
in your second watch, change to return $scope.tableData.filteredData; may solve your problem

orderBy dd/mm/yyyy by year angular

The data I am receiving from a webservice is formatted as dd/mm/yyyy. The problem with this is when sorting, it sorts by dd rather than yyyy.
<td ng-repeat="thead in resultHeader">
{{thead.head}}
<span class="sortorder" ng-show="predicate === thead.line" ng-class="{reverse:reverse}"></span>
</td>
Controller:
$scope.order = function(predicate) {
var results = $scope.model.resultList;
$scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false;
$scope.predicate = predicate;
$scope.model.currentPage = 1;
$scope.model.beginFrom = 0;
};
How can I sort this data by yyyy with my current set up?
{
"name": "Test",
"ShipmentDate": "06\/08\/2012"
}
The key part is to add a $filter to your module and use that $filter to get the Date value from the string. you can use Date.parse('dd/mm/yyyy') to get the time in float, and then run an Array.sort() to your data.
If you converted your date strings to Date objects, you can use the orderBy: filter to sort by date.
<td ng-repeat="thead in resultHeader | orderBy:'ShipmentDate':shouldBeReversedOrder ">
{{thead.head}}
<span class="sortorder" ng-show="predicate === thead.line" ng-class="{reverse:reverse}"></span>
</td>
When you want to display the date back in a proper formate you can use {{thead.ShipmentDate | 'yyyy-mm-dd'}} to format it for you.
Use the custom order for this. Look:
function MyCtrl($scope, orderByFilter) {
$scope.sortedFriends = [
{
name: 'John',
age: 25,
dateTest: '10/10/2015'
}, {
name: 'Jimmy',
age: 25,
dateTest: '10/12/2015'
},{
name: 'Mary',
age: 28,
dateTest: '10/09/2009'
}, {
name: 'Ed',
age: 27,
dateTest: '30/03/2014'
},{
name: 'Andrew',
age: 27,
dateTest: '11/11/2016'
}];
$scope.orderByCustom = function(friend) {
console.log(friend)
return friend.dateTest.split('/')[2];
};
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app ng-controller="MyCtrl">
<ul>
<li ng-repeat="friend in sortedFriends | orderBy:orderByCustom"">
{{friend}}
</li>
</ul>
</div>

How to edit a table row using angluarJS?

I'm searching for a solution to edit in-line table rows. Pretty much like this fiddle, but with populated comboboxes too.
function Ctrl($scope) {
$scope.model = {
contacts: [{
id: 1,
name: "Ben",
age: 28
}, {
id: 2,
name: "Sally",
age: 24
}, {
id: 3,
name: "John",
age: 32
}, {
id: 4,
name: "Jane",
age: 40
}],
selected: {}
};
// gets the template to ng-include for a table row / item
$scope.getTemplate = function (contact) {
if (contact.id === $scope.model.selected.id) return 'edit';
else return 'display';
};
$scope.editContact = function (contact) {
$scope.model.selected = angular.copy(contact);
};
$scope.saveContact = function (idx) {
console.log("Saving contact");
$scope.model.contacts[idx] = angular.copy($scope.model.selected);
$scope.reset();
};
$scope.reset = function () {
$scope.model.selected = {};
};
});
How can I make inline editable comboboxes ? Each line should have a name, age and a group of options.
It would be easier, and I would venture to say nicer, if you use ng-grid instead of html tables. The grid has built-in editing, and you can customize the kind of editor, such that your cell could display as plain text, but use a combo box for editing.
Here is a plunker that shows an editable combo box on the Gender column:
http://plnkr.co/edit/zsxqZNQCnpFySjSWcf1D?p=preview
{field:'gender', displayName: 'Gender', enableCellEdit: true,editableCellTemplate: '<span id="gender"><select ng-class="\'colt\' + col.index" + ng-input="COL_FIELD" ng-model="COL_FIELD" ng-options="c.value as c.name for c in genders"></select></span>'}]
And here is the documentation for ng-grid:
http://angular-ui.github.io/ng-grid/

Resources