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"/>
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 am not sure if I am doing this correctly, but I am trying to convert something was in a controller to a directive because I want to use it multiple times and just change a few values, so instead of making many huge object literals, I will have just one and just change the values passed in. I am trying to bind chartConfig, but it doesn't seem to be working. Am I doing this wrong?
Here is my directive:
app.directive('percentageSquare', function(){
return {
restrict: 'E',
scope: {
bgClass: '#',
percentage: '#',
chartConfig: '='
},
link: function(scope){
var fontSize = 80;
var percentage = scope.percentage || 0;
scope.chartConfig = {
options: {
chart: {
// Chart settings here
}
}
};
},
templateUrl: '/templates/dashboard/charts/PercentageChart.html'
};
});
Here is the template that the directive is using (PercentageChart.html):
<div class="drop-shadow">
<div class="row">
<div class="col-sm-12">
<highchart config="chartConfig" class="{{bgClass||''}}" ng-show="true"></highchart>
</div>
</div>
</div>
Here is how I am calling the directive:
<percentage-square bg-class="bg-orange" percentage="23"></percentage-square>
Now my chartConfig no longer binds to the directive like it used to when it was in a controller. What can be done to fix this?
Edit
I have gotten a little further, this seems to work:
scope.$watch(scope.chartConfig, function(){
scope.chartConfig = {
// Chart Settings
};
});
But it seems to load the chart twice, as I get two animations.
Looks like what I want to do is watch chartConfig. it must also be wrapped in a string in order for it to work properly. Using scope.chartConfig loaded the chart twice, while 'chartConfig' loads the chart once and properly.
scope.$watch('chartConfig', function(){
scope.chartConfig = {
// Chart Settings
};
});
I have a directive whose data is being received via an api call. The directive itself works fine, the problem arises (I believe) because the directive is loaded before the api call finishes. This results in the whole shebang just not working. Instead of my expected output, I just get {{user}}.
My directive looks like this:
app.directive('myDirective', function() {
return {
restrict: 'A',
require: '^ngModel',
scope: {
ngModel: '=',
},
template: '<tbody style="background-color: red;" ng-bind-html="renderHtml(listing_html)"></tbody>',
controller: ['$scope', '$http', '$sce',
function($scope, $http, $sce) {
$scope.listing_html += "<td>{{user.name}}</td>"
$scope.renderHtml = function(html_code) {
return $sce.trustAsHtml(html_code);
};
}
],
link: function(scope, iElement, iAttrs, ctrl) {
scope.$watch('ngModel', function(newVal) {
// This *is* firing after the data arrives, but even then the
// {{user}} object is populated. And the `user in ngModel` doesn't
// run correctly either.
console.log(scope.ngModel);
scope.listing_html = "<tr ng-repeat='user in ngModel'><td>{{user}}</td></tr>"
})
}
};
});
And my html is simply
<table my-directive my-options='{"Name": "name", "Email": "email"}' ng-model='userData'></table>
I've created a plunker with a ton of comments to hopefully help explain the issue.
This question is very similar to this one, with the key distinction of that solution not working. Adding ng-cloak to mine just makes it not display.
It may also be worth noting that I've been using this as reference on the way to construct a directive.
I think you're making this a bit more complicated that it needs to be. If you're going to try to insert dynamic HTML with Angular expressions in them, you need to use the $compile service to compile them first (this hooks up the directives, etc, in that dynamic HTML to Angular). With that said, I don't think you need to do that for what you're trying to accomplish.
Take a look at this updated plunk: http://plnkr.co/edit/RWcwIhlv3dMbjln4dOyb?p=preview
You can use the template in the directive to produce the dynamic changes you need. In my example, I've used ng-repeat to repeat over the users provided to the directive, and also to the options provided to the directive. ng-repeat does the watching, so as soon as the data provided to the directive via ng-model is updated, the ng-repeats reflect those changes.
<tbody style="background-color: red;">
<tr><th ng-repeat="option in myOptions">{{option.name}}</th></tr>
<tr ng-repeat="user in ngModel">
<td ng-repeat="option in myOptions">{{user[option.value]}}</td>
</tr>
</tbody>
The options I defined in the main controller like this.
$scope.tableOptions = [
{"name": "Name", "value": "name"},
{"name": "Email", "value": "email"}
];
You could add other properties to this that are used by the directive, such as display order, etc. You could even remove an item from the options dynamically and that data would then be removed from the output table.
Let me know if this helps, or if I've misunderstood what you were trying to accomplish.
I am not 100% sure, but I believe that ngBindHtml will not help you in this case.
ngBindHtml is for displaying some "normal" HTML, but you want to display some Angular, magic HTML.
For that you need to $compile the HTML to something that is Angular-aware and link the compiled HTML to a scope.
I used the following approach (with apparently good results):
controller: function ($scope, $element, $compile) {
var html = createTmpl(angular.fromJson($scope.myOptions));
$scope.$watch('ngModel', function (newVal) {
var elem = angular.element(html); // Creating element
var linkingFn = $compile(elem); // Compiling element
linkingFn($scope); // Linking element
$element.html(''); // Removing previous content
$element.append(elem); // Inserting new content
// The above is purposedly explicit to highlight what is
// going on. It's moe concise equivalent would be:
//$element.html('').append($compile(html)($scope));
});
where createTmpl() is defined to take into account myOptions and return the appropriate template for creating a table with a header-row (based on the keys of myOptions) and data-rows with the properties defined as myOptions's values:
function createTmpl(options) {
// Construct the header-row
var html = '<tr>';
angular.forEach(options, function (value, key) {
html += '<th>' + key + '</th>';
});
html += '</tr>\n';
// Construct the data-rows
html += '<tr ng-repeat="user in ngModel">';
angular.forEach(options, function (value, key) {
html += '<td>{{user' + value + '}}</td>';
});
html += '</tr>\n';
// Return the template
return html;
}
See, also, this short demo.
Of course, this is for demonstration purposes only and does not handle everything a production-ready app should (e.g. accounting for errors, missing properties, changes in myOptions and whatnot).
UPDATE:
I had very strong competion, so I did a slight modification of the code above in order to support nested properties. E.g. given an object with the following structure:
user = {
name: 'ExpertSystem',
company: {
name: 'ExpertSystem S.A.',
ranking: 100
}
};
we can have the company name displayed in a column of our table, just by defining myOptions like this:
myOptions='{"Company name": "company.name"}
In my highest level scope, I fetch some data from the server for use throughout the page like this: $scope.lotsOfData = $http.get("lotsOfData");. So now my scope holds a promise object for lotsOfData. Then, in my HTML, I have directives that are only concerned with a small set of the lotsOfData object. One such directive could look something like this:
<div>
{{lotsOfData.foo.blah[source].bar[id].someData}}<br>
{{lotsOfData.foo.blah[source].bar[id].otherData}}<br>
{{lotsOfData.foo.blah[source].bar[id].differentData}}
</div>
where source and id are being set through attributes on the directive. My HTML page then looks something like this:
<data-subset source="1" id="1" />
<data-subset source="1" id="2" />
<data-subset source="2" id="1" />
<data-subset source="3" id="1" />
I hate having to repeat lotsOfData.foo.blah[source].bar[id] throughout the directive. Is there any way to set in the scope so my directive could look more like this?
<div>
{{currObj.someData}}<br>
{{currObj.otherData}}<br>
{{currObj.differentData}}
</div>
Not only does this clean up the HTML, but if we ever restructure that lotsOfData object, there's be only one place to change how it's getting the currObj object. In the link function for my directive I tried this:
link: function(scope, element, attrs) {
scope.currObj = scope.lotsOfData.foo.blah[attrs.source].bar[attrs.id];
}
However, since lotsOfData is a promise object, it doesn't have a property called foo. I don't know a whole lot about how the promise object works, so maybe I just need to know how I can get to the properties I need.
I hope what I'm trying to accomplish here makes sense and someone could point me in the right direction as to how to make this work. Thanks.
link: function(scope, element, attrs) {
scope.$watch('lotsOfData.foo.blah['+attrs.source+'].bar['+attrs.id+']', function(newVal, oldVal) {
scope.currObj = newVal;
}
}
This should fix the problem, you will simply watch for changes on your data and set the currObj with the new value.
This is how I accomplished what I wanted to. I created an outer directive to the one mentioned above and then created an isolated scope for the inner directive that bound to the data I wanted.
So my outer directive HTML looks like this:
<div>
<data-subset curr-obj="lotsOfData.foo.blah[source].bar[id]"></data-subset>
</div>
And the javascript looks like this:
myModule.directive('outer-data', function() {
return {
restrict: 'E',
replace: true,
templateUrl: 'outerData.html',
link: function(scope, element, attrs) {
scope.id = attrs.id;
scope.source = attrs.source;
},
scope: true
}
});
Then the javscript for my dataSubset looks like this:
myModule.directive('dataSubset', function() {
return {
restrict: 'E',
replace: true,
templateUrl: 'dataSubset.htm',
scope: {
currObj: '='
}
}
});
So then in my top-level HTML file I have something like this:
<outer-data source="1" id="1" />
<outer-data source="1" id="2" />
<outer-data source="2" id="1" />
<outer-data source="3" id="1" />
And I can just reference currObj on my dataSubset like I wanted.