Updating the scope through directive. Best practices - javascript

I am struggling with the concept of scope while working with a directive. I read through the offical docs as well as half a dozen blogs and stuff, but i am not able to get this done.
I have 5 fields for text input. What i want to achive is, if one field is filled, the next sibling shall appear:
<table>
<tr ng-repeat="entry in activeField.entries">
<td entry-value contenteditable="">{{entry.value_0}}</td>
<td entry-value contenteditable="" ng-show="entry.value_0">{{entry.value_1}}</td>
<td entry-value contenteditable="" ng-show="entry.value_1">{{entry.value_2}}</td>
<td entry-value contenteditable="" ng-show="entry.value_2">{{entry.value_3}}</td>
<td entry-value contenteditable="" ng-show="entry.value_3">{{entry.value_4}}</td>
</tr>
</table>
This is my directive entryValue
terminal.directive('entryValue',function(){
return{
restrict: 'A',
controller: 'terminalController',
link: function(scope, element){
element.bind('input',function(){
// Here i want to update the scope
})
}
}
});
As the user makes an input, i want to push that value to my controllers scope, which contains the object activeField. I have tried to bind $watch to my directives linking function as well as scope.$apply() but neither works.
I'd appreciate any help

<tr ng-repeat="entry in activeField.entries">
<td entry-value contenteditable=""
ng-hide="!$first && !activeField.entries[$index-1].value">{{entry.value}}</td>
</tr>
app.directive('entryValue',function(){
return{
replace: true,
controller: 'terminalController',
template: '<div><input ng-model="entry.value"></div>',
link: function(scope, element){
}
}
});
$scope.activeField = {entries:
[ {value: ''}, {value: ''}, {value: ''}, {value: ''}, {value: ''} ] };
fiddle

Related

Div not showing on ng-click of element returned by custom directive

I have created a directive in which a table html is returned successfully. One of the columns is anchor link <td><a ng-click="showLogDiv()">Show Modified Data</a></td> on whose click i want to show a div containing further data belonging to that row but it doesnot show.
//logdetails.html - the templateUrl proprerty of my directive
<div>
<table class="table table-hover table-responsive">
<thead class="table-thead">
<tr style="background-color:#56a7d6">
<th>AccessLogId</th>
<th>EntityName</th>
<th>EntityId</th>
<th>RequestType</th>
<th>Modified Data</th>
<th>Creation Date</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="value in viewData.logdata">
<td>{{value.AccessLogId}}</td>
<td>{{value.EntityName}}</td>
<td>{{value.EntityId}}</td>
<td>{{value.RequestType}}</td>
<!--<td><a ng-click="showLogDetails({{value.ModifiedData| json}})">Show Modified Data</a></td>-->
<td><a ng-click="showLogDiv()">Show Modified Data</a></td>
<td>{{value.CreatedDate | date:'medium'}}</td>
</tr>
</tbody>
</table>
<!--<div ng-show="divShow">Hello</div> I want to show {{value.ModifiedData| json}} contents here but even hardcoded Hello value not shown -->
<div ng-show="divShow">Hello</div>
</div>
In controller i have
$scope.divShow = false;
$scope.showLogDiv = function () {
alert($scope.divShow);
$scope.divShow = true;
alert($scope.divShow);
};
My directive
.directive("myActivityLogs", function () {
return {
restrict: 'E',
replace: 'true',
//template: '<div></div>',
//template: '<b>{{viewData.logdata[1].ModifiedData}}</b>'
templateUrl: 'app/modules/appScreen/logdetails.html'
//scope: {
// logsData:'='
//},
//link: function (scope, element, attrs) {
//link: function () {
// alert(viewData.logdata);
//}
};
});
How to hide/show part of html returned by directive and also how can i bind data to that part?
I am new to angularjs and nothing makes sense right now so maybe i am doing things wrong way so please explain in detail it would be very helpful.
One way to do this is to use a directive controller. You can change your directive like this:
.directive("myDirective", function() {
return {
restrict: 'E',
replace: 'true',
templateUrl: './logdetails.html',
scope: {
viewData: '='
},
controller: function($scope) {
$scope.divShow = false;
this.showLogDiv = function() {
$scope.divShow = true;
};
},
controllerAs: 'ctrl'
};
})
And then change your template HTML as the following so that it uses the controller:
<div>
<table class="table table-hover table-responsive">
<thead class="table-thead">
<tr style="background-color:#56a7d6">
<th>AccessLogId</th>
<th>EntityName</th>
<th>EntityId</th>
<th>RequestType</th>
<th>Modified Data</th>
<th>Creation Date</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="value in viewData.logdata">
<td>{{value.AccessLogId}}</td>
<td>{{value.EntityName}}</td>
<td>{{value.EntityId}}</td>
<td>{{value.RequestType}}</td>
<td><a href ng-click="ctrl.showLogDiv()">Show Modified Data</a></td>
<td>{{value.CreatedDate | date:'medium'}}</td>
</tr>
</tbody>
</table>
<div ng-show="divShow">Hello</div>
</div>
Notice that I've used <a href ng-click="ctrl.showLogDiv()">. You can refer to this working plunker to know more.

angular (jquery timepicker) unable to get value of input

I want to make a custom directive with the jquery plugin timepicker. I'm not getting the input value in the console, it says undefined.
here's a plunkr
<table class="table table-bordered">
<thead>
<tr>
<th>Time From</th>
<th>Time To</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<input type="text" ng-model="row1" size=6/ disabled>
</td>
<td>
<input type="text" ng-model="dup_row1 " size=6 timepicki/>
{{dup_row1}}
</td>
</tr>
</tbody>
</table>
var app = angular.module('myApp', []);
app.directive('timepicki', [
function() {
var link;
link = function(scope, element, attr, ngModel) {
element.timepicki();
};
return {
restrict: 'A',
link: link,
require: 'ngModel'
};
}
])
app.controller('ctrl', function($scope) {
$scope.row1 = "00:00"
$scope.submit=function(){
console.log($scope.dup_row1)
}
});
The code you've posted is not the same as the code in your plunker.
The AngularJS Developer Guide says;
Use controllers to:
Set up the initial state of the $scope object.
In your example above, you log the value of $scope.dup_row1 on submit, but your controller never sets that value, and as such it is undefined.
The following will print "hello" to the console;
app.controller('ctrl', function($scope) {
$scope.row1 = "00:00"
$scope.dup_row1 = "hello"
$scope.submit=function(){
console.log($scope.dup_row1)
}
});

ng-repeat and custom directive issue in thead

I am trying to move our thead generation to a directive, but when using this, the headers lose their styling and end up bunching up on the left. Can anyone offer some guidance as to what I'm doing wrong?
The headers appear like this:
Col1Col2Col3Col4Col5
Instead of this when I use the same html without a directive. Note that these are properly aligned above their data columns:
Col1 Col2 Col3 Col4 Col5
index.html
<table class="table table-hover">
<thead>
<table-headers></table-headers>
</thead>
...
...
</table>
directives.js
app.directive('tableHeaders', function() {
return {
restrict: 'E',
transclude: true,
replace: true,
priority: 1001,
templateUrl: '/PATH/TO/PARTIAL/table-headers.html'
};
});
table-headers.html
<tr>
<th ng-cloak="true" ng-repeat="header in coreHeaders" ng-click="setOrderBy(header)"> {{header}} <i class="fa" ng-show="header == ordering.currentHeader" ng-class="ordering.reverse ? 'fa-caret-up' : 'fa-caret-down'"></i></th>
<th class="align-right" ng-cloak="true" ng-repeat="header in statHeaders" ng-click="setOrderBy(header)">{{header}} <i class="fa" ng-show="header == ordering.currentHeader" ng-class="ordering.reverse ? 'fa-caret-up' : 'fa-caret-down'"></i></th>
</tr>
The only element you can have as a <thead>'s child is <tr>. Even if you have an Angular's directive which replaces it's original markup with proper tr elements, the browser will be faster ;) and will mangle your html before directive compilation so the results may be quite unexpected.
Try implementing table-headers as an attribute (restrict: 'A', and remove the replace: true option) instead. This way it will be a valid DOM tree from the start.

How to dynamically change the content of a table column

Given the following HTML fragment, how can I create the content of the td depending on the column.
<div ng-app="" ng-controller="controller" >
<table>
<tr>
<th ng-repeat="column in columns">
{{ column.header }}
</th>
<tr ng-repeat="row in rows">
<td ng-repeat="column in columns">
<!-- TODO -->
</td>
</tr>
</table>
</div>
Each column can show a different kinds of data. For example, one might just show a string, another might contain a text input field that is bound to a property of the row.
I would like to call a function on the column (column.createCell(row)) that creates that necessary HTML and then put the result in place of <!-- TODO -->.
In WPF, I would just put a ContentPresenter with a DataTemplateSelector, but I don't know what the equivalent is in Angular. I read about something called "ng-bind-html", is that the way to go?
It's not given what kind of custom element you want to build for each column, but for DOM manipulation in AngularJS best practise is to keep it in a directive. Something like this:
in your html:
<body ng-controller="MainCtrl">
<table>
<tr ng-repeat="row in rows">
<td ng-repeat="column in row">
<custom-column="column"></custom-column>
</td>
</tr>
</table>
</body>
app.js
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
// whatever you wanted to define here...
$scope.rows = ...
$scope.columns = ...
});
app.directive('customColumn', function() {
return {
scope: {
obj: '=customColumn',
},
link: function(scope, element, attrs) {
var watcher = scope.$watch('obj', function(obj) {
if (!obj) return;
// build custom element
var html = '<div>'+scope.obj.name+'</div>';
element.html(html);
// delete watch if you only need to draw once
watcher();
});
}
}
});

Transclude in Angular directive putting elements inside a single 'span'

Here is my directive:
myapp.directive('envtable', function () {
return {
restrict: 'E',
replace: true,
transclude: true,
template: '<table class="table" ng-transclude></table>'
};
});
This is how i use it in html (using bootstrap css)
<envtable>
<tr>
<td>OS</td>
<td>{{env.osName}}</td>
</tr>
<tr>
<td>OS Version</td>
<td>{{env.osVersion}}</td>
</tr>
</envtable>
However, the code generated looks like this in chrome:
<table class="table" ng-transclude=""><span class="ng-scope ng-binding">
OS
Windows 8
OS Version
6.2
</span></table>
As you can see, Angular just ignored all my tr/td tags and put the contents in a single span element. Why is this happening?
Btw, as an experiment, i tried using just a transcluded p tag in the envtable instead of the tr\td tags and in that case angular just adds a ng-scope class to the p tag. So why does it screw up these tr/td tags?
It turns out this works with restrict: 'A'
<table envtable>
<tr>
<td>OS</td>
<td>{{env.osName}}</td>
</tr>
<tr>
<td>OS Version</td>
<td>{{env.osVersion}}</td>
</tr>
</table>
Demo
Just provide another example in case your table template has other elements like thead
Plunker
app.directive('envtable', function() {
return {
replace: true,
transclude: true,
template: '<table class="NewTable"><thead><th>Col1</th><th>Col2</th><th>Col3</th></thead></table>',
link: function(scope, elem, attrs, controller, transcludeFn) {
var item = transcludeFn(scope, function(clone) {
return clone.children();
});
elem.append(item);
}
};
});
<table envtable>
<tbody>
<tr ng-repeat='r in rows'>
<td>{{r.col1}}</td>
<td>{{r.col2}}</td>
<td>{{r.col3}}</td>
</tr>
</tbody>
</table>
I think this may be a repeat but your solution is simple. Avoid using <table>!
If you remove the <table> tags, replace them with <div>'s with display: table styling it should work just fine.

Categories