Selecting single row on click in a data table - angularjs

I have been trying to select one row that is clicked on but the problem I am facing is that the table being paginated shows the selected row on different pages and does not show on other pages.
I am simply removing and adding a css class on click on a row.
something like -
function rowClicked (){
var table = $('#test').DataTable();
$('#test tbody').on( 'click','tr', function () {
if ( $(this).hasClass('selected') ) {
$(this).removeClass('selected');
console.log('helloe 1');
}
else {
table.$('tr.selected').removeClass('selected');
$(this).addClass('selected');
console.log('helloe 3');
}
} );
}

You can user Angualr directive like that to your table <table select-row>
angular.module('myApp')
.directive('selectRow', selectRow);
function selectRow($compile) {
var directive = {
bindToController: true,
controller: testCtrl,
controllerAs: 'vm',
link: link,
restrict: 'EA',
scope: {}
};
return directive;
function link(scope, element, attrs, vm) {
var target, compileScope, newTd, template ;
target = angular.element($('table tr'));
compileScope = scope;
scope.arr = [];
angular.forEach(target, function(item) {
return scope.arr.push(item.textContent);
});
template = '<table><thead><tr><th></th><tbody><tr ng-class="selected[$index]" ng-repeat="t in arr track by $index" ng-click="selectRow($index)"><td >{{t}}</td></tr></tbody></tr></thead></table>';
template = angular.element(template);
$compile(template)(compileScope);
element.replaceWith(template);
// function to select row
scope.selectRow = function($index) {
scope.selected = [];
scope.index = $index;
scope.selected[$index] = scope.selected[$index] === 'selected' ? '' :'selected';
}
}
function testCtrl() {
}
}

Related

$scope not update from AngularJS directive

I implemented custom directive for if select element, in which if there is only one item then it selects that item by default else it works as regular drop down which works fine.
My problem is I have two drop down as cascade. A Company list and a Department list. The Department list depends on a Company list. So when only one Company is selected by default then Department list will also update respectively by default.
For that I try to use ng-change but It does not access updated value it shows old value in log.
My angularjs code is below:
angular.module('TestApp', [
]);
angular.module('TestApp').directive('advanceDropdown', function () {
var directive = {}
directive.restrict = 'A';
directive.require = 'ngModel';
directive.scope = {
items: '=advanceDropdown',
model: '=ngModel',
key: '#key',
change: '&ngChange'
}
directive.link = function (scope, element, attrs, ctrl) {
scope.$watch('items', function (n, o) {
if (scope.items.length == 1) {
scope.model = scope.items[0][scope.key];
scope.change();
}
});
}
return directive;
});
(function () {
'use strict';
angular.module('TestApp').controller('TestCtrl', ['$scope', TestCtrl]);
function TestCtrl($scope) {
$scope.departmentList = [];
$scope.companyList = [];
$scope.designation = new Designation();
$scope.active = null;
$scope.BindDepartments = function () {
console.log('updated companyId:' + $scope.designation.CompanyId);
//Code here
//Load department base on selected company
//$scope.departmentList = data;
}
$scope.GetCompanyById = function (companyId) {
//Get company data by id
//recived from server
$scope.companyList = [{CompanyId:2,CompanyName:'xyz'}];
}
$scope.Init = function () {
$scope.active = null;
$scope.designation = new Designation();
$scope.GetCompanyById(2);
}
}
})();
function Designation() {
this.DesignationId = 0;
this.DesignationName = '';
this.DepartmentId = 0;
this.CompanyId = 0;
}
Designation.prototype = {
constructor: Designation
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.2/angular.min.js"></script>
<div ng-app="TestApp" ng-controller="TestCtrl">
<select ng-model="designation.CompanyId"
ng-options="dept.CompanyId as dept.CompanyName for dept in companyList"
ng-change="BindDepartments()"
advance-dropdown="companyList" key="CompanyId" required>
<option value="" disabled>--Select Company--</option>
</select>
</div>
In a BindDepartment() method $scope.designation.CompanyId value was not updated.
It Show log updated company id:0
I expect updated company id:2
Plunker demo
Avoid using isolate scope with the ng-model controller.
//directive.scope = {
// items: '=advanceDropdown',
// model: '=ngModel',
// key: '#key',
// change: '&ngChange'
//}
directive.scope = false;
Instead use the $setViewValue method to change the model value.
directive.link = function (scope, element, attrs, ctrl) {
scope.$watchCollection(attrs.advanceDropdown, function (n, o) {
if (n && n.length == 1) {
ctrl.$setViewValue(n[0]);
}
});
}
The $setViewValue method will automatically invoke the ng-change expression.

Angularjs responsive directive live updating issue (possibly due to ng-repeating the directive)

I am creating a post feed by ng-repeating JSON files from the cloud. I tried to make the posts responsive by using angular directives that update the template url with the screen size.
The problem is that only the last post in the ng-repeat responds and changes templates (with or without the reverse filter) when I resize the page. The other posts just remain the template that it was when originally loaded.
Here's the ng-repeat in the page
<div ng-show="post_loaded" ng-repeat="post in posts | reverse | filter:searchText ">
<feed-post>
</feed-post>
</div>
Here's the directive javascript file
app.directive('feedPost', function ($window) {
return {
restrict: 'E',
template: '<div ng-include="templateUrl"></div>',
link: function(scope) {
$window.onresize = function() {
changeTemplate();
scope.$apply();
};
changeTemplate();
function changeTemplate() {
var screenWidth = $window.innerWidth;
if (screenWidth < 768) {
scope.templateUrl = 'directives/post_mobile.html';
} else if (screenWidth >= 768) {
scope.templateUrl = 'directives/post_desktop.html';
}
}
}
};});
This happens because you re-assigning the .onresize in each directive and it stays effective only for the last linked directive.
I'd suggest to use it in a more angular way. You don't actually need a custom directive
In the controller that manages list of posts add reference to $window in $scope
$scope.window = $window;
Then in template make use of it
<div ng-include="directives/post_mobile.html" ng-if="window.innerWidth < 768"></div>
<div ng-include="directives/post_desktop.html" ng-if="window.innerWidth >= 768"></div>
To avoid extra wrappers for posts feed you might want to use ng-repeat-start, ng-repeat-end directives
this is a directive i wrote based on bootstrap sizes and ngIf directive :
mainApp.directive("responsive", function($window, $animate) {
return {
restrict: "A",
transclude: 'element',
terminal: true,
link: function($scope, $element, $attr, ctrl, $transclude) {
//var val = $attr["responsive"];
var block, childScope;
$scope.$watch(function(){ return $window.innerWidth; }, function (width) {
if (width < 768) {
var s = "xs";
} else if (width < 992) {
var s = "sm";
} else if (width < 1200) {
var s = "md";
} else {
var s = "lg";
}
console.log("responsive ok?", $attr.responsive == s);
if ($attr.responsive == s) {
if (!childScope) {
$transclude(function(clone, newScope) {
childScope = newScope;
clone[clone.length++] = document.createComment(' end responsive: ' + $attr.responsive + ' ');
block = {
clone: clone
};
$animate.enter(clone, $element.parent(), $element);
});
}
} else {
if (childScope) {
childScope.$destroy();
childScope = null;
}
if (block) {
block.clone.remove();
block.clone = null;
block = null;
}
}
});
}
};
});

AngularJs filtering on click using a custom directive

I am trying to filter a list in using a click event in custom directive the filter works fine but how do I assign the filtered items so it is reflected in ng-repeat? Right now the list generated using ng-repeat on quesCompletedHeaders is not changed after the click event has taken place
app.directive('sectionSelector', ['$filter', 'questionnaireSections',
function ($filter, questionnaireSections) {
return {
restrict: 'A',
link: function (scope, iElement, iAttrs) {
iElement.on('click', function () {
scope.$apply(function () {
// filters correct question range
var filtered = $filter('rangeFilter')(scope.quesCompletedHeaders, [5, 10], true);
console.log(filtered); //=> This is correct how to put back on scope?
// There is an ng-repeat on quesCompletedHeaders in the template
// but nothing changes even when assigning filted results back
// on scope
scope.quesCompletedHeaders = filtered;
// works with $parent not sure why a child scope is
// created with the directive
scope.$parent.quesCompletedHeaders = filtered
});
});
}
};
}
]);
Ok I found out the reason sectionSelector is creating a child scope which should not be since scope:false is the default? why is this happening?
using scope.$parent.quesCompletedHeaders = filtered; solves the issue
Add scope 'quesCompletedHeaders' to directive
HTML
<hello-world sectionSelector="filtered.quesCompletedHeaders"></hello-world>
Javascript
app.controller('MainCtrl', function($scope) {
$scope.filtered.quesCompletedHeaders = {};
});
app.directive('sectionSelector', ['$filter', 'questionnaireSections',
function ($filter, questionnaireSections) {
return {
restrict: 'EA',
scope: {
questionnaireSections: "=",
},
link: function (scope, iElement, iAttrs) {
iElement.on('click', function () {
scope.$apply(function () {
// filters correct question range
var filtered = $filter('rangeFilter')(scope.quesCompletedHeaders, [5, 10], true);
console.log(filtered);
scope.filtered.quesCompletedHeaders = filtered;
});
});
}
};
}
]);
There is an easy alternative instead of directive.
you can have ng-click on the element and apply filter in controller to the list.
In HTML,
<input type="button" value="select section" ng-click="filterquestionaire(5,10)"/>
in Controller,
$scope.filterquestionaire = function(start,end) {
var filtered = $filter('rangeFilter')(scope.quesCompletedHeaders, [start, end], true);
$scope.quesCompletedHeaders = filtered;
};
OK so here is my final solution.
$scope.rangeFilter = function (range) {
$scope.quesRangeStart = range[0];
$scope.quesRangeEnd = range[1];
}
In the template I am piping the filter
ng-repeat="question in quesCompletedHeaders|rangeFilter:[quesRangeStart, quesRangeEnd]"
and on the filter buttons
ng-repeat="section in questionnaireSections"
ng-click="rangeFilter(section.range)"
and filter code
app.filter('rangeFilter', function () {
return function (items, rangeInfo) {
var min = parseInt(rangeInfo[0]);
var max = parseInt(rangeInfo[1]);
var filtered = items.filter(function (header) {
return header.rang >= min && header.rang <= max;
});
return filtered;
};
});

angularjs directive 2 way binding not working

Here is my directive, it's simple task is to Locale Date String time:
.directive('localeDateString',['$window', function ($window) {
return {
restrict: 'E',
replace: true,
scope: {
time: '='
},
template: '<span>{{timeLocal}}</span>',
link: function ($scope, $element) {
if ($scope.time != null) {
profileDate = new Date($scope.time);
var cultureCode = $window.ApiData.CultureCode;
$scope.timeLocal = profileDate.toLocaleDateString(cultureCode);
}
}
};
}])
Usage in HTML:
<li ng-repeat="note in profile.AccountProfile.Notes" class="noteItem">
<locale-date-string time="note.Created" ></locale-date-string>
<span>{{note.UserName}}</span>
<!-- other stuff .. -->
</li>
When I'm loading the object "profile" from JSON everything is OK
the problem is when i change "note.Created" from controller - the directive seem not to work(other members of Note are updating ok):
In the controller:
DataService.updateProfileRemark(objRemark)
.then(function (response) {
// all is ok;
var profileIndex = $scope.ProfileList.indexOf(profile);
var noteIndex = $scope.ProfileList[profileIndex].AccountProfile.Notes.indexOf(note);
// this is working:
$scope.ProfileList[profileIndex].AccountProfile.Notes[noteIndex].UserName = objRemark.UserName;
// this is not:
$scope.ProfileList[profileIndex].AccountProfile.Notes[noteIndex].Created = Date.now();
},
function (errResponse) {
// handle err
}
);
For example, here is the scope before "updateProfileRemark":
and after:
Why the 2 way binding not working?
Thanks.
link is only executed once. If you want to setup two-way binding between $scope.timeLocal and $scope.time, setup a $watch:
link: function ($scope, $element) {
$scope.$watch('time', function(newTime) {
if (newTime != null) {
var profileDate = new Date(newTime);
var cultureCode = $window.ApiData.CultureCode;
$scope.timeLocal = profileDate.toLocaleDateString(cultureCode);
}
});

Jquery Masonry with AngularJS

in my project i try to implement jquery masonry. but its getting working. i try googling but found some post. but i tried it its not working.
my directive code is
shout.directive("shoutList", function($timeout) {
return {
restrict : 'E',
replace : true,
templateUrl : 'views/shout/shout-list.html',
scope : {
shouts : "="
},
//require : "ShoutController",
controller : function($scope) {
$scope.deleteShout = function() {
console.log('shout deleted');
}
},
link : function(scope, element, attr) {
scope.$watch('shouts', function() {
// console.log("changing......");
// scope.$evalAsync(
document.getElementById("shout-content-holder").masonry({
itemSelector: '.shout'
})
// );
});
}
}
});
directive template is
<div id="shout-content-holder">
<div class="shout" ng-repeat="shout in shouts">
<p>{{shout.message}}</p>
<img src="media/images/delete.png" width="32" height="32" ng-click="deleteShout()"/>
</div>
</div>
i load the shouts from a webservice. please help me to make this work...
Putting what I mentioned in comment as an answer. This could actually be done using a template rather than using .append() , it would be cleaner. What you'd need is a template to contain a list of columns and ng-repeat, should work just as well, but you'd have to wait for the first item to get inserted before you calculate where to insert the second item; hence the use of .append() here.
.directive('columns', function(){
return {
restrict: 'E',
scope: {
itemList: '=', // a list of items
colCount: "#" // number of columns
},
link: function(scope, elm, attrs) {
//console.log(scope.itemList);
var numCols = parseInt(scope.colCount);
var colsArr = [];
for(var i=0; i< numCols; i++){
colsArr.push(angular.element("<div class='column' style='width:"+(100/numCols -.5)+"%' >Col "+(i+1)+"</div>"));
elm.append(colsArr[i]);
}
angular.forEach(scope.itemList, function(value, key){
var item = angular.element("<div class='item' style='height:"+value.height+"px; background:"+'#'+Math.floor(Math.random()*16777215).toString(16)+"'>"+value.value+"</div>");
var smallestColumn = getSmallestColumn();
angular.element(smallestColumn).append(item);
});
function getSmallestColumn(){
var smallestHeight = colsArr[0][0].offsetHeight;
var smallestColumn = colsArr[0][0];
angular.forEach(colsArr, function(column, key){
if(column[0].offsetHeight < smallestHeight){
smallestHeight = column[0].offsetHeight;
smallestColumn = colsArr[key];
}
});
return smallestColumn;
}
}
};
});
plnkr.co/edit/UyRS0clrCwDpSrYgBsXS?p=preview
You probably want to trigger your masonry() call on the $last ng-repeat instead of using $watch. I just recently answered a question about this, so I will refer you there: https://stackoverflow.com/a/14656888/215945

Resources