DataTables inside bootstrap accordion in angularjs - angularjs

I am working on a module where I have to redesign some products. Following is the screenshot of how the products used to get displayed previously.
Now the products will be displayed inside accordion of their specific name. I am bound to use ui.bootstrap Version: 0.11.0 - 2014-05-01. Following is a sketch of how the products will be displayed now. In each accordion there will be a datatable of that particular product in which the columns will dynamically generate and we would be able to check the particular products we want.
Following is my html code:
<accordion>
<accordion-group ng-repeat="AllProduct in AllProducts">
<accordion-heading>
{{AllProduct.TypeName}}
</accordion-heading>
</accordion-group>
<table id="dtVoice" class="table manage-user-table offer-mgt-table" dt-options="dtOptions" dt-columns="dtColumns"></table>
</accordion>
The way i have dynamically created datatables are as follows:
dtColumns.push(DTColumnBuilder.newColumn(null).withTitle('').notSortable()
.renderWith(function(data, type, full, meta) {
return '<input type="checkbox" ng-model="showCase.selected[' + data.id + ']"/>';
}));
for (var key in $scope.VoiceProducts[0]) {
if (key == "ProductName" || key == "LongDistanceMinutes" || key == "IsCallWaiting") {
dtColumns.push(
DTColumnBuilder.newColumn(key).withTitle(key)
)
}
$scope.dtColumns = dtColumns;
$scope.dtOptions = DTOptionsBuilder.newOptions()
.withOption('data', $scope.VoiceProducts)
.withOption('dataSrc', '')
angular.element('#dtVoice').attr('datatable', '')
}
$compile(angular.element('#dtVoice'))($scope);
Following is my json
[
{
"ProductList": [
{
"ProductName": "Voice",
"IsActive": false,
"IsDeleted": false,
"LongDistanceMinutes": "",
"IsCallWaiting": "",
"CallWaitingId": "",
"IsThreeWayCalling": "",
"IsCallerId": "",
"IsCallForwarding": "",
"IsCallRejection": "",
"ID": 552,
"OfferId": 0
}
],
"ID": 2,
"IsActive": false,
"IsDeleted": false,
"TypeName": "Voice"
}
]
How to put this datatable inside accordion? Because by doing whatever I'm, i'm unable to achieve it.

I had the same problem on the pass.
Please use ng-if as a flag to recreate the table when the accordion item is active. The accordion and tab components avoid the table to be shown.
The code below can help.Notice the ng-click and ng-if
<accordion>
<accordion-group ng-repeat="AllProduct in AllProducts">
<accordion-heading ng-click="setGroup('AllProduct.TypeName')">
{{AllProduct.TypeName}}
</accordion-heading>
<table id="dtVoice" ng-if="group=='AllProduct.TypeName'" class="table manage-user-table offer-mgt-table" dt-options="dtOptions" dt-columns="dtColumns"></table>
</accordion-group>
</accordion>

UPDATED: As per new information (using angular-datatable)
The solution now boils down to computing the columns and options per accordion-group.
Working Plunker with 2 accordion groups
As you can see in the HTML below the options and columns are computed per accordion.
<table datatable class="table manage-user-table offer-mgt-table" dt-options="getDtOptions(AllProduct)" dt-columns="getDtColumns(AllProduct)"></table>
Angular code showing getDtColumns and getDtOptions. I have kept the data very simple for demonstration purposes and copied the current dtColumns code however you can customize it so that you can even have more than 1 type of table :
var app = angular.module('myApp', ['ui.bootstrap', 'datatables']);
app.controller('myCtrl', function($scope, DTColumnBuilder, DTOptionsBuilder, DTColumnDefBuilder, $timeout, AllProducts) {
$scope.AllProducts = AllProducts
$scope.getDtColumns = function(allProduct) {
var items = allProduct.ProductList;
if (allProduct.dtColumns) allProduct.dtColumns.length = 0;
allProduct.dtColumns = allProduct.dtColumns || [];
var dtColumns = allProduct.dtColumns;
dtColumns.push(DTColumnBuilder.newColumn('').withTitle('').notSortable()
.renderWith(function(data, type, full, meta) {
return '<input type="checkbox" ng-model="showCase.selected[' + full.id + ']"/>';
}));
for (var key in items[0]) {
if (key == "ProductName" || key == "LongDistanceMinutes" || key == "IsCallWaiting") {
dtColumns.push(
DTColumnBuilder.newColumn(key).withTitle(key).notSortable()
)
}
}
return dtColumns;
};
$scope.getDtOptions = function(allProduct) {
if (allProduct.options) return allProduct.options;
var items = allProduct.ProductList;
allProduct.options = allProduct.options || DTOptionsBuilder.newOptions().withOption('aaData', items);
return allProduct.options;
};
});
OLDER ANSWER without angular-datatable
First of all I do not recommend jQuery DataTable or any other jQuery component in AngularJS applications. I personally try not to bundle jQuery or perform DOM manipulation using jQuery.
However to get you going with what you have I suggest the following:-
Remove these two lines because simply adding those attributes datatable dynamically is not going to trigger the DataTable binding:-
angular.element('#dtVoice').attr('datatable', '')
}
$compile(angular.element('#dtVoice'))($scope);
and try using something like this:-
$('#dtVoice').DataTable( {columnDefs: $scope.dtColumns });
Further more just to clean up a bit I create a new directive (just typing out loud):
app.directive('myDatatable`, function(){
return {
restrict: 'A',
scope: {
'dtColumns': '='
}
link: function($scope, elem, attr) {
$('#dtVoice').DataTable( {columnDefs: $scope.dtColumns});
}
};
});
and your table something like below:
<table id="dtVoice"
class="table manage-user-table offer-mgt-table"
dt-options="dtOptions"
dt-columns="dtColumns" my-datatable></table>

Related

How do i select the server-side filtered item in ui-grid using angularjs?

I have a ui-grid table which has been implemented with Server-side filtering, now i have a scenario that i am passing the filtering term in my api from another page and coming to the ui-grid page with the filtered data for the grid but the selection of the filter for that column is not applied.
Code:
$scope.gridOptions = {
enableSelectAll: false,
exporterCsvFilename: fileNm,
exporterMenuPdf: false,
enableFiltering: true,
showGridFooter: true,
paginationPageSizes: [25, 50, 75],
paginationPageSize: 25,
useExternalPagination: true,
enableGridMenu: true,
..
..
.
Sample columnDef
var sparableColumn = {
name: 'sparable', displayName: 'Sparable', headerCellClass: $scope.highlightFilteredHeader,
filter: {
type: uiGridConstants.filter.SELECT,
selectOptions: $scope.sparableFilter
},
cellTemplate:'<div ng-if="row.entity.sparable" class="tcenter">{{row.entity.sparable}}</div><div ng-if="!row.entity.sparable"></div>',
cellClass: function (grid, row, col) {
if (grid.getCellValue(row, col) === 'NO') {
return 'red tcenter';
}
if(grid.getCellValue(row, col) === 'YES') {
return 'green tcenter';
}
return 'tcenter';
}
};
Server side filter inside onRegisterApi:
onRegisterApi: function (gridApi) {
$scope.gridApi = gridApi;
$scope.gridApi.core.on.filterChanged( $scope, function() {
var grid = this.grid;
$scope.serversideFilters = [];
angular.forEach(grid.columns, function(value, key) {
if(value.filters[0].term) {
var dummy = {};
console.log('FILTER TERM FOR ' + value.colDef.name + ' = ' + value.filters[0].term);
dummy['k'] = value.colDef.name;
dummy['v'] = value.filters[0].term;
$scope.serversideFilters.push(dummy);
}
});
getPage();
});
I know we need to populate or select it via grid.columns.filters but how and where to put the code to activate the filtered column selection is the question.
Thanks in advance!
I've put together a plnkr to outline what I think you are looking for. It is based off of the ui-grid tutorial on Saving State, but shrinks the amount of data you are storing down to the columns array (where each column object contains the filters). To test, add some filter content to the filter headers, and save state. 'X' the filters out, hit restore, and the filters are back.
Down below, I've outlined some ideas on "what to do" with this array.
JS
This code comes from the above mentioned tutorial, with slight modifications to avoid storing the extra data.
Note that $scope.gridOptions.saveFilter = true. The save* options have been set to false to avoid any extra data handling.
// ensure
var app = angular.module('app', ['ui.grid', 'ui.grid.saveState']);
// left out extra code for brevity
var columns = [];
$scope.saveState = function() {
var saved = $scope.gridApi.saveState.save();
columns = saved.columns;
console.log('columns', columns);
};
$scope.restoreState = function() {
$scope.gridApi.saveState.restore($scope, {
columns: columns
});
};
HTML
Directives are "listed" to ensure they stand out.
<div class="grid" ui-grid="gridOptions" ui-grid-save-state>
<button id="save" type="button" class="btn btn-success"
ng-click="saveState()">Save</button>
<button id="restore" type="button" class="btn btn-success"
ng-click="restoreState()">Restore</button>
Stashing the Array
Depending on how you are handling navigation, you could watch for $routeChangeStart, and call saveState():
$scope.$on('$routeChangeStart', function(next, current) {
// Serialize, post/put, etc here
});
When loading the the second page, you can retrieve the data doing something like this:
// app.config
$routeProvider.
when('/page2', {
templateUrl: 'page_2.tpl.htm',
controller: 'Page2Controller',
resolve: {
gridFilters: function($http){
// We must return something with a promise. $http already does that, so does $resource.
// See: https://docs.angularjs.org/api/ng/service/$q
return $http({
method: 'GET',
url: '/api/resource/with/grid/settings'
})
}
}
})
Local Storage
A second approach would be keeping the data locally using a service. The answer provided in passing-data-between-controllers-in-angular-js sums this pattern up perfectly, so it need not be replicated here.

Ng-repeat - "Are you sure to delete ?" from a modal

I'm retrieving a list of objects (item) from a Django API.
my_app.factory('list_of_items', function($resource) {
return $resource(
'/api/petdata/') });
Then I display everything in a html page within a ng-repeat:
<div ng-controller="ModalDemoCtrl">
<div ng-repeat="item in items | filter:{display:'1'} | orderBy: 'item_name'">
<div class="box box-widget widget-user">
{{ item.pet_name }}{% endverbatim %}
<button type="button" class="btn btn-box-tool" ng-click="askDelete(item)" href="#"><i class="fa fa-times"></i></button>
</div>
<div>
Everything's fine so far.
Then I want the user to be able to delete one of the item by clicking on the button from the html page.
What means deleting here :
1. Update the API database by changing the property "display:1" to "display:0".
2. Remove the item from the ng-repeat.
I want to make a "Are you sure" modal to confirm the delete process.
This is the askDelete function.
angular.module('djangular-demo').controller('Ctrl_List_Of_Pets', function($scope, $http, $window,$filter,list_of_pets,pet_by_id,$uibModal) {
$scope.items = list_of_items.query()
$scope.askDelete = function (idx,item,size,parentSelector) {
// console.log("PET",$scope.pet_to_be_undisplayed);
var parentElem = parentSelector ?
angular.element($document[0].querySelector('.modal-demo ' + parentSelector)) : undefined;
var modalInstance = $uibModal.open({
animation: true,
ariaLabelledBy: 'LOL',
ariaDescribedBy: 'modal-body',
templateUrl: "myModalContent.html",
controller: function($scope) {
$scope.ok = function() {
modalInstance.close();
};
$scope.cancel = function() {
modalInstance.dismiss('cancel');
};
},
size: size,
appendTo: parentElem,
resolve: {
}
});
modalInstance.result.then(function() {
reallyDelete(item);
});
};
var reallyDelete = function(item) {
$scope.entry = items_by_id.get({ id: item.id }, function() {
// $scope.entry is fetched from server and is an instance of Entry
$scope.entry.display = 0;
$scope.entry.$update({id: $scope.entry.id},function() {
//updated in the backend
});
});
$scope.items = window._.remove($scope.items, function(elem) {
return elem != item;
});
};
});
What works :
Updating the DB works with a PUT request (code hasn't been provided).
What doesn't work :
Removing the item from the ng-repeat never works. Or it throws me an error like here because it doesn't know window._.remove or it doesn't know $scope.items. It depends from what I try. Or the modal close and there is no update of the ng-repeat list, no refresh and every items remain whereas the PUT request to update worked.
I read every article on scope inheritance and I think I didn't make any mistake here but I'm might be wrong. I've been struggling for too long so I post here !
Would you suggest anything to make it work ?
Thank you for your rime.
First:
$scope.askDelete = function (idx,item,size,parentSelector) receives the item index, the item, size, and parent selector... and you are calling ng-click="askDelete(item)"
I assume you are attempting to pass the item, but in askDelete you are receiving as first parameter the index (maybe you should do ng-click="askDelete($index)"?)
Second:
In reallyDelete why are you removing the items array like this:
$scope.items = window._.remove($scope.items, function(elem) {
return elem != item;
});
?
IMHO, it would be a much cleaner code if we just do:
$scope.items.splice(idx, 1) //<- idx would be the idx of the entry in the items
You may want to take a look at Splice

Kendo Grid with angularjs Filter Row Custom Directive

I've been trying to no end to try and create a custom filter template for Kendo grid, when the grid is in filterMode: "row". I can get the HTML to appear, but the angular directive is never compiled.
This is the filter configuration for the column:
filterable: {
cell: {
template: function getTemplate(args) {
var container = $("<span>").appendTo(args.element.parent());
var buttonTemplate = kendoGridService.compileTemplate({
template: "modules/common/templates/kendo/columns/days-of-week-edit.tpl.html",
data: options
});
container.append(buttonTemplate);
},
showOperators: false
}
}
Here is the compile template function mentioned above:
compileTemplate: function compileTemplate(options) {
var template = $templateCache.get(options.template);
var templateFn = kendo.template(template);
var result = templateFn(options.data);
return result;
}
Here is what I see in the HTML:
<span class="k-operator-hidden">
<input data-bind="value: value">
<span>
<days-of-week-edit data-item="dataItem"></days-of-week-edit>
</span>
</span>
And finally, this is what I see in the UI. Not that the default input field is there, but nothing else is rendered.
Seems like you also have to compile your template with Angular. Try using $compile service:
var template = $templateCache.get(options.template);
var templateFn = kendo.template(template);
var result = templateFn(options.data);
$compile(result)($scope);
return result;
Just make sure you have a valid $scope object with the dataItem property inside

AngularJs. Ng repeat jquery sortable unexpected behavior

I faced with unexpected behavior while using jquery sortable in conjunction with angular ng repeat directive. I'm trying to sort items inside different columns in a table.
When I'm dragging 1st element of 2nd column to 1st position (or to 2nd position) in 1st column - all is OK, but if I'll try to put 1st element of 2nd column to 3rd or 4th position it just goes away with subsequent elements.
If you would look at json encoded array at the bottom of document you will see that components are correctly sorted.
I would be grateful for any kind of assistance.
Here is live example: http://plnkr.co/edit/j6xMb4vhcUDVk8POjzpW?p=preview
HTML
<div data-ng-app="myApp" data-ng-controller="defaultCtrl">
<table border="1" data-dnd-list="doc">
<tr>
<td data-ng-repeat="column in doc.columns" data-index="{{$index}}" class="column">
<table class="item" style="border:1px solid red; cursor: pointer" width="100%" data-ng-repeat="component in column.components" data-index="{{$index}}">
<tr>
<td style="padding:5px">{{ component.content }}</td>
</tr>
</table>
</td>
</tr>
</table>
{{ doc }}
</div>
JS
var app = angular.module("myApp", []);
app.controller("defaultCtrl", ["$scope", function($scope) {
$scope.doc = {
columns: [
{
components: [
{
content: "1 column 1 component"
},
{
content: "1 column 2 component"
},
{
content: "1 column 3 component"
}
]
},
{
components: [
{
content: "2 column 1 component"
}
]
}
]
}
}]);
app.directive('dndList', function() {
return function(scope, element, attrs) {
var toUpdate;
var oldComponentIndex = -1;
var oldColumnIndex = -1;
scope.$watch(attrs.dndList, function(value) {
toUpdate = value;
console.log("New changes: ", toUpdate);
},true);
$(element[0]).sortable({
items:'.item',
start:function (event, ui) {
var i = $(ui.item);
// on start we define where the item is dragged from
oldComponentIndex = i.attr("data-index");
oldColumnIndex = i.closest(".column").attr("data-index");
},
stop:function (event, ui) {
var i = $(ui.item);
var newComponentIndex = i.index();
var newColumnIndex = i.closest(".column").attr("data-index");
var toMove = toUpdate.columns[oldColumnIndex].components[oldComponentIndex];
toUpdate.columns[oldColumnIndex].components.splice(oldComponentIndex, 1);
toUpdate.columns[newColumnIndex].components.splice(newComponentIndex, 0, toMove);
scope.$apply(scope.doc);
}
})
}
});
OK. I found a solution. The fact is that when you put new item in sortable list, the DOM became unsynchronized with AngularJS. To prevent this you need to remove HTML of new item.
i.remove();
Here is updated plunker. http://plnkr.co/edit/j6xMb4vhcUDVk8POjzpW?p=preview

Disabling nesting filter in AngularJS

There is the following code:
tr ng-repeat="order in orders | filter: { restaurant: { id: currentRestaurant.id } } | orderBy:'-id'"
This code works good and filter orders by restaurant.id correctly. But I need to disable this filter in some case in order to show all orders. My controller code:
$scope.setCurrentRestaurant = (restaurant) ->
$scope.currentRestaurant = restaurant
I don't know how I can disable filtering programmatically or using filter directive. Thanks in advance.
If the filter parameter is undefined then it does not filter. If the filter parameter is the filtering expression, then it filters based on that.
So, you could introduce a filter expression like so:
"filter: (enableFilter || undefined) && {restaurant:{id:currentRestaurant.id}}"
plunker
I think that Angular doesn't contain native features for that.
But you can create custom solution for this case.
For example, you can broadcast event, when you want to disable filter. This event can be broadcasted from anywhere (service, directive, controller). For simplicity, here I've broadcasted it from controller.
Also I've created filterByRestaurant in the controller.
View:
<div ng-controller="MyCtrl">
<div ng-repeat="order in orders | filter:filterByRestaurant">
{{order.name}}
</div>
<button ng-if="enableFilter" ng-click="disableOrEnableFilter()">Disable filter</button>
<button ng-if="!enableFilter" ng-click="disableOrEnableFilter()">Enable filter</button>
</div>
Controller:
function MyCtrl($scope, $rootScope) {
$scope.enableFilter = true;
$scope.currentRestaurant = {id: 2, name: "Rest2"};
$scope.orders = [
{id:1, restId: 1, name: "order from Rest1"},
{id:2, restId: 2, name: "order from Rest2"},
{id:3, restId: 3, name: "order from Rest3"}
];
$scope.filterByRestaurant = function(order) {
return $scope.currentRestaurant.id == order.restId || !$scope.enableFilter;
};
$scope.$on('filter:disable', function() {
$scope.enableFilter = !$scope.enableFilter;
});
$scope.disableOrEnableFilter = function(){
$rootScope.$broadcast("filter:disable");
};
}
This is working JSFiddle with example.
Hope it will help.

Resources