AngularJs To Do list Directive - angularjs

Edit
JS Fiddle added JSFiddle
I'm trying to learn AngularJs and so I'm trying to take the "To Do" application to the next level.
Instead of an item being done or not, I want to have three states: (status)
0 = Waiting
1 = Working
2 = Completed
I also want to have a priority:
1 = High
2 = Medium
3 = Low
Here is my data:
[
{"taskId":1,"description":"Test 1.","priority":1,"status":0}
{"taskId":2,"description":"Test 2.","priority":1,"status":0}
{"taskId":3,"description":"Test 3.","priority":1,"status":1}
]
I've got three lists that displays each status wonderfully. When I move an item from one status to another, it goes away from the original list and appears in the appropriate list for the new status.
However, I am now working with directives and I want to turn those three separate lists into a single directive and it's really kicking my butt.
This is the hard-coded version of my To-Do list that displays the To-Do items in a 'Waiting' status.
<h3 style="display: inline-block;">Waiting</h3>
<div class="label" style="display: inline-block;" ng-hide="getStatusCount(0) == 0">{{getStatusCount(0)}}</div>
<div class="infoTableWrap">
<table class="infoTable">
<thead>
<tr>
<th style="text-align: left">Description</th>
<th style="text-align: right">Priority</th>
<th style="text-align: right">Status</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="task in tasks.items | filter:{status:0}">
<td style="text-align: left">{{task.description}}</td>
<td style="text-align: right">
<select name="ddlPriority" ng-model="task.priority" ng-options="option.id as option.name for option in priorityOptions"></select>
</td>
<td style="text-align: right">
<select name="ddlStatus" ng-model="task.status" ng-options="option.id as option.name for option in statusOptions"></select>
</td>
</tr>
</tbody>
</table>
</div>
Here is my attempt at making that a directive
<script type="text/template" id="taskListTemplate">
<h3 style="display: inline-block;">{{header}}</h3>
<div class="label" style="display: inline-block;" ng-hide="getStatusCount(2) == 0">{{getStatusCount(2)}}</div>
<div class="infoTableWrap">
<table class="infoTable">
<thead>
<tr>
<th style="text-align: left">Description</th>
<th style="text-align: right">Priority</th>
<th style="text-align: right">Status</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="task in taskList | filter:{status:2}">
<td style="text-align: left">{{task.description}}</td>
<td style="text-align: right">
<select name="ddlPriority" ng-model="task.priority" ng-options="option.id as option.name for option in priorityOptions"></select>
</td>
<td style="text-align: right">
<select name="ddlStatus" ng-model="task.status" ng-options="option.id as option.name for option in statusOptions"></select>
</td>
</tr>
</tbody>
</table>
</div>
</script>
Here is how I'm calling it:
div task-list-directive header="Completed Tasks" status-type="2" task-list="tasks.items"></div>
And here is the directive code
.directive("taskListDirective", function() {
return {
restrict: "EA",
scope: {
header: "#",
taskList: "=",
statusType: "#"
},
template: function () {
return angular.element(document.querySelector("#taskListTemplate")).html();
}
};
})
What works:
Header (the header is coming across just fine)
taskList (the task list is coming across just fine)
What doesn't work in the directive:
Setting the statusType so I don't have to hard-code the 2 in the template
The lists come up empty, but I figure that's a scope thing and I was going to turn those into directives as well.
Thanks,
Duane
Here is my JSFiddle
I included the form to add new items...and there are two hard coded lists for "Waiting" and "Working"...and I commented out the html for the directive as I couldn't get that to work in JSFiddle.

You should be able to reference statusType in your directive and use it to filter on.
ng-repeat="task in taskList | filter:{status:statusType}"
and
{{getStatusCount(statusType)}}
Since the directive uses isolated scope, you can't reference priorityOptions and statusOptions that are in the controller. You could pass them in the same way you do with taskList. But you could also define a controller for the directive and put those things there. Here I've done both. Since you also use priorityOptions in your form, I kept it in the controller and passed it in to the directive. Since statusOptions is only used in the directive, I moved it out of the controller.
.directive("taskListDirective", function () {
return {
restrict: "EAC",
scope: {
header: "#",
taskList: "=",
priorityOptions: "=",
statusType: "#"
},
template: function () {
return angular.element(document.querySelector("#taskListTemplate")).html();
},
controller: function ($scope) {
$scope.getStatusCount = function (statusType) {
return countStatusTypes(statusType);
}
//Private Methods
function countStatusTypes(statusType) {
var count = 0;
angular.forEach($scope.taskList, function (item) {
if (item.status === statusType * 1) {
count++;
}
});
return count;
}
$scope.statusOptions = [{
name: 'Waiting',
id: 0
}, {
name: 'Working',
id: 1
}, {
name: 'Completed',
id: 2
}];
}
};
});
Here's your JSFiddle updated to use the directly exclusively.

Related

ng-click, ng-model not working in angularjs datatable

I have a datatable with column filters made with AngularJS.
Here is the HTML:
<body ng-app="myApp" ng-controller="appController as Ctrl">
<table class="table table-bordered table-striped table-hover dataTable js-exportable" datatable="ng" dt-options="Ctrl.dtOptions" dt-columns="Ctrl.dtColumns">
<thead>
<tr>
<th></th>
<th>Name</th>
</tr>
</thead>
<tfoot>
<tr>
<th></th>
<th>Name</th>
</tr>
</tfoot>
<tbody>
<tr ng-repeat="user in userList">
<td>
<input type="checkbox" id="user-{{ $index }}" ng-model="Ctrl.checkboxValue[$index]" ng-click="Ctrl.checkValue(user.id)" ng-true-value="{{user.id}}" />
<label for="user-{{ $index }}"></label>
</td>
<td>
<a href="#">
{{ ::user.name }}
</a>
</td>
</tr>
</tbody>
</table>
Here's the script:
angular.module('myApp', ['ngAnimate', 'ngSanitize', 'datatables', 'datatables.columnfilter'])
.controller('appController', function($scope, $compile, DTOptionsBuilder, DTColumnBuilder){
$scope.userList = [
{
id: '1',
name: 'hello'
},
{
id: '2',
name: 'hi'
}
];
var vm = this;
vm.dtOptions = DTOptionsBuilder.newOptions()
.withPaginationType('full_numbers')
.withOption('createdRow', function (row, data, dataIndex) {
$compile(angular.element(row).contents())($scope);
})
.withColumnFilter({
aoColumns: [{
}, {
type: 'text',
bRegex: true,
bSmart: true
}]
});
vm.dtColumns = [
DTColumnBuilder.newColumn('').withTitle(''),
DTColumnBuilder.newColumn('name').withTitle('Name'),
];
vm.checkboxValue = [];
vm.checkValue = function(id){
console.log(id);
}
});
Issues:
id of the user does not get passed to checkValue function. Hence, the console.log is undefined.
Suppose if the checkbox of 1st user is checked, the value of checkboxValue array is [undefined: '1']. If checkbox of 2nd user is checked the value of checkboxValue array becomes [undefined: '2'].
Only one checkbox gets checked. Why is that?
Demo: https://plnkr.co/edit/A3PJfBuwtpUQFAIz8hW7?p=preview
You kill your code with redundancy. Look at this :
When using the angular way, you CANNOT use the dt-column directive.
Indeed, the module will render the datatable after the promise is
resolved. So for DataTables, it's like rendering a static table.
You are in fact using the "angular way" along with dt-columns. You could switch to use DTColumnDefBuilder but why define the columns when you already have a <thead> section? It would only make sense if you need to use sorting plugins etc on specific columns. And / or not is specifying the header in the markup.
Moreover, when you are rendering with angular it is not necessary to $compile anything, in fact is is very wrong, angular already does that. So
remove your dt-columns or replace it with a dt-column-defs literal
remove your $compile from the createdRow callback
Then it works. I would also remove the ng-true-value="{{user.id}}" attribute. You want an array representing the checkboxes state, why set the state to the user.id and not true or false?
vm.dtOptions = DTOptionsBuilder.newOptions()
.withPaginationType('full_numbers')
.withColumnFilter({
aoColumns: [{
}, {
type: 'text',
bRegex: true,
bSmart: true
}]
});
and
<input type="checkbox" id="user-{{ $index }}" ng-model="Ctrl.checkboxValue[user.id]" ng-click="Ctrl.checkValue(user.id)" />
Is really all you need.
forked plunkr -> https://plnkr.co/edit/Z82oHi0m9Uj37LcdUSEW?p=preview

angular-datatables - ng-repeat does not update datatable with new rows when $onChanges event called in component controller

I have made an angular-datatable component to be used in my project (using angular 1.5) and have bound the data it is populated with (the angular way) to an array of objects that gets updated in the parent scope. Upon changing the parent scope value, the $onChanges event is called and the bound data is updated, but the rows in the table do not change via ng-repeat to reflect the update -- after the table is initially drawn, no changes can be seen with subsequent data changes. Here is my code:
JS:
angular.module('datatable').
component('datatable', {
bindings: {
graphData: '<',
tableId: '=',
divId: '='
},
templateUrl: 'components/graphs/datatable.template.html',
controller: function datatableController (DTOptionsBuilder, DTColumnBuilder) {
let self = this;
self.tableKeys = Object.keys(self.graphData[0])
self.$onChanges = function () {
self.dtOptions = {
paginationType: 'full_numbers',
displayLength: 30,
dom: 'Bfrtip',
buttons: [
{extend: 'excelHtml5'},
'copy'
],
scrollY: 450,
scrollX: '100%',
scrollCollapse: true,
};
};
}
})
HTML (Component):
<datatable graph-data="$ctrl.submittedResults" table-id="'allDataTableResults'" div-id="'allDataTable'"></datatable>
HTML (Template):
<div class="col-md-10 col-md-offset-1">
<div class="well well-lg" style="background: white">
<div id="{{$ctrl.divId}}" style="width: 100%; height: 700px; display: block; margin: 0 auto">
<table datatable="ng" dt-options="$ctrl.dtOptions"
class="table table-striped table-bordered" cellspacing="0">
<thead>
<tr>
<th ng-repeat="key in $ctrl.tableKeys">{{key}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in $ctrl.graphData">
<td ng-repeat="val in row">{{val}}</td>
</tr>
</tbody>
</table>
</div>
</div>
I have tried just about everything I have seen in related questions on Stack Overflow to no avail, and I had no luck with rerendering the table completely with dtInstance. Any help would be greatly appreciated.
v0.5.5 doesn't allow '$' char in controller variable name which unfortunately is the default value in Angular Component. Override it as a workaround e.g. controllerAs: ctrl
https://github.com/l-lin/angular-datatables/blob/v0.5.5/dist/angular-datatables.js#L892
Reported this issue: https://github.com/l-lin/angular-datatables/issues/916

How to render a column with model binding using angular-datatables?

Is there a way to render a column with model binding in textbox using DTColumnBuilder?
Something like:
DTColumnBuilder.newColumn('ColumnName').withTitle('Column Name').renderWith(function (data) {
return '<input type="text" ng-model="ColumnName" />';
}),
No. You can render the table with (example) :
DTColumnBuilder.newColumn('firstName', 'First name')
.renderWith(function (data) {
return '<input type="text" ng-model="json.firstName" />'
}),
but the ng-model is never recognized because it is not angular itself that do the rendering. If you let angular do the rendering, i.e datatable="ng" and ng-repeat it works :
<table datatable="ng" dt-options="dtOptions" dt-columns="dtColumns">
<tr ng-repeat="item in json">
<td>{{ item.id }} </td>
<td><input ng-model="item.firstName"/></td>
<td>{{ item.lastName }} </td>
</tr>
</table>
demo -> http://plnkr.co/edit/f0ycjJvsACaumY13IVUZ?p=preview
notice that the JSON items is updated when you are editing in the input boxes.
Had the same problem, here is my solution:
Register callback for dtInstance
On "draw.dt" from DataTable $compile related html with angular
In other words:
HTML:
<table datatable=""
dt-options="vm.dtOptions"
dt-columns="vm.dtColumns"
dt-instance="vm.dtInstanceCallback"
class="table table-bordered table-condensed">
</table>
JS:
renderWith(function(data, type, full) {
return `<a class="ng-scope"><span ng-click='vm.remove("${data}")' class='fa fa-times-circle'></span></a>`
});
...
vm.dtInstanceCallback = (dtInstance) => {
vm.dtInstance = dtInstance;
dtInstance.DataTable.on('draw.dt', () => {
let elements = angular.element("#" + dtInstance.id + " .ng-scope");
angular.forEach(elements, (element) => {
$compile(element)($scope)
})
});
}
I minimized selection of elements, to optimize performance, maybe it's not needed. So far tested in Chrome & Safari, worked in both

Nested Angular directives not working as expected

I have been making a directive to display some HTML and handle events as it seemed sensible to keep this code reusable. This directive was placed inside a ng-repeat and worked as expected.
Now I come to add some more display logic to it: 2 modes 'grid' and 'list', and create 2 ng-repeat elements for each mode. At this point the whole thing seems to fall apart with rendering doing some unexpected stuff.
I have created a Plunkr to demonstrate: http://plnkr.co/edit/GjxOjxE7KOlvYjxCqVJj?p=preview
App.js
var app = angular.module('myApp', []);
app.directive('documentObject', [function() {
return {
restrict: 'E',
transclude: true,
scope: {
object: '=',
viewmode: '=',
},
templateUrl: 'storage-object.html',
link: function(scope, element, attrs) {
if (scope.viewMode === 'grid') {
scope.class = 'grid-view';
} else {
scope.class = 'list-view';
}
}
}
}]);
app.controller('DocumentsController', ['$scope', function($scope) {
$scope.browser = {
viewMode: 'grid',
files: [{name: 'First'}, {name: 'Second'}, {name: 'Third'}]
};
}]);
index.html
<div ng-switch="browser.viewMode" class="filebrowser">
<div ng-switch-when="grid">
<div ng-repeat="file in browser.files">
<document-object viewmode="browser.viewMode" object="file"></document-object>
</div>
</div>
<table ng-switch-when="list">
<tr ng-repeat="file in browser.files">
<document-object viewmode="browser.viewMode" object="file"></document-object>
</tr>
</table>
</div>
storage-object.html
<div ng-if="viewmode == 'grid'" class="filebrowser {{ viewmode }}">
Grid Mode: {{ viewmode }}
<span class="file-label {{ class }}">{{ object.name }}</span>
</div>
<td ng-if="viewmode == 'list'" class="filebrowser {{ viewmode }}">
List Mode: {{ viewmode }}
<span class="file-label {{ class }}">{{ object.name }}</span>
</td>
Grid mode should only display the items as a grid, and list mode as a table. What is happening is grid mode is displaying everything and list mode is just rendering one element.
What is going on here?
This happens because you are not allowed to have anything else as a direct child of a <tr> than a <td> or <th> element.
In your case you are having a <document-object> element, which causes the browser to move it around (as it is not allowed to be where it is), which in turn confuses ng-switch.
Wrapping <document-object> in <td></td> and chenging it's template (e.g. replacing <td> with <div>), solves the problem.
index.html:
...
<table ng-switch-when="list">
<tr ng-repeat="file in browser.files">
<td>
<document-object viewmode="browser.viewMode" object="file"></document-object>
</td>
</tr>
</table>
storage-object.html:
...
<div ng-if="viewmode == 'list'" class="filebrowser {{ viewmode }}">
List Mode: {{ viewmode }}
<span class="file-label {{ class }}">{{ object.name }}</span>
</div>
See, also, this updated plunkr.
Please see here http://plnkr.co/edit/aMOsKAATd4aBl0swPik1?p=preview
you missed <td> tag
<table ng-switch-when="list">
<tr ng-repeat="file in browser.files">
<td> <!--here -->
<document-object viewmode="browser.viewMode" object="file"></document-object>
</td>
</tr>
</table>

AngularJS dynamic table with multiple headers based on hierarchical collections

I'm trying to create a table with two rows for header, based on a hierarchical collection. I've found that ng-repeat can't do that, and I'm trying to make the job with a directive and Angular.forEach.
Here the jsfiddle : http://jsfiddle.net/echterpat/RxR2M/9/
But my problem is that when I made the first display of table, collections were empty (they were filled by REST call afterward), so link method was not able to build all the table. And then when REST updated collections, link method was not called anymore...
Any idea to call directive after REST answer, and to fill my table with collected data ?
Directive with angular.forEach :
myApp.directive('myTable', ['$compile', function (compile) {
var linker = function (scope, element, attrs) {
var html = '<table BORDER="1"><thead>';
html += '<tr><th><button type="submit" class="btn btn-info">Back</button></th>';
angular.forEach(scope.myFirstCol, function (item, index) {
html += '<th colspan="{{item.mySecondCol.length}}" id="item_{{item.id}}"> {{item.name}}</th>';
});
html += '</tr><tr><th><input type="checkbox" ng-model="selectAll"/></th>';
angular.forEach(scope.myFirstCol, function (item2, index) {
angular.forEach(item2.mySecondCol, function (item3, index) {
html += '<th id="headerStep_{{item3.id}}">{{item3.name}}</th>';
});
});
html += '</tr></thead>';
html += '</table>';
element.replaceWith(html);
compile(element.contents())(scope);
};
return {
restrict: 'E',
rep1ace: true,
link: linker,
scope: {
myFirstCol: '=myFirstCol'
}
};
}]);
If you dont want to use a directive, you can go for nested tables:
<table BORDER="1">
<thead>
<tr>
<th>
<button type="submit" class="btn btn-info">Back</button>
</th>
<th ng-repeat="item in myFirstCol">{{item.name}}</th>
</tr>
<tr>
<th>
<input type="checkbox" ng-model="selectAll" />
</th>
<th ng-repeat="item in myFirstCol">
<table BORDER="1">
<tr>
<th ng-repeat="item2 in item.mySecondCol">
{{item2.name}}
</th>
</tr>
</table>
</th>
</tr>
</thead>
</table>

Resources