Smart-Table - AngularJs - Wrap within a directive - angularjs

I'm very new to AngularJs and trying to "wrap" the Smart-Table plugin inside a directive.
I can get the rows but the pagination is not showing
Currently this is what I did.
app.directive('grid', [ function () {
return {
restrict: 'E',
replace: true,
template: function (element, attrs) {
return '<table class="table table-striped">'
+ '<thead>'
+ ' <tr>'
+ '<th st-ratio="20" st-sort="Team">Team</th>'
+ '<th st-ratio="20" st-sort="TeamFreq">Team Freq</th>'
+ '<th st-ratio="10" st-sort="TeamSac">Team Sac</th>'
+ '<th st-ratio="30" st-sort="Priority">Priority</th>'
+ '</tr>'
+ '</thead>'
+ '<tbody>'
+ '<tr ng-repeat="row in dataset">'
+ ' <td>{{row.firstName}}</td>'
+ '<td>{{row.lastName | uppercase}}</td>'
+ '<td>{{row.age}}</td>'
+ '<td>{{row.email}}</td>'
+ '</tr>'
+ '</tbody>'
+ '<tfoot>'
+ '<tr>'
+ '<td colspan="4" class="text-center">'
+ '<div class="pagination pagination-xs m-top-none pull-right" st-pagination="1" st-items-by-page="itemsByPage" st-displayed-pages="4"></div>'
+ '</td>'
+ '</tr>'
+ '</tfoot>'
+ '</table>';
},
scope: {
},
controller: function ($scope) {
},
link: function (scope, elem, attrs) {
var nameList = ['Pierre', 'Pol', 'Jacques', 'Robert', 'Elisa'], familyName = ['Dupont', 'Germain', 'Delcourt', 'bjip', 'Menez'];
function createRandomItem() {
var
firstName = nameList[Math.floor(Math.random() * 4)],
lastName = familyName[Math.floor(Math.random() * 4)],
age = Math.floor(Math.random() * 100),
email = firstName + lastName + '##whatever.com',
balance = Math.random() * 3000;
return {
firstName: firstName,
lastName: lastName,
age: age,
email: email,
balance: balance
};
}
scope.rowCollection = [];
for (var j = 0; j < 10; j++) {
scope.rowCollection.push(createRandomItem());
}
scope.dataset = [].concat(scope.rowCollection);
}
};
}]);
My html contains this tag
<grid st-table="dataset"></grid>
This code is just a test, the data will be passed using a service.. and the template will be dynamic.
I need some help :-)
Thanks

I couldn't get the pagination to show when I was using smart-table in a directive the other day. Turns out it had nothing to do with the directive and I just pulled/updated to the very latest version of smart-table from GitHub and it all worked. I started looking at the what had changed but got side tracked and moved onto something more productive, happy that it was now working.
Version I have that appears to be working fine is tagged 1.4.2.
With dynamic data from a service, however, I think you're going to need to look at the st-safe-src attribute too. Although I'm new to this whole Angular / smart-table business too.

I believe you have to match the st-table value and the st-pagination value to get the pagination to show.

Related

include a variable into a template in angularjs

I'm using angularJs. I would like to include a variable into a popover template.
angular.forEach($scope.myList, function (obj) {
obj.details = '<div uib-popover-html="' + template/template.html + '" type="button"' +
'popover-placement="bottom" name="' + obj.id+'" id="' + obj.id+'" ' +
'popover-trigger="\'click\'"> ' +
'<i class="glyphicon glyphicon-eur"></i></div>';
The template is quite simple:
<div>
<button ng-click="onClick(object.id)"></button>
</div>
What is my problem is I don't know how to link this object.id with the forEach. So, for me it is just a template with a param.
Is it possible to do?

angular directive scope before return

I need to access a scope variable before entering the return of a directive.
I have a directive which returns a select element with options for each of the trucks.
<tc-vehicle-select label="Truck" selected="activeDailyLog.truck"></tc-vehicle-select>
I need to use the selected value in the directive to put a selected tag on the appropriate option element.
.directive('tcVehicleSelect', function(localStorageService) {
/* Get a list of trucks for the organization and populate a select element for
the user to choose the appropriate truck.
*/
var trucks = localStorageService.get('trucks');
var truckHtml;
for (var i = 0; i < trucks.length; i++) {
var truck = trucks[i];
var injectText;
if(truck.description){
injectText = truck.description
}else{
injectText = 'truck ' + truck.id
}
truckHtml += '<option value="' + truck.id + '">' + injectText + '</option>'
}
return {
scope: {
label: '#',
active: '#'
},
replace: true,
template: '<label class="item item-input item-select">' +
'<div class="input-label">{{label}}</div>' +
'<select ng-model="timeLog.truck"><option value="">None</option>' + truckHtml +
'</select></label>'
};
});
I have everything working in this directive except I'm stuck on setting the selected attribute on the correct element. If I could access the selected variable passed in I would be able to do it by inserting into the truckHtml, but I haven't found examples using that - only using the variables below in the retrun block.
Any ideas?
UPDATE: Also wanted to clarify that the activeDailyLog.truck in the HTML has the correct value I'm looking for.
It makes sense to place your directive's code inside the link function.
To retrieve the passed scope variable inside the directive, use = for two-way binding to the same object.
Code:
.directive('tcVehicleSelect', function(localStorageService) {
/* Get a list of trucks for the organization and populate a select element for
the user to choose the appropriate truck.
*/
return {
scope: {
selected: '='
},
replace: true,
template: '<label class="item item-input item-select">' +
'<div class="input-label">{{label}}</div>' +
'<select ng-model="timeLog.truck"><option value="">None</option>' + truckHtml +
'</select></label>',
link: function(scope, elem, attrs) {
var trucks = localStorageService.get('trucks');
trucks.forEach(function(truck) {
var injectText;
if(truck.description){
injectText = truck.description
} else {
injectText = 'truck ' + truck.id
}
truckHtml += '<option value="' + truck.id + '">' + injectText + '</option>'
}
// Access scope.selected here //
console.log(scope.selected);
}
};
});
Also replaced with Array.forEach() method, as it seemed more relevant in this context!
Internally there are very few differences between directive and a factory. The most important thing here is that directives are cached - your code is only run once and angular will keep reusing its return value every time it needs to use that directive.
That being said, you cannot access scope within directive declaration body - if you could it would be a scope of the first directive and its result would be cached and used for all other places you would use same directive.
I do like the idea of building the options before the return though, assuming you are sure it will not change during the application life (as it will save you from some unnecessary bindings). However, it is usually not true, so I would rather move the whole logic into a compile or even link function:
.directive('tcVehicleSelect', function(localStorageService) {
return {
scope: {
label: '#',
active: '#'
},
replace: true,
template: '<label class="item item-input item-select">' +
'<div class="input-label">{{label}}</div>' +
'<select ng-model="timeLog.truck"><option value="">None</option>'
'</select></label>',
link: function(scope, element) {
var trucks = localStorageService.get('trucks'),
select = element.find('select');
option;
trucks.forEach(function(truck) {
option = angular.element('<option></option>');
option.attr('value', truck.id);
option.html(truck.description || "truck" + truck.id);
if (truck.id === scope.selected.id) {
option.attribute('selected', 'selected');
}
select.append(option);
});
}
};
});

Angularjs, need best solution to move dynamic template formation and compilation code of multiple directives from controller to custom directive

I need to display multiple angularjs directives in a single page on tab click. It could be a combination c3 chart directives and ng grid directives. I am preparing the model with all these relevant parameters in the controller then forming the template and then compiling in the controller itself, which is perfectly working fine. As I realized doing DOM manipulation in controller is not a good practice, I am trying to do it in the custom directive.
This directive should support the following features :
The template should be combination of C3 chart directives.
The template can also have Angularjs ng Grid directive also along with c3 chart directives.
In future I also would like to use Good Map directive along with C3 chart and ng grid directives.
And some of these directives should be supported with custom dropdown.
For now I have used the following code in my controller which is perfectly working fine.
var template = '<div class= "chartsDiv">';
var dashletteId = 0;
var dashletterName = "chart";
var chartName = "";
for (var dashVar = 0; dashVar < data.tabDetails.length; dashVar++) {
dashletteId = data.tabDetails[dashVar].dashletteId; // Added
dashletterName = data.tabDetails[dashVar].dashletteName;
var axisType = data.tabDetails[dashVar].axisType;
var dataType = data.tabDetails[dashVar].dataType;
chartName = "chart" + eachTab.tabName.replace(/ +/g, "") + dashletteId ;
$scope[chartName] = {};
if (axisType == "timeseries") {
var xticksClassiffication = data.tabDetails[dashVar].xticksClassification;
var tickFormatFunction = {};
$scope[chartName] = {
data: {
x: 'x',
columns: data.tabDetails[dashVar].columns,
type: data.tabDetails[dashVar].dataType
},
axis: {
x: {
type: data.tabDetails[dashVar].axisType,
tick: {
format: data.tabDetails[dashVar].xtickformat
// '%Y-%m-%d'
}
}
},
subchart: {
show: true
}
};
}
if (dashletteId == 7) {
template += ' <div class="col"> <p class="graphtitle">' + dashletterName + ' </p> <span class="nullable"> <select ng-model="chartTypeSel" ng-options="eachChartType.name for eachChartType in chartTypeOptions" ng-change="transformChart(chartTypeSel, \'' + chartName + '\')"> </select> </span> <c3-simple id = "' + chartName + '" config="' + chartName + '"></c3-simple> </div>'
} else {
template += ' <div class="col"> <p class="graphtitle">' + dashletterName + ' </p> <c3-simple id = "' + chartName + '" config="' + chartName + '"></c3-simple> </div>';
}
}
template += ' </div>';
angular.element(document.querySelectorAll('.snap-content')).append($compile(template)($scope));
In order to make it simple I have provided only some sample code. Based on dashletteId, I have some specific requirements for which I am creating template dynamically based on dashletteId, all this code is perfectly working fine for me. Now my aim is to move all this template formation and compilation code from controller to a custom directive and I am looking for best possible solution for this, can any suggest me some pointers towards best solution.
For a specific user when he clicks any tab, what template has to be formed for compilation is predefined. So I can get that either during ng-init function call or tab's click (i.e, select) function call.
The following is sample code for my ng grid template formation.
if (axisType == "table") {
var config = {
9: {
gridOptions: 'gridOptionsOne',
data: 'dataOne',
columnDefs: 'colDefsOne'
},
10: {
gridOptions: 'gridOptionsTwo',
data: 'dataTwo',
columnDefs: 'colDefsTwo'
},
11: {
gridOptions: 'gridOptionsThree',
data: 'dataThree',
columnDefs: 'colDefsThree'
},
18: {
gridOptions: 'gridOptionsFour',
data: 'dataFour',
columnDefs: 'colDefsFour'
}
};
$scope.getColumnDefs = function(columns) {
var columnDefs = [];
columnDefs.push({
field: 'mode',
displayName: 'Mode',
enableCellEdit: true,
width: '10%'
});
columnDefs.push({
field: 'service',
displayName: 'Service',
enableCellEdit: true,
width: '10%'
});
angular.forEach(columns, function(value, key) {
columnDefs.push({
field: key,
displayName: value,
enableCellEdit: true,
width: '10%'
})
});
return columnDefs;
};
if (dataType == "nggridcomplex") {
$scope.serverResponse = {
columns: data.tabDetails[dashVar].columns,
data: data.tabDetails[dashVar].data
};
$scope[config[dashletteId].columnDefs] = $scope.serverResponse.columns;
$scope[config[dashletteId].data] = $scope.serverResponse.data;
} else {
if (dashletteId == 18) {
$scope.serverResponse = {
columns: data.tabDetails[dashVar].timespans[0], // This is for column headers.
data: data.tabDetails[dashVar].columns
};
} else {
$scope.serverResponse = {
columns: data.tabDetails[dashVar].timespans[0], // This is for column headers.
data: data.tabDetails[dashVar].columns
};
}
$scope[config[dashletteId].columnDefs] = $scope.getColumnDefs($scope.serverResponse.columns);
$scope[config[dashletteId].data] = $scope.serverResponse.data;
}
$scope[config[dashletteId].gridOptions] = {
data: config[dashletteId].data,
showGroupPanel: true,
jqueryUIDraggable: false,
columnDefs: config[dashletteId].columnDefs
};
template += ' <div class="col"> <p class="graphtitle">' + dashletterName + ' </p> <div class="gridStyle" ng-grid="' + config[dashletteId].gridOptions + '"></div>';
}
So in a single page I need to show four directives, it could be 3 c3 charts and 1 ng Grid table directive, or 2 C3 charts and 2 ng Grids tables, etc based on the predefined choice made by the user.
The following is preliminary code of my custom directive before working on further on this I thought of taking input from others for better approach. Here in my link function the template I need to get dynamically from controller upon tab click or ng-init phase, etc.
app.directive('customCharts', ['$compile', function($compile) {
return {
restrict: 'EA',
scope: {
chartName: '='
},
link: function(scope, element) {
var template = ' <div class="col"> <p class="graphtitle">' + dashletterName + ' </p> <c3-simple id = "' + chartName + '" config="' + chartName + '"></c3-simple> </div>'
var parent = angular.element(document.querySelectorAll('.chartsDiv')) // DOM element where the compiled template can be appended
var linkFn = $compile(template);
var content = linkFn(scope);
parent.append(content);
}
}
}]);
Please let me know if I need to provide any further clarification to my question. Any directions please with some sample code.
I would recommend creating one or more directives that each add a single component to the DOM - so 1,2, and 3 should be separate directives. In my experience, if you create a directive that adds multiple components you may end up trading an unwieldy controller for an unwieldy directive.
As for things like config and columnDefs, I would define configuration in the controller, or in a separate service/factory that is passed to the controller if you want to keep your controller really light, and then pass the config to the directive scope. This will allow you to re-use a directive within or across apps.
To watch for controller changes, in the directive scope.$watch() the appropriate attributes and then call the appropriate update functions in the directive. I believe this is what you mean by 4. This is demonstrated by the dropdown in my example.
I've had success without using $compile, but maybe others can argue for it.
Word of caution: If you are using a third party library that uses asynchronous functionality that Angular isn't aware of, you may need to put your directive's update behavior inside of a scope.$apply(), or else the DOM won't update until the next Angular digest cycle.
If your HTML is more than a line or two, I would recommend using templateUrl.
Here is a toy example using jQuery in a trivial way.
app.directive('chart', function() {
return {
restrict: 'EA',
scope: {config: '='},
templateUrl: 'chart.html',
link: function(scope, element) {
var el = element[0];
function update() {
$(el).html('<h4>' + scope.config.chartType + '</h4>');
}
scope.$watch('config', update, true);
}
}
});

AngularJS 1.0.7 in html5Mode and AngularStrap bs-select directive redirect to index

I have and AngularJS 1.0.7 web application and I´m using AngularStrap select directive. Everything was working fine until I have moved my application to html5Mode. I did that to prettify my URLs.
But now, when I select an option in any bs-select component I´m redirected to index.
HTML
<select class="show-tick" ng-model="selectType" ng-options="boatType as (boatType.name | translate) for boatType in boatTypes" bs-select>
<option value="" selected>{{'BOAT_TYPE' | translate}}</option>
</select>
JS Controller:
$scope.$watch('selectType', function() {
if($scope.selectType != undefined) {
BoatModel.query({type_id: $scope.selectType.id},
function success(result){
if(result.length == 0){
$scope.boatModels = new Array ();
$scope.selectModel = undefined;
SearcherService.setModel(undefined);
}
else{
$scope.boatModels = result.slice();
}
}
);
SearcherService.setType($scope.selectType.id);
$scope.selectModel = undefined;
}
else {
SearcherService.setType(undefined);
$scope.selectModel = undefined;
}
});
I fixed it.
I found out the code generated by the Bootstrap Select directive is not compatible with html5Mode because it inserted an href="#" in each option in the select. When you click on it you are redirected to the index.
Original Code in the directive
createA:function(test, classes) {
return '<a tabindex="-1" href="#" class="'+classes+'">' +
'<span class="pull-left">' + test + '</span>' +
'<i class="icon-ok check-mark"></i>' +
'</a>';
I just fixed it by removing the href, like this:
createA:function(test, classes) {
return '<a tabindex="-1" class="'+classes+'">' +
'<span class="pull-left">' + test + '</span>' +
'<i class="icon-ok check-mark"></i>' +
'</a>';

ng-repeat in a directive is being linked outside the directive

(Lots of similar questions/answers, but couldn't find a solution to this)
Trying to created nested directives. The issue is that the inner directives are being placed above the outer directive.
angular.module('mayofest14ClientApp')
.controller('OrderCtrl', ['$scope',
function ($scope) {
$scope.order = {
activities: [
{formattedTime: '2014-03-04', performedBy: 'matt', action: 'Action', comment: 'Some comment'},
],
};
}
])
.directive('orderActivity', [function () {
return {
scope: {
activities: '=',
},
replace:true,
restrict: 'E',
template:
'<div class="order_activity">' +
' <table>' +
' <caption>order_id History</caption>' +
' <thead>' +
' <tr>' +
' <th>Date</th>' +
' </tr>' +
' </thead>' +
' <tbody>' +
' <p ng-repeat="record in activities" record="record">Order activity {{record.action}} (This is where I want to call a nested directive)</p>' +
'' +
' </tbody>' +
' </table>' +
'</div>',
};
}]);
And the HTML,
<order-activity activities="order.activities"></order-activity>
The result is, the p tag with the ng-repeat is appearing before the template in the orderActivity directive.
I've read stuff about using transclude, messing with replace, some people mentioned using $timeout's or $watch's to adjust the order. The latter especially seemed messy and I couldn't find a good example of it.
Essentially, what can I do to get this rendering in the proper order?
Should I build the link function to generate the template, and write in all the 'record in activities' it self and avoid the 'ng-repeat'?
Oh, this is Angular 1.2.16
Thanks.
(Not sure this is a great solution)
The real motivation was to call an inner directive. I had used a p tag in the question just to simplify.
In any case, I seemed to solve this by ensuring that the inner-directive was compiled before the outer directive. I did this by using $compile and $interpolate, and by creating a scope for the inner directive.
My outer directive:
angular.module('mayofest14ClientApp')
.directive('orderActivity', ['$compile', function ($compile) {
var head =
'<div class="order_activity">' +
' <table>' +
' <caption>order_id History</caption>' +
' <thead>' +
' <tr>' +
' <th>Date</th>' +
' <th>Performed By</th>' +
' <th>Action</th>' +
' <th>Comment(s)</th>' +
' </tr>' +
' </thead>' +
' <tbody>\n';
var body = '';
var foot = ' </tbody>' +
' </table>' +
'</div>';
return {
scope: {
activities: '=',
},
restrict: 'E',
link: function (scope, element, attrs) {
for (var i = 0; i<scope.activities.length; i++) {
var itemScope = scope.$new(true);
itemScope.record=scope.activities[i];
body += $compile('<order-activity-item record="record"></order-activity-item>')(itemScope).html();
}
console.log("Compiling order-activity");
element.html(
$compile(head+body+foot)(scope)
);
}
};
}]);
My inner directive:
angular.module('mayofest14ClientApp')
.directive('orderActivityItem', ['$compile', '$interpolate', function ($compile, $interpolate) {
var template = '<tr><td>{{record.date}}</td><td>{{record.performedBy}}</td><td>{{record.action}}</td><td>{{record.comment}}</td></tr>';
return {
scope: {
record: '=',
},
restrict: 'E',
replace: true,
compile: function compile(element, attributes) {
console.log('orderItem (compile)');
return {
pre: function preLink(scope, element, attributes) {
console.log('orderItem (pre-link)');
element.html($interpolate(template)(scope));
},
post: function postLink(scope, element, attributes) {
console.log('orderItem (post-link)');
}
};
},
};
}]);
So the important parts here are that:
- I'm creating a scope, and giving it the record object read by the inner directive
- In the inner directive compile function, it $interpolates the values into the html, and then compiles it
- The compiled and interpolated HTML is returned to the linking of the outer directive
- The outer directive is then built.
I have not yet checked for memory errors or anything, nor do I know yet if doing this will impact performance in a negative way.
The reason I chose outer/inner directives is that I'd like the inner directives to have actions on it (click to modify, etc..), and this is how I would lay out the objects in a proper OO design. Not sure if directives should reflect OO architecture though.
Solutions I saw elsewhere used $watch's and $ons, some even used $timeouts. The latter seems messy and unpredictable, while the former seems odd.. In any case I wasn't really able to get those working.

Resources