I created a simple directive that utilizes colResizable plugin that simply activates the plugin on a table after it is rendered:
app.directive("columnResizable", this.columnResizable = function($timeout) {
return {
restrict: "A",
link: function(scope, element, attrs) {
// Initialize colResizable plugin after DOM
$timeout(function() {
element.colResizable({
liveDrag:true,
postbackSafe: true,
partialRefresh: true,
minWidth: 100
});
});
}
}
});
This directive worked fine until I needed to adda feature to hide and show columns in the table. I use ng-hide and turn columns on or off by changing a localStorage boolean variable. The columns hide/show just as expected if I start from a "show all" state. But refuses to show if I start from a hidden state:
<button class="btn btn-default"
ng-class="{active: emailHidden}"
ng-click="emailHidden = !emailHidden">Hide Email</button>
<table column-resizable class="table table-bordered table-striped">
<thead>
<tr>
<th>Name</th>
<th ng-hide="emailHidden">Email</th>
</tr>
</thead>
<tbody ng-if="!scheduleEntries || scheduleEntries.length == 0">
<tr>
<td>Foo Bar</td>
<td ng-hide="emailHidden">foo#bar.com</td>
</tr>
</tbody>
</table>
I created a plunker with regular boolean variable. If you change the $scope.emailHidden variable to start hidden, you can see that the Email column will not show when the button is clicked: http://plnkr.co/edit/Y20BH2
I ended up adding a watcher for the emailHidden variable inside the directive, once changed, it resets the column widths and reinitializes the colResizable plugin:
scope.$watch('emailHidden', function(newVal) {
element.find('th').css('width','auto');
$timeout(function() {
element.colResizable({
liveDrag:true,
postbackSafe: true,
partialRefresh: true,
minWidth: 100
});
});
});
updated plunker
I appreciate it if anyone has a better way to work this out. Preferably something that doesn't involve $watch
When $scope.emailHidden value is set to true, ColResizer updated the width of the second column to zero. And this width is not getting updated even after the emailHidden flag is set to false. As the second column is not having any width in this case, we are not able to see it on the screen.
Related
I am trying to populate a table based on an array of objects. This array doesn't contain objects of the same type and for each row I'd like a completetly diferent style and, onclick function, basically a completely different behaviour.
For instance,
var data=[
{
type:'dir-b',
data: { ... }
},
{
type:'dir-b',
data: { ... }
},
{
type:'dir-c',
data: { ... }
}
]
For object type dirB I want a template and controller and for dirC a completely different function and template.
The solution I found was to create 3 directives. One of wich will run to determine one of the other two directives to add based on data.
.directive("dirA", function($compile){
return{
restrict:'A',
priority:1000,
terminal:true,
link: function(scope, element, attribute){
element.removeAttr("dir-a");//prevent endless loop
element.attr(attribute.type,"");
$compile(element)(scope);
}
}
})
.directive("dirB", function($compile){
return{
restrict:'A',
replace:true,
link: function(scope, element, attribute){
console.log("dirA");
}
}
})
.directive("dirC", function($compile){
return{
restrict:'A',
replace:true,
link: function(scope, element, attribute){
console.log("dirC");
}
}
});
Using <tr dir-a type='{{d.type}}' ng-repeat='d in data'/> is not having the desired effect. Either I give dirA a priority of 0 and it can parse the attribute but it's repeated more times than the array size, or I give it a priority of 1000 and it can't parse the b.type and use it as a literal.
Does anyone have a solution for this?
You could potentially use an ngSwitch here.
Plnkr
HTML
<div ng-repeat="(key, d) in data track by $index">
<div class="tbody" ng-switch on="d.type">
<div class="row" ng-switch-when="dir-b" dir-b>{{d}}</div>
<div class="row" ng-switch-when="dir-c" dir-c>{{d}}</div>
</div>
</div>
Then you just define dirB and dirC directives.
This doesn't display as an html table though, you can hopefully work from this though?
Not sure this was the best solution but it was the solution I found.
<table>
<tbody ng-repeat='d in data'>
<tr ng-if='d.type=="dir-b"' dir-b></tr>
<tr ng-if='d.type=="dir-c"' dir-c></tr>
</tbody>
</table>
This way due to ng-if only the correct row will ever be displayed but the problem is that tbody will be repeated as many row as there are in data. But until there is a beter solution this is how I did it.
This is the directive I'm using, there's a list of names that is dynamically generated from JSON. When you click on a name, it is suppose to show/hide a window with more info on that name. What happens instead is it shows/hides every window for every name in the list. I want it to just show/hide the window for the one I click on.
JS:
app.directive("taskListing", function() {
return {
restrict: 'E',
templateUrl: "/templates/elements/tasklisting.html",
scope: {},
link: function(scope, element, attrs, $sce){
element.on("click", function(){
angular.element("tbody.task-tbody tr").toggleClass("hidden");
});
},
};
});
HTML:
<table class="table" ng-controller="taskController">
<tbody class="task-tbody" ng-repeat="task in tasks" ng-if="task.title != ''">
<tr >
<td>
<span class='tasks-task'>{{task.title}}</span>
</td>
</tr>
<!--This table row is toggled show/hide-->
<tr class="hidden" bgcolor="#F8F8F8" >
<td>
<strong>Description:</strong>
<p>{{task.description}}</p>
</td>
</tr>
</tbody>
</table>
you have wrong query in angular.element("tbody.task-tbody tr") you must specify which tr you want to show
first hide all tr and then show only one with specific ID for example
angular.element("tbody.task-tbody tr").addClass('hidden');
angular.element("#task_8").removeClass('hidden');
specify task id in template:
<tr id="task_{{task.id}}">
It is difficult to be certain without your HTML, but I believe that your issue is angular.element("tbody.task-tbody tr").toggleClass("hidden");.
angular.element(document) aliases a jQuery function (ng docs). In this case it is aliasing a selector and selecting all of the rows in your "tbody.task-tbody tr". Thus, when you are calling .toggleClass("hidden"), jQuery is applying the "hidden" class to all of those elements.
Given that you only want to hide the element that has been clicked on, you can use the provided reference to the element in the directive to apply "hidden" exclusively to that element.
For example:
app.directive("taskListing", function() {
return {
restrict: 'E',
templateUrl: "/templates/elements/tasklisting.html",
scope: {},
link: function(scope, element, attrs, $sce){
element.on("click", function(){
// use element instead of 'angular.element'
element.toggleClass("hidden");
});
}
}
});
I think this will solve your problem.
The element you're listening the click event is the directive itself, so everytime you click on something inside the directive, every <tr> will have the toggleClass performed.
So instead of element.on("click",.... you should do element.find("tbody.task-tbody tr").on("click",....
And if you only want to toggle the visiblity of the <tr> with the #F8F8F8 background, I suggest you add a class to target it more easily.
[edit]
Your link function would be:
function(scope, element) {
element.find("tbody.task-tbody tr").on("click", function() {
this.toggleClass("hidden");
});
}
I have two date fields within a ng-repeat table, the code looks like this:
<tr ng-repeat="ol in orderLines">
<td>
<input class="audioStartTime" type="text" ng-model="ol.AudioStartTime"/>
</td>
<td>
<input class="audioEndTime" type="text" ng-model="ol.AudioEndTime"/>
</td>
Just like the name stated, audioStartTime and audioEndTime are time fields, so I need to apply input mask on them. I used the following code:
$('.audioStartTime').each(function(index) {
$(this).inputmask("hh:mm:ss", {placeholder: "HH:MM:SS", insertMode: false, showMaskOnHover: false});
});
I tested this code. After the part completely loads and I input them in the developer tool's console, it works pretty well. However, the problem is, when I put this code into document.ready, this doesn't seem to work.
It seems to me that angularJS might have a mechanism of reloading/rerendering the table content after calculating the ng-repeat data. Is that true? Is so, where can I put my input mask code so it can be done after the loading of angularJS elements?
I just got the answer myself. And this is the link helped me getting my answer: http://valve.github.io/blog/2013/08/01/jquery-inputmask-plugin-plus-angularjs/
So basically to use any jQuery plugin, I should be putting that into a directive than in the controller. So in the directive:
assignToFirmApp.directive('inputMaskTime', function() {
return {
require: "ngModel",
link: function (scope, elem, attr, ctrl) {
$(elem).inputmask("hh:mm:ss", {placeholder: "HH:MM:SS", insertMode: false, showMaskOnHover: false});
elem.on('keyup', function ()
{
scope.$apply(function()
{
ctrl.$setViewValue(elem.val());
});
});
}
};
});
And in the page:
<input class="audioStartTime" input-mask-time="true" type="text" ng-model="ol.AudioStartTime"/>
I'm using Angular's ng-repeat to generate a table with multiple rows. I want to make specific table cells animate when a user mouseovers the table row.
In this example case below, I only want to make the corresponding animated cells be visible (or opacity:1) when the row is mouseovered, but I don't want the the rows to change height (i.e. the row height should account for non-visible cell data).
I have tried CSS animation and ng-animate, but all of my attempts animate all of the rows' corresponding cells (e.g., in a multi-row table where the second column is animated, all cells in the second column will respond when the mouse is over any part of the table).
Full example available in jsBin includes both Greensock TweenMax and css animation attempts.
Relevant html (in this version, only the 2nd-column/red cells change visibility/opacity):
<table class="view-container">
<tr ng-repeat="row in ctrl.rows"
ng-click="fadeIt($index)"
id={{$index}}>
<td>index #{{($index)}}</td>
<td class="animation red" hide-me="isHidden")>red background</td>
<td class="animation blue">blue backgrounds</td>
</tr>
</table>
Relevant js (using TweenMax)
var app = angular.module('app', ['ngAnimate']);
app.controller('MainCtrl', ['$scope', function ($scope) {
$scope.isHidden = false;
$scope.fadeIt = function(id) {
$scope.isHidden = !$scope.isHidden;
};
}]);
app.directive("hideMe", function ($animate) {
return function (scope, element, attrs) {
scope.$watch(attrs.hideMe, function (newVal) {
if (newVal) {
$animate.addClass(element, "fade");
} else {
$animate.removeClass(element, "fade");
}
});
};
});
app.animation(".fade", function () {
return {
addClass: function (element, className) {
TweenMax.to(element, 1, {opacity: 0});
},
removeClass: function(element, className) {
TweenMax.to(element, 1, {opacity: 1});
}
};
});
So I figured out how to do most of what I wanted.
The keys were to:
include display:block!important; inside my .animation class
place a content <div></div> within each <td></td>
place all styling on the interior <div> instead of on the <td>, e.g.
<td> <!-- no styles on the <td>; nothing between the <td> and <div> -->
<div class="animation" ng-show="showDiv">
<!-- all table data goes inside this div -->
</div>
</td>
My working example is available here - http://jsbin.com/wufehu/5/edit?html,css,output
I have a table that allows the column headers to be re-ordered by dragging and dropping. The column headers are bound to an angular model, except the last column that is always fixed. I am using jquery UI dragtable to handle the drag/drop aspect (wired up using a simple custom directive).
This worked well enough in Angular 1.0.8, but after upgrading to 1.2.2 sometimes the last fixed column moves to the middle of the table, and the angular model gets out-of-sync with the view.
Working jsfiddle using 1.0.8: http://jsfiddle.net/Lw5dG/
Broken jsfiddle using 1.2.2: http://jsfiddle.net/Lw5dG/2 (drag col3 all the way to the left to recreate and note that the UL element below the table shows the wrong column order)
(both contain exactly the same code, the only difference is the version of angular used)
View
<div ng-app="myapp" ng-controller="TestCtrl">
<table id="tab1">
<tr>
<th class="accept" drag-table ng-repeat="col in model.cols">{{col}}</th>
<th class="static">STATIC</th>
</tr>
</table>
<ul>
<li ng-repeat="col in model.cols">{{col}}</li>
</ul>
</div>
JS
var myapp = angular.module('myapp', []);
myapp.controller('TestCtrl', function ($scope) {
$scope.model = {};
$scope.model.cols = ['Col1', 'Col2', 'Col3'];
$scope.dragTableDone = function(){
$scope.model.cols = [];
$('#tab1 th:not(.static)').each(function () {
console.log('pushing ' + $(this).text());
$scope.model.cols.push($(this).text());
});
$scope.$apply();
};
});
myapp.directive('dragTable', function() {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.$watch('$last', function (v) {
if (v) {
$(element).closest('table').dragtable({ maxMovingRows: 1, beforeStop:scope.$parent.dragTableDone, dragaccept: '.accept' });
}
});
}
};
});
$(function() {
$('table').dragtable();
});
I appreciate that I may not be doing this the angular way, so if you can suggest a better approach please let me know.