I have a table set up using the smart-table plug in for AngularJS. Everything appears to work nicely. Rather than having the user click on the table header to trigger a sort, I'd like to programmatically trigger sorting from my Angular controller. I do not see a way of doing this in the documentation here:
http://lorenzofox3.github.io/smart-table-website/
Am I overlooking something?
Found this on JSFiddle, might help you: http://jsfiddle.net/vojtajina/js64b/14/
<script type="text/javascript" ng:autobind
src="http://code.angularjs.org/0.10.5/angular-0.10.5.js"></script>
<table ng:controller="SortableTableCtrl">
<thead>
<tr>
<th ng:repeat="(i,th) in head" ng:class="selectedCls(i)" ng:click="changeSorting(i)">{{th}}</th>
</tr>
</thead>
<tbody>
<tr ng:repeat="row in body.$orderBy(sort.column, sort.descending)">
<td>{{row.a}}</td>
<td>{{row.b}}</td>
<td>{{row.c}}</td>
</tr>
</tbody>
</table>
A quick hack i found on how to do this is by setting the table header st-sort property and then triggering a click() on that element
<tr>
<th id="myelement" st-sort="date" st-sort-default="reverse">Date</th>
...
</tr>
Then later:
setTimeout(function() {
document.getElementById('myelement').click()
},
0);
Here is the 'angular' way to do this. Write a directive. This directive will have access to the smart table controller. It will be able to call the controller's sort by function. We will name the new directive stSortBy.
The below HTML includes the standard smart table syntatic sugar. The only new attribute directive here is st-sort-by. That's where the magic will happen. It's bound to a scoped variable sortByColumn. This is a string value of the column to sort
<table st-sort-by="{{sortByColumn"}}" st-table="displayedCollection" st-safe-src="rowCollection">
<thead>
<tr>
<th st-sort="column1">Person</th>
<th st-sort="column2">Person</th>
</tr>
</thead>
</table>
<button ng-click="toggleSort()">Toggle sort columns!</button>
Here is the stSortBy directive that hooks into the st table controller
app.directive('stSortBy', function() {
return {
require: 'stTable',
link: function (scope, element, attr, ctrl) {
attr.$observe('stSortBy', function (newValue) {
if(newValue) {
// ctrl is the smart table controller
// the second parameter is for the sort order
ctrl.sortBy(newValue, true);
}
});
}
};
});
Here is the controller. The controller sets the sort by in it's scope
app.controller("MyTableWrapperCtrl", ["$scope", function($scope) {
$scope.sortByColumn = 'column2';
$scope.toggleSort = function() {
$scope.sortByColumn = $scope.sortByColumn === 'column2' ? 'column1' : 'column2';
// The time out is here to guarantee the attribute selector in the directive
// fires. This is useful is you do a programmatic sort and then the user sorts
// and you want to programmatically sort back to the same column. This forces a sort, even if you are sorting the same column twice.
$timeout(function(){
$scope.sortByColumn = undefined;
}, 0);
};
}]);
Related
current I am working with a combination of datatables + angularjs. I'm following documentation here. https://l-lin.github.io/angular-datatables/archives/#!/gettingStarted
My table is rendering and getting populated with angularjs code. The issue I am coming across is I only want the photo text to appear only when it exist. When I inspect the element, ng-hide is always set. It'll appear fine if I remove ng-if.
Code
<table datatable="ng" class="contact-grid-table row-border" dt-options="vm.dtOptions2" dt-column-defs="vm.dtColumnDefs" dt-instance="dtInstanceCallback" id="tableId">
<thead>
<tr>
<th>Firstname</th>
<th>Lastname</th>
<th>Photo</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="contact in vm.contacts">
<td>{{contact.firstName}}</td>
<td>{{contact.lastName}}</td>
<td><span ng-if="contact.photo">{{contact.photo}}</span></td>
</tr>
</tbody>
</table>
I also tried the suggestion here ng-show not working in datatables columns but my table will no longer load. Page just freezes.
May I ask if there's any suggestions on how to get ng-if/show to work with datatables.
vm.dtOptions2 = DTOptionsBuilder.newOptions()
.withOption('createdRow', function (row, data, dataIndex) {
// Recompiling so we can bind Angular directive to the DT
$compile(angular.element(row).contents())($scope);
})
.withOption('headerCallback', function (header) {
if (!vm.headerCompiled) {
// Use this headerCompiled field to only compile header once
vm.headerCompiled = true;
$compile(angular.element(header).contents())($scope);
}
})
.withPaginationType('full_numbers').withOption('responsive', true)
.withColReorder()
.withColReorderOrder([0,1, 2,3])
.withScroller()
.withOption('bFilter', false) // for search box
.withOption('bInfo', false) // for showing counts at the bottom
.withOption('deferRender', true)
.withOption('scrollY', 200)
// Set order
// Fix last right column
.withDisplayLength(2)
.withFixedHeader({
bottom: false
});
My directive for ng-focus.
Edit: I was told there is existing core ng-focus. as i am very new to angular I don't see ng-focus as an option. As in fig below. Also it does not work
var ngControlMod = angular.module('ngControlModule', []);
ngControlMod.directive('ngFocus', function ($timeout) {
return {
link: function (scope, element, attrs) {
scope.$watch(attrs.ngFocus, function (val) {
if (angular.isDefined(val) && val) {
$timeout(function () { element[0].focus(); });
}
}, true);
element.bind('blur', function () {
if (angular.isDefined(attrs.ngFocusLost)) {
scope.$apply(attrs.ngFocusLost);
}
});
}
};
});
Here is how I am using in in my view
<table id="tblMyTable" class="table table-bordered table-striped table-hover table-condensed">
<thead>
<tr>
<th>My Dropdown</th>
<th>My Date</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="ca in myList">
<td>
<select name="select_{{ca.ID}}" ng-model="ca.List"
ng-options="corr as corr.caText for corr in caList track by corr.caValue"
ng-change="GetData(ca, ca.List)">
<option value="">--Select--</option>
</select>
</td>
<td>
<input name="Date_{{ca.ID}}" ng-focus="isFocus_{{ca.ID}}"
date-picker="" ng-model="ca.caDate"
placeholder="MM/DD/YYYY" />
</td>
</tr>
</tbody>
</table>
The idea is, if a user selects an option in dropdown the focus should automatically move to date in that row.
To achieve this I have added an ID as an identifier in attribute "name" for the html control in table row and in my controller
$scope.GetData= function (ca, Selected) {
var propName = "isFocus_" + ca.ID;
$scope[propName] = true;
}
After all the coding the result is not consistent. it works sometimes and other times it does . And I can't figure out the reason. Also I am getting this error
angular.js:14700 Error: [$parse:syntax] Syntax Error: Token '{' is an
unexpected token at column 9 of the expression [isFocus_{{ca.QuesID}}]
starting at [{{ca.QuesID}}].
http://errors.angularjs.org/1.6.6/$parse/syntax?p0=%7B&p1=is%20an%20unexpected%20token&p2=9&p3=isFocus_%7B%7Bca.QuesID%7D%7D&p4=%7B%7Bca.QuesID%7D%7D
at angular.js:116
at AST.throwError (angular.js:15258)
at AST.ast (angular.js:15008)
at Parser.parse (angular.js:16350)
at $parse (angular.js:16496)
at Object.compile (angular.js:27337)
at applyDirectivesToNode (angular.js:9737)
at compileNodes (angular.js:9097)
at compileNodes (angular.js:9109)
at compileNodes (angular.js:9109) "<input name="Date_{{ca.ID}}" ng-focus="isFocus_{{ca.ID}}" date-picker="" ng-model="ca.caDate" placeholder="MM/DD/YYYY" />"
Be aware that adding a custom ng-focus directive will not replace the existing core ng-focus. The $compile service will instantiate both the new custom directive and the existing core ng-focus directive.
Avoid using the ng- prefix for custom directives. The ng- prefix is reserved for core directives. For more information, see AngularJS Wiki - Best Practices.
<input name="Date_{{ca.ID}}" ̶n̶g̶-̶f̶o̶c̶u̶s̶=̶"̶i̶s̶F̶o̶c̶u̶s̶_̶{̶{̶c̶a̶.̶I̶D̶}̶}̶"̶
ng-focus="'isFocus_'+ca.ID"
date-picker="" ng-model="ca.caDate"
placeholder="MM/DD/YYYY" />
Why mixing interpolation and expressions is bad practice:
It increases the complexity of the markup
There is no guarantee that it works for every directive, because interpolation itself is a directive.
If another directive accesses attribute data before interpolation has run, it will get the raw interpolation markup and not data.
It impacts performance, as interpolation adds another watcher to the scope.
Since this is not recommended usage, we do not test for this, and changes to AngularJS core may break your code.
— AngularJS Developer Guide - Interpolation
If I type onto page
<liberty-directive></liberty-directive>
That works fine.
However, I have database table with list of directive names.
So
<table>
<tr ng-repeat="lib in vm.liberty">
<td>{{lib.Tag}}</td>
</tr>
</table>
So this is the object with the directive tag
{{lib.Tag}} = <td class="ng-binding"><liberty-directive></liberty-directive></td>
Viewing source it "looks fine" , but copy and paste to this it is changing, how to prevent that?
To get it work, you have to compile your html in each iteration of ng-repeat (use $compile). For that, you can use a simple custom directive: (PLUNKER DEMO)
.directive('compileHtml', ['$compile', function($compile) {
return function(scope, element, attrs) {
element.html(attrs.compileHtml);
$compile(element.contents())(scope);
};
}]);
Then in your HTML use it like:
<tr ng-repeat="d in vm.data">
<td compile-html="{{d.htmlContent}}"></td>
</tr>
Controller:
function myCtrl() {
var vm = this;
vm.data = [
{ "htmlContent": "<my-custom-directive></my-custom-directive>" },
{ "htmlContent": "<div>Custom: <span my-attr-directive></span></div>" },
//... more items
];
}
Check this post if you want more examples.
I am working along DB guys, they are sending me the data thru XML, and depending the kind of element they specify is what I need to display in the view.
The code you will see is a dynamic table
<table>
<thead>
<tr>
<th ng-repeat="column in cols">
<span>{{column}}</span>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in rows">
<td ng-repeat="column in cols"
ng-init="isXX = column.indexOf('XX') === 0">
<span ng-if="!isXX">{{row[column]}}</span>
<button ng-if="isXX" class="btn btn-xs btn-blue"
ng-click="fillOpen()">
{{column.substring(3).replace('_', ' ')}}
</button>
</td>
</tr>
</tbody>
</table>
and here is what I have in the controller
ReportsFactory.pendingBets(reportParam).then(function(data) {
if (data.length) {
gridInfo = _.forEach(data, function(item) {return item;});
$scope.rows = gridInfo;
$scope.cols = Object.keys($scope.rows[0]);
}
}
as you can see here I have this ng-init
ng-init="isXX = column.indexOf('XX') === 0" where I am telling the app, if the property I am receiving comes with XX at the index, then display a button <button ng-if="isXX" ng-click="fillOpen()">...</button> but so far, I have some more props coming with XX at the beginning, so I need to do it more dynamic.
This is how my view looks so far
what I need to know, is how to read that XML, this is the XML printed in the Nodejs terminal
[{ BET: 57635034,
CUSTOMER: 181645,
SPORT: 'NFL',
'XX_FILL OPEN': '<element><element_type>WAGER_ACTION_BUTTON</element_type><element_call>fillOpen(57635034)</element_call><element_content/></element>',
XX_VIEW: '<element><element_type>BASIC_DROPDOWN</element_type><element_call>callThisFunction()</element_call><element_content><li>1</li><li>2</li><li>3</li><li>4</li></element_content></element>',
XX_CANCEL: '<element><element_type>BASIC_CHECKBOX</element_type><element_call/><element_content>1</element_content></element>'
}]
so, the first says
'XX_FILL OPEN': '<element><element_type>WAGER_ACTION_BUTTON</element_type><element_call>fillOpen(57635034)</element_call><element_content/></element>'
WAGER_ACTION_BUTTON should be a button
the second one says
BASIC_DROPDOWN that should be a dropdown and so on, so, how should I do in order to display the proper HTML element depending on what the XML says ?
Any suggestions ?
if I understood you correctly you want to dynamically render the xml or html content to your view... I assume that element and element type are directive you have or something.
use
ngBindHtml
e.g:
<div class="col-xs-offset-1 m-r-offset-8 p-t-offset-2 font-l-16">
<span mathjax-bind ng-bind-html="question.question.body"></span>
</div>
or you might need to use the trustAsHtml function
<div class="col-xs-offset-1 m-r-offset-8 p-t-offset-2 font-l-16">
<span mathjax-bind ng-bind-html="trustAsHtml(question.question.body)"></span>
</div>
$scope.trustAsHtml = function (val) {
return $sce.trustAsHtml(val);
};
this will take your string xml (html) code and render it...
you could always build a personalize directive and use $compile as well like:
app.directive('ngHtmlCompile',function ($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
// watch the 'compile' expression for changes
return scope.$eval(attrs.ngHtmlCompile);
},
function(value) {
// when the 'compile' expression changes
// assign it into the current DOM
element.html(value);
// compile the new DOM and link it to the current
// scope.
// NOTE: we only compile .childNodes so that
// we don't get into infinite loop compiling ourselves
$compile(element.contents())(scope);
}
);
};
});
and in the code just call the ng-html-compile... no need for $sce
Simple html:
<table class="table table-condensed">
<tr data-ng-repeat="customer in customers" data-ng-class="customerSelectedClass(customer)">
<td>
{{customer.Name}}
</td>
</tr>
</table>
In my controller - two functions to select customer and return proper class to highlight a table row:
$scope.customerSelectedClass = function (customer) {
if (customer == $scope.selectedCustomer) {
console.log('returing info for ' + customer.Name);
return "info";
}
return "";
};
$scope.selectCustomer = function (customer) {
console.log('selecting ' + customer.Name);
$scope.selectedCustomer = customer;
}
I noticed that when I click on a customer link, customerSelectedClass function executes twice. selectCustomer function on ng-click directive executes once, as it should. Angular is only included once on the page. I wonder if this is a bug in Angular or something that I am doing wrong?
Behind the scenes, angular is setting up a $watch on the function that is resolving the class name. Because angular uses dirty checking to see if there has been a change, this method will be called twice during the $digest cycle. This is ok.
I would suggest that you don't add this code the the controller though, because if you are managing many css classes, you could be adding a lot of unnecessary code. Try something like this instead:
<table class="table table-condensed">
<tr data-ng-repeat="customer in customers" data-ng-class="{'info': customer == selectedCustomer}">
<td>
{{customer.Name}}
</td>
</tr>
</table>
Then, there is no need for a controller function customerSelectedClass. This will only add the info class if the right-hand side of the : resolves to true. And there is no problem resolving the correct customer in the ng-repeat.
Hope this helps.