Rendering a table in Angular 1 without ng-repeat - javascript

I'm trying to find a better way for rendering a large table in Angular than using ngRepeat.
I tried using one-time {{::binding}}s, but found the initial render time remained unchanged, which wasn't satisfactory.
Back to the drawing board, I'm trying to find a much more performant method for assembling data into a HTML table in the DOM. I've been trying to use angular.element() to assemble all the parts into a whole, but with no luck. Any insights?
var app = angular.module('app', []);
app.directive('myTable', function() {
return {
restrict: 'E',
scope: {
ngModel: "=",
},
require: 'ngModel',
link: function(scope, element, attrs) {
scope.$watch('ngModel', function() {
if (typeof scope.ngModel == 'undefined') {
console.log("ngModel not defined yet");
return;
}
element.html('');
var table = angular.element('<table/>');
var tbody = angular.element('<tbody/>');
scope.ngModel.forEach(function(m) {
var tr = angular.element('<tr/>');
m.fields.forEach(function(f) {
var td = angular.element('<td/>')
td.text(f.value);
td.append(tr);
});
tr.append(tbody);
})
tbody.append(table);
table.append(element);
})
}
}
});
app.controller('AppController', function($scope) {
$scope.data = [{
fields: [{
value: 1,
metadata: ""
}, {
value: 2,
metadata: ""
}, {
value: 3,
metadata: ""
}, {
value: 4,
metadata: ""
}, {
value: 5,
metadata: ""
}, ]
},
{
fields: [{
value: 6,
metadata: ""
}, {
value: 7,
metadata: ""
}, {
value: 8,
metadata: ""
}, {
value: 9,
metadata: ""
}, {
value: 10,
metadata: ""
}, ]
}
]
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body>
<div ng-app="app" ng-controller="AppController">
<my-table ng-model="data"></my-table>
</div>
</body>

It is inappropriate to involve the ngModel controller in a directive that has no user input elements. Also isolate scope is unnecessary. Evaluate the table-data attribute in the watch expression.
Also of course, fix the backwards append statements:
app.directive('myTable', function() {
return {
restrict: 'E',
̶s̶c̶o̶p̶e̶:̶ ̶{̶
̶n̶g̶M̶o̶d̶e̶l̶:̶ ̶"̶=̶"̶,̶
̶}̶,̶
̶r̶e̶q̶u̶i̶r̶e̶:̶ ̶'̶n̶g̶M̶o̶d̶e̶l̶'̶,̶
link: function(scope, element, attrs) {
scope.$watch(attrs.tableData ̶'̶n̶g̶M̶o̶d̶e̶l̶'̶, function(data) {
if (data) {
console.log("table-data not defined yet");
return;
}
element.html('');
var table = angular.element('<table/>');
var tbody = angular.element('<tbody/>');
data.forEach(function(m) {
var tr = angular.element('<tr/>');
m.fields.forEach(function(f) {
var td = angular.element('<td/>')
td.text(f.value);
̶t̶d̶.̶a̶p̶p̶e̶n̶d̶(̶t̶r̶)̶;̶ tr.append(td);
});
̶t̶r̶.̶a̶p̶p̶e̶n̶d̶(̶t̶b̶o̶d̶y̶)̶;̶ tbody.append(tr);
})
̶t̶b̶o̶d̶y̶.̶a̶p̶p̶e̶n̶d̶(̶t̶a̶b̶l̶e̶)̶;̶ table.append(tbody);
̶t̶a̶b̶l̶e̶.̶a̶p̶p̶e̶n̶d̶(̶e̶l̶e̶m̶e̶n̶t̶)̶;̶ element.append(table);
})
}
}
});
Usage:
<my-table table-data="data"></my-table>
THE DEMO
var app = angular.module('app', []);
app.directive('myTable', function() {
return {
restrict: 'E',
link: function(scope, element, attrs) {
scope.$watch(attrs.tableData, function(data) {
console.log(data);
if (!data) {
console.log("tableData not defined yet");
return;
}
element.html('');
var table = angular.element('<table/>');
var tbody = angular.element('<tbody/>');
data.forEach(function(m) {
var tr = angular.element('<tr/>');
m.fields.forEach(function(f) {
var td = angular.element('<td/>')
td.text(f.value);
tr.append(td);
});
tbody.append(tr);
})
table.append(tbody);
element.append(table);
})
}
}
});
app.controller('ctrl', function($scope) {
$scope.data = [{
fields: [{
value: 1,
metadata: ""
}, {
value: 2,
metadata: ""
}, {
value: 3,
metadata: ""
}, {
value: 4,
metadata: ""
}, {
value: 5,
metadata: ""
}, ]
},
{
fields: [{
value: 6,
metadata: ""
}, {
value: 7,
metadata: ""
}, {
value: 8,
metadata: ""
}, {
value: 9,
metadata: ""
}, {
value: 10,
metadata: ""
}, ]
}
]
})
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="app" ng-controller="ctrl">
<my-table table-data="data"></my-table>
</body>

Related

Easy Autocomplete nested Json structure

I'm having a hard time to structure the list location for items array in order to access name attribute in search.js
Below is the nested JSON structure:
{
menus: [
{
name: "Summer ",
url: "/menus/2",
items: [
{
name: "man o man", //this is what I'm trying to access
url: "/menus/2/items/7"
}
]
]
}
So far I've tried in search.js:
document.addEventListener("turbolinks:load", function() {
$input = $("[data-behavior='autocomplete']")
var options = {
getValue: "name",
url: function(phrase) {
data = "/search.json?q=" + phrase;
return data;
},
categories: [
{
listLocation: "menus",
header: "--<strong>Menus</strong>--",
},
{
listLocation: "items", //this is where I'm having problem with
header: "--<strong>Items</strong>--",
}
],
list: {
onChooseEvent: function() {
var url = $input.getSelectedItemData().url
$input.val("")
Turbolinks.visit(url)
}
}
}
$input.easyAutocomplete(options)
})

selectable table modal popup in angular js

I am trying to open a modal popup with table. How can I do this? In my app.js, on the click event of row open a modal, I also want to update some field with the selected item value. But i can't update with selected value.
my app.js
var tableApp = angular.module('tableApp', ['ui.bootstrap']);
tableApp.controller('tableController', function ($scope,$rootScope, $filter, $modal) {
$scope.filteredPeople = [];
$scope.currentPage = 1;
$scope.pageSize = 10;
$scope.people = [{ id: "1", name: "joe",disable:true },
{ id: "2", name: "bill", disable: true },
{ id: "3", name: "john", disable: true },
{ id: "1", name: "joe", disable: true },
{ id: "2", name: "bill", disable: true },
{ id: "3", name: "john", disable: true },
{ id: "1", name: "joe", disable: true },
{ id: "2", name: "bill", disable: true },
{ id: "3", name: "john", disable: true },
{ id: "1", name: "joe", disable: true },
{ id: "2", name: "bill", disable: true },
{ id: "3", name: "john", disable: true },
{ id: "1", name: "joe" },
{ id: "2", name: "bill", disable: true },
{ id: "3", name: "john", disable: true }];
$scope.getPage = function () {
var begin = (($scope.currentPage - 1) * $scope.pageSize);
var end = begin + $scope.pageSize;
$scope.filteredPeople = $filter('filter')($scope.people, {
id: $scope.idFilter,
name: $scope.nameFilter
});
$scope.totalItems = $scope.filteredPeople.length;
$scope.filteredPeople = $scope.filteredPeople.slice(begin, end);
};
$scope.getPage();
$scope.pageChanged = function () {
$scope.getPage();
};
$scope.open = function () {
$scope.id = generateUUID();
};
$scope.dblclick = function (index) {
for (var i = 0; i < $scope.filteredPeople.length; i++) {
$scope.filteredPeople[i].disable = true;
}
return index.disable = false;
}
$scope.rowSelect = function (rowdata) {
alert(rowdata.name);
}
});
tableApp.controller('DetailModalController', [
'$scope', '$modalInstance', 'item',
function ($scope, $modalInstance, item) {
$scope.item = item;
$scope.dismiss = function () {
$modalInstance.dismiss();
};
$scope.close = function () {
$modalInstance.close($scope.item);
};
}]);
tableApp.directive('myModal', function ($log, $compile) {
var parm = [];
return {
restrict: 'E',
templateUrl: 'modalBase.html',
scope: {
modal: '=',
idF:'='
},
link: function (scope, element, attrs) {
debugger;
parm.name = attrs.idf;
}
//controller: function ($scope) {
// debugger;
// console.log($scope);
// $scope.selected = {
// item: $scope.modal.items[0]
// };
// $scope.ok = function () {
// debugger;
// alert(parm.name);
// $scope.modal.instance.close($scope.selected);
// };
// $scope.cancel = function () {
// $scope.modal.instance.dismiss('cancel');
// };
// $scope.modal.instance.result.then(function (selectedItem) {
// $scope.selected = selectedItem;
// }, function () {
// $log.info('Modal dismissed at: ' + new Date());
// });
//}
};
});
As I understand, you use angular.ui. I would suggesst you to use $modal service instead of $modalInstance. Using that you can call your modal instance with $modal.open(). And also you don't need to close it in your controller - place appropriate methods on your modal template and it will work by its services
Template:
<script type="text/ng-template" id="myModalContent.html">
<div class="modal-header">
<h3 class="modal-title">I'm a modal!</h3>
</div>
<div class="modal-body">
<ul>
<li ng-repeat="item in items">
{{ item }}
</li>
</ul>
Selected: <b>{{ selected.item }}</b>
</div>
<div class="modal-footer">
<button class="btn btn-primary" type="button" ng-click="$close()">OK</button>
<button class="btn btn-warning" type="button" ng-click="$dismiss('cancel')">Cancel</button>
</div>
</script>
Controlelr
var modalInstance = $uibModal.open({
templateUrl: 'myModalContent.html',
controller: 'ModalInstanceCtrl',
resolve: {
items: function () {
return $scope.items;
}
}
});
modalInstance.result.then(function (selectedItem) {
$scope.selected = selectedItem;
}, function () {
$log.info('Modal dismissed at: ' + new Date());
});
};
You can find more info about it in angular.ui documentation for modals

Adding extra/default option using ng-options

I am building a tag-manager in an angular form that uses two dropdown menus (in this demo a food category and a specific item). When the user selects a food category the item dropdown should appear, and when that dropdown has a value selected the I want a string added to my tag list in the format of ': '. Below is the code:
app.js
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope){
$scope.tags = [];
$scope.userCategory;
$scope.userFood;
$scope.primaryFoods = [
{
'id': 1,
'parent_id': null,
'name': 'Pizza'
},
{
'id': 4,
'parent_id': null,
'name': 'Burgers'
},
{
'id': 7,
'parent_id': null,
'name': 'Pasta'
},
];
$scope.secondaryFoods = [
{
'id': 2,
'parent_id': 1,
'name': 'Cheese Pizza'
},
{
'id': 3,
'parent_id': 1,
'name': 'Combo Pizza'
},
{
'id': 5,
'parent_id': 4,
'name': 'Cheese Burgers'
},
{
'id': 6,
'parent_id': 4,
'name': 'Hamburgers'
},
];
});
app.directive('doubleTagManager', function() {
return {
restrict: 'E',
scope: {tags: '=', primary: '=', secondary: '=', userPrimary: '=', userSecondary: '='},
templateUrl: 'double-tag-manager.html',
link: function ($scope, $element) {
var input = angular.element($element.find('select')[1]);
// This adds the new tag to the tags array in '<Primary>: <Secondary>' format
$scope.add = function() {
var new_value = input[0].value;
if ($scope.tags.indexOf(new_value) < 0) {
$scope.tags.push($scope.userPrimary.name + ': ' + $scope.userSecondary.name);
}
};
// This is the ng-click handler to remove an item
$scope.remove = function (idx) {
$scope.tags.splice(idx, 1);
};
input.bind( 'change', function (event) {
$scope.$apply($scope.add);
});
}
};
});
double-tag-manager.html
<div class="row">
<div class="col-md-6">
<select name="uFoodsPrimary" id="foodPrimary" class="form-control"
ng-model="userPrimary"
ng-options="item.name for item in primary track by item.name" required>
<option value="">Select a Food category!</option>
</select>
</div>
<div class="col-md-6" ng-show="userPrimary">
<select name="uFoodsSecondary" id="foodSecondary" class="form-control"
ng-model="userSecondary"
ng-options="item.name for item in (secondary | filter: {parent_id: userPrimary.id})
track by item.name"
required>
<option value="">Select a Food sub-category!</option>
</select>
</div>
</div>
<div class="tags">
<a ng-repeat="(idx, tag) in tags" class="tag" ng-click="remove(idx)">{{tag}}</a>
</div>
What I would like to add is the ability to select 'All foods' so users don't need to select all the items individually but I cannot seem to figure out how to add an additional field using ng-options.
Fiddle
BONUS: If a category is selected that has no children I would like it added to the tags list by default.
Erik,
Here is the modified code to achieve your all select feature. Further you can enhance it more to achieve your BONUS use case.
Rather than putting too much effort to achieve tagging in this way, I would suggest to use existing angularui-select2 component. It has lot of other options also. It will make your life more easy.
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.tags = [];
$scope.sub_cat_show = false;
$scope.all_sub_cat_show = false;
$scope.userCategory;
$scope.userFood;
$scope.primaryFoods = [{
'id': 0,
'parent_id': null,
'name': 'All Foods'
}, {
'id': 1,
'parent_id': null,
'name': 'Pizza'
}, {
'id': 4,
'parent_id': null,
'name': 'Burgers'
}, {
'id': 7,
'parent_id': null,
'name': 'Pasta'
}];
$scope.secondaryFoods = [
{
'id': 2,
'parent_id': 1,
'name': 'Cheese Pizza'
}, {
'id': 3,
'parent_id': 1,
'name': 'Combo Pizza'
}, {
'id': 5,
'parent_id': 4,
'name': 'Cheese Burgers'
}, {
'id': 6,
'parent_id': 4,
'name': 'Hamburgers'
}, ];
});
app.directive('doubleTagManager', function() {
return {
restrict: 'E',
scope: {
tags: '=',
primary: '=',
secondary: '=',
userPrimary: '=',
userSecondary: '=',
sub_cat_show: '=',
'all_sub_cat_show': '='
},
template: "<div class='row'><div class='col-md-6'><select ng-change='primaryChange()' name='uFoodsPrimary' id='foodPrimary' class='form-control' ng-model='userPrimary' ng-options='item.name for item in primary track by item.name' required> <option value=''>Select a Food category!</option></select></div><div ng-show='sub_cat_show' class='col-md-6'><select ng-show='all_sub_cat_show' ng-change='secondaryChange()' name='uFoodsSecondary' id='foodSecondary' class='form-control' ng-model='userSecondary'" +
//options code
"ng-options='item.name for item in (secondary | filter: {parent_id: userPrimary.id}) track by item.name' required>" +
//end options code
"<option value=''>Select a Food sub-category!</option></select> <select ng-show='!all_sub_cat_show'><option value=''>Food all sub-category</option></select> </div></div><div><a ng-repeat='(idx, tag) in tags' class='tag' ng-click='remove(idx)'>{{tag}}</a></div>",
link: function($scope, $element) {
var primarySel = angular.element($element.find('select')[0]);
var secondarySel = angular.element($element.find('select')[1]);
// This adds the new tag to the tags array in '<Primary>: <Secondary>' format
$scope.primaryChange = function() {
$scope.tags = []; // reset
$scope.sub_cat_show = primarySel[0].value?true:false;
if (primarySel[0].value == 'All Foods')
{
$scope.all_sub_cat_show = false;
angular.forEach($scope.primary, function(primary, index) {
angular.forEach($scope.secondary, function(secondary, index) {
if(secondary.parent_id==primary.id)
$scope.tags.push(primary.name + ': ' + secondary.name);
});
});
}
else
{
$scope.all_sub_cat_show = true;
}
}
$scope.secondaryChange = function() {
var new_value = secondarySel[0].value;
if ($scope.tags.indexOf(new_value) < 0) {
$scope.tags.push(primarySel[0].value + ': ' + new_value);
}
};
// This is the ng-click handler to remove an item
$scope.remove = function(idx) {
$scope.tags.splice(idx, 1);
};
}
};
});
<script src="https://code.angularjs.org/1.4.3/angular-animate.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js"></script>
<!DOCTYPE html>
<html >
<head>
<meta charset="utf-8" />
<title>AngularJS Demo</title>
<script>
document.write('<base href="' + document.location + '" />');
</script>
</head>
<body>
<div ng-app="myApp" ng-controller="myCtrl">
<double-tag-manager tags="tags" primary="primaryFoods" secondary="secondaryFoods" user-primary="userCategory" user-secondary="userFood">
</double-tag-manager>
</div>
</body>
</html>
After looking more into stack overflow the best (and perhaps only) solution was to chain another filter that would clone the filtered array and unshift another option.
The new filter:
app.filter('addAll', function () {
return function(input) {
// clone the array, or you'll end up with a new "None" option added to your "values"
// array on every digest cycle.
var newArray = input.slice(0);
newArray.unshift({name: "All Foods"});
return newArray;
};
});
Updated ng-options tag:
ng-options="item.name for item in (secondary | filter: {parent_id: userPrimary.id} | addAll)
track by item.name"
SO post
Updated Fiddle

How do i correctly put a $scope.$watch on a two dimensional array?

Putting an Angular watch on a multidimensional array is proving problematic
I have a screen where the user will see two teams (outer array) with a teamsheet(inner array) for each team. He is able to drag and drop the players to change the batting order.
The battingOrder is updated according to the player's spot in the array.
The following solution is a bit of a cop out/short term, but it does solve my immediate stumbling block.
A more preferable solution would be appreciated though.
The following code is in my controller.
$scope.$watch(
"Teams[0].TeamSheet",
function (newValue, oldValue) {
for (var i = 0; i < newValue.length; i++) {
newValue[i].battingOrder = i + 1;
}
},
true
);
$scope.$watch(
"Teams[1].TeamSheet",
function (newValue, oldValue) {
for (var i = 0; i < newValue.length; i++) {
newValue[i].battingOrder = i + 1;
}
},
true
);
$scope.Teams = [{
TeamName: "South-Africa",
TeamSheet: [
{
name: "A Peterson",
battingOrder: 1,
mode: "view"
},
{
name: "Riley Rossouw",
battingOrder: 2,
mode: "view"
},
{
name: "H Amla",
battingOrder: 3,
mode: "view"
},
{
name: "F Du Plessis",
battingOrder: 4,
mode: "view"
},
{
name: "AB De Villiers",
battingOrder: 5,
mode: "view"
}
]
},
{
TeamName: "Australia",
TeamSheet: [
{
name: "D Warner",
battingOrder: 1,
mode: "view"
},
{
name: "S Watson",
battingOrder: 2,
mode: "view"
},
{
name: "M Clarke",
battingOrder: 3,
mode: "view"
},
{
name: "C Rogers",
battingOrder: 4,
mode: "view"
},
{
name: "S Smith",
battingOrder: 5,
mode: "view"
}
]
}];
you can try to update batting order directly inside the view:
<div ng-repeat="member in team" ng-init="member.battingOrder = $index">
</div>
then you do not need to use $watch
EDIT
the previous solution will not work after you swap items in your array. Use the following code to overcome this problem:
<body ng-controller="MainCtrl">
<div ng-repeat="member in team">
{{setIndex(member,$index)}}
{{member.name}},{{member.index}}
</div>
<button ng-click="swap()">swap first with last</button>
</body>
controller:
app.controller('MainCtrl', function($scope) {
$scope.team = [ { index: 0, name : 'first' }, { index: 0, name : 'second' }, { index: 0, name : 'third' } ];
$scope.swap = function () {
var tmp = $scope.team[0];
$scope.team[0] = $scope.team[2];
$scope.team[2] = tmp;
}
$scope.setIndex = function (member, index)
{
member.index = index;
}
});
http://plnkr.co/edit/V3ixkww7dxcx8uZ7f0hj?p=preview

Angularjs: call a controller method with param from a directive dropdown on change event

I have made a dropdown directive. On selecting a value from the dropdown, a method from the controller is called with the selected filter passed. Everything works fine except that the method is always returning the default selected value, no matter, what is selected.
html:
<div ng-controller="Ctrl">
<div>
<dropdown filters="filters" filter="filter" select="selectFilter(filter)"></dropdown>
</div>
</div>
javascript:
var app = angular.module('app', []);
function Ctrl($scope){
$scope.filters = [
{ Id: 1, Name: "All" }, { Id: 2, Name: "filter1" }, { Id: 3, Name: "filter2" }, { Id: 4, Name: "filter3"}
];
$scope.filter = $scope.filters[0];
$scope.selectFilter = function(selectedFilter){
alert(selectedFilter.Name);
};
}
app.directive('dropdown', function(){
return {
restrict: "E",
scope: {
filter: "=",
filters: "=",
select: "&"
},
template: "<select ng-model=\"filter\" ng-change=\"select(filter)\" ng-options=\"f.Name for f in filters\"></select>"
};
});
Working fiddle: http://jsfiddle.net/wXV6Z/98/
You're using wrong syntax to call method binding.
Try:
ng-change=\"select({filter:filter})\
DEMO
I made this, but the other answer seems better :P
http://jsfiddle.net/wXV6Z/101/
var app = angular.module('app', []);
function Ctrl($scope, $element){
$scope.filters = [
{ Id: 1, Name: "All" }, { Id: 2, Name: "filter1" }, { Id: 3, Name: "filter2" }, { Id: 4, Name: "filter3"}
];
$scope.filter = $scope.filters[0];
}
app.directive('dropdown', function(){
return {
restrict: "E",
scope: {
filter: "=",
filters: "=",
},
template: "<select ng-model=\"filter\" ng-change=\"selectFilter(filter)\" ng-options=\"f.Name for f in filters\"></select>",
controller: function($scope, $element) {
$scope.selectFilter = function(selectedFilter) {
alert(selectedFilter.Name);
}
}
}
});

Categories

Resources