Travelling across a table in angularjs - angularjs

I am displaying a table and letting the user click on a particular cell in this table. When the user clicks on a particular cell, I highlight it.
Next I am trying to mobilize the arrow keys on the keyboard. i.e
when user presses "right-arrow" key .. the next cell should get highlighted and if the user presses "top-arrow" key.. the cell above the current one should get selected.
I believe you guys get the flow.
This is excel like functionality.
I am almost there but not yet.. any anyone point me in the right direction.
My plnkr here:
http://plnkr.co/edit/Hahh4uyQ130zOS8noC3D

I would create a directive watching a model value object called 'selected'. Something like this:
<tr ng-repeat="element in body">
<td ng-repeat="h in header"
my-cell="selected"
row="{{$parent.$index}}"
col="{{$index}}"
style="width:{{element.width}}px">{{element[h.column]}}
</td>
</tr>
UPDATE
The issue around the above solution is that ng-repeat has isolated scope for each $index entry it is in so changes in $scope.select were not being seen by other cells. To get around this I used $emit and $on events, look at: http://plnkr.co/edit/CS21gUe3arstrgubnpTr?p=preview
angular.module("CustomTable").directive("selectMe", function($rootScope) {
return ({
restrict: "A",
link: link,
require: "^customTable"
});
function link(scope, element, attributes, ctCtrl) {
var selected,
mkEvent = function(r,c) {
scope.$emit('selectMeEvent',{
row: r,
col: c
});
}
element.on('click', function(e) {
mkEvent(attributes.row,attributes.col);
});
$rootScope.$on('selectMeEvent',function(e,sObj) {
(sObj.row === attributes.row &&
sObj.col === attributes.col) ?
element.addClass('highlight-me') :
element.removeClass('highlight-me');
});
}
});

Related

Change vm variable value after clicking anywhere apart from a specific element

When I click anywhere in the page apart from ul element (where countries are listed) and the suggestion-text input element (where I type country name), vm.suggested in controller should be set to null. As a result ul element will be closed automatically. How can I do this?
I've seen Click everywhere but here event and AngularJS dropdown directive hide when clicking outside where custom directive is discussed but I couldn't work out how to adapt it to my example.
Markup
<div>
<div id="suggestion-cover">
<input id="suggestion-text" type="text" ng-model="vm.countryName" ng-change="vm.countryNameChanged()">
<ul id="suggest" ng-if="vm.suggested">
<li ng-repeat="country in vm.suggested" ng-click="vm.select(country)">{{ country }}</li>
</ul>
</div>
<table class="table table-hover">
<tr>
<th>Teams</th>
</tr>
<tr ng-if="vm.teams">
<td><div ng-repeat="team in vm.teams">{{ team }}</div></td>
</tr>
</table>
<!-- There are many more elements here onwards -->
</div>
Controller
'use strict';
angular
.module('myApp')
.controller('readController', readController);
function readController() {
var vm = this;
vm.countryNameChanged = countryNameChanged;
vm.select = select;
vm.teams = {.....};
vm.countryName = null;
vm.suggested = null;
function countryNameChanged() {
// I have a logic here
}
function select(country) {
// I have a logic here
}
}
I solved the issue by calling controller function from within the directive so when user clicks outside (anywhere in the page) of the element, controller function gets triggered by directive.
View
<ul ng-if="vm.suggested" close-suggestion="vm.closeSuggestion()">
Controller
function closeSuggestion() {
vm.suggested = null;
}
Directive
angular.module('myApp').directive('closeSuggestion', [
'$document',
function (
$document
) {
return {
restrict: 'A',
scope: {
closeSuggestion: '&'
},
link: function (scope, element, attributes) {
$document.on('click', function (e) {
if (element !== e.target && !element[0].contains(e.target)) {
scope.$apply(function () {
scope.closeSuggestion();
});
}
});
}
}
}
]);
This is just an example but you can simply put ng-click on body that will reset your list to undefined.
Here's example:
http://plnkr.co/edit/iSw4Fqqg4VoUCSJ00tX4?p=preview
You will need on li elements:
$event.stopPropagation();
so your html:
<li ng-repeat="country in vm.suggested" ng-click="vm.select(country); $event.stopPropagation()">{{ country }}</li>
and your body tag:
<body ng-app="myWebApp" ng-controller="Controller01 as vm" ng-click="vm.suggested=undefined;">
UPDATE:
As I said it's only an example, you could potentially put it on body and then capture click there, and broadcast 'closeEvent' event throughout the app. You could then listen on your controller for that event - and close all. That would be one way to work around your problem, and I find it pretty decent solution.
Updated plunker showing communication between 2 controllers here:
http://plnkr.co/edit/iSw4Fqqg4VoUCSJ00tX4?p=preview
LAST UPDATE:
Ok, last try - create a directive or just a div doesn't really matter, and put it as an overlay when <li> elements are open, and on click close it down. Currently it's invisible - you can put some background color to visualize it.
Updated plunker:
http://plnkr.co/edit/iSw4Fqqg4VoUCSJ00tX4?p=preview
And finally totally different approach
After some giving it some thought I actually saw that we're looking at problem from the totally wrong perspective so final and in my opinion best solution for this problem would be to use ng-blur and put small timeout on function just enough so click is taken in case someone chose country:
on controller:
this.close = function () {
$timeout(()=>{
this.suggested = undefined;
}, 200);
}
on html:
<input id="suggestion-text" type="text" ng-model="vm.countryName" ng-change="vm.countryNameChanged()" ng-blur="vm.close()">
This way you won't have to do it jQuery way (your solution) which I was actually trying to avoid in all of my previous solutions.
Here is plnker: http://plnkr.co/edit/w5ETNCYsTHySyMW46WvO?p=preview

How to get current sorted column in smart-table

I have st-sort directive on all columns in my smart-table. When an user clicks certain column how can I get current sorted column? Is there some trick or do I have to listen to the click event on those column headers?
you can use the directive st-pipe, this function will be called for sorting, paginating or filtering events.
<table st-table="displayedCollection" st-safe-src="rowCollection" st-pipe="customPipe">
$scope.customPipe = function(tableState){
console.log(tableState.sort);
}
I think the technique which will give you the best control will be to create a plugin which will watch changes in the table state and will call a provided callback whenever a change is detected (in your case you will pay particular attention to the "sort" namespace of the table state
module.directive('stSentinel',function (){
return{
require:'^stTable',
scope:{
onChange:'&stSentinel'
},
link:function(scope, element, attr, stTable){
scope.$watch(function(){
return stTable.tableState();
},function (newVal){
scope.onChange(newVal);
},
true)}
}
};
});
which you can use in your markup
<table st-table="foo" st-sentinel="myCtrl.applyChange(tableState)"> ... </table>
Your controller will define the applyChange method to react the changes

Turn On ng-repeat element when loading repeater

I have a typical ng-repeat as you'll see below:
<button ng-repeat="tube in safeConfig.tubes" on-repeat-done="tubes_done" ng-class="{'btn btn-on' : tube.toggled, 'btn btn-off' : !tube.toggled}" ng-click="toggleBtn(tube, 'tube', $index)">{{tube.label}}</button>
As you can see when the button is clicked it runs a function to toggle the button on. It also adds an item to an array that is used in a custom filter to filter out items from a separate ng-repeat element.
I'm trying to figure out how to have the buttons in the ng-repeat already turned on when the page loads. The directive I have attached to the ng-repeat item is being called. the filterData service loads the array that holds the items that are filtering the list below:
secure.directive("toggleButtonOnLoad", ['filterData', function (filterData) {
return {
restriction: 'A',
scope: {tube: '='},
link: function ($scope, element, attributes) {
var filterItems = filterData.getFilterItems();
if ($scope.$last) {
$scope.$emit(attributes["onRepeatDone"] || "repeat_done", element);
}
}
}
}]);
Is there a way to access the individual button and turn it on with the structure I've started? It seems wrong that filterItems would get called with every button, maybe extra unnecessary work. I need to essentially do something like item.toggled on each item if it is in the array.
When I step through it I'm trying to access element[0].innerText() to compare it with items in the array. And it just shows the binding {{tube.label}} Any way to show it evaluated?
Any thoughts or direction would be appreciated.
I ended up adding two attributes on my HTML
device='tube' toggle-button-on-load=''
Full HTML below:
<button ng-repeat="tube in safeConfig.tubes" device='tube' toggle-button-on-load='' ng-class="{'btn btn-on' : tube.toggled, 'btn btn-off' : !tube.toggled}" ng-click="toggleBtn(tube, 'tube', $index)">{{tube.label}}</button>
The directive gives me access to the device
secure.directive("toggleButtonOnLoad", ['filterData', 'utility', function (filterData, utility) {
return {
restriction: 'A',
scope: { device: '=' },
link: function ($scope, element, attr) {
var filterItems = filterData.getFilterItems();
if (filterItems.indexOf(utility.squish($scope.device.label)) !== -1) {
$scope.device.toggled = true;
}
}
}
}]);
I can now access the object itself getting bound to the element and turn the toggle on/off depending on the arry returned from my service getFilterItems().

AngularJS directive remove class in parent element

I am using the following code to add / remove class "checked" to the radio input parent. It works perfectly when I use JQuery selector inside the directive but fails when I try to use the directive element, can someone please check my code and tell me why it is not working with element and how I can possibly add/ remove class checked to the radio input parent while using element instead of the jquery selectors? Thanks
.directive('disInpDir', function() {
return {
restrict: 'A',
scope: {
inpflag: '='
},
link: function(scope, element, attrs) {
element.bind('click', function(){
//This code will not work
if(element.parent().hasClass("checked")){
scope.$apply(function(){
element.parent().removeClass("checked");
element.parent().addClass("checked");
});
}else{
scope.$apply(function(){
element.parent().addClass("checked");
});
}
//This code works perfectly
$('input:not(:checked)').parent().removeClass("checked");
$('input:checked').parent().addClass("checked");
});
}
};
});
HTML:
<div class="inpwrap" for="image1">
<input type="radio" id="image1" name="radio1" value="" inpflag="imageLoaded" dis-inp-dir/>
</div>
<div class="inpwrap" for="image2">
<input type="radio" id="image2" name="radio1" value="" inpflag="imageLoaded" dis-inp-dir/>
</div>
Your code actually works for me in Plnkr (more or less):
http://plnkr.co/edit/vJJRYQQxH7u2bKSc27UA?p=preview
When you run this, the 'checked' class gets correctly added to the parent DIVs using only the first code you included. (I commented out the jQuery mechanism - I didn't add jQuery to this page, as a test.)
However, I think what you're trying to accomplish isn't working out because you're only capturing click events. The radio button that loses its checked attribute doesn't get a click event, only the next one does. In jQuery your selector is really broad - you're hitting every radio button, so it does what you want. But since you only trap click on the radio button that receives the click, it doesn't do what you want using the other pattern. checked gets added, but never removed.
A more Angular-ish pattern would be something like this:
http://plnkr.co/edit/HN7tLxkRA0jUL5GPjk5V?p=preview
link: function($scope) {
$scope.checked = false;
$scope.$watch('currentValue', function() {
$scope.checked = ($scope.currentValue === $scope.imgNumber);
});
$scope.setValue = function() {
$scope.currentValue = $scope.imgNumber;
};
}
What you see here lets Angular do all the dirty work, which is kind of the point. You can actually go a lot further than this - you could probably cut half the code out and do it all with expressions. The point is that in Angular, you really want to focus on the DATA (the model). You wire all of your behaviors and events up (controller) to things that manipulate that data, and then wire up all your DOM styles, classes, templates (view), etc. up to conditionals against that same data. And that is the point of MVC!

is there a way to get the DOM object given a $$hashKey?

I have a <table> where each row has a couple of input type="text". I want to validate if an input is empty and if so, then add a CSS class to this input field which will display an error. All I got in the $scope is the $$hashKey, I know this is an unique value to identify an element of a ng-repeat list.
How could I get the DOM object given this $$hashKey?. I was digging using the Developer Tools but I didn't find it.
Instead of trying to manipulate the DOM (ie find and element and add/remove a class) from your controller (or service), you should be doing it from a directive.
Write a directive that will do the validation for you:
.directive('validateField', function(){
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attrs, ngModel){
scope.$watch(attrs.ngModel, function(newVal, oldVal){
var isValid = false;
// do some validation checking on newVal here
ngModel.$setValidity('tableInput', isValid);
});
}
};
});
As noted in the docs here, the $setValidity function will automatically add a class to the element for you, based on whatever key you provide. In this case, we provided a key of 'tableInput' so it will add a class of ng-invalid-table-input when the model is invalid, and a class of ng-valid-table-input when the model is valid.
So in your CSS, all you now have to do is create a rule with some special styles:
input.ng-invalid-table-input{
/* special styles go here */
}
input.ng-valid-table-input{
/* special styles go here */
}
And then you would use this in your view as such:
<table>
<tr ng-repeat="things in listOfThings">
<td ng-repeat="thing in things">
<input type="text" ng-model="someValue" validate-field />
</td>
</tr>
</table>
And then any input in your table will be dynamically (and automatically) validated and styled. Does that make sense? You'd have to modify the above example, but hopefully it points you in the right direction.

Resources