I am dynamically building a table based on what SQL returns me. In the example below, I can fill a table with the start time, end time and user with no issue:
var StartTime = document.createElement('td');
var EndTime = document.createElement('td');
var InUser = document.createElement('td');
StartTime.innerText = value[i].startTime;
EndTime.innerText = value[i].endTime;
InUser.innerText = value[i].inUser;
The output will be a table with the number of entries I have received from SQL. What I would like to do now is create another td element to add to the last column of the table. This will be a button that will allow me to show and hide certain rows. For some reason, the ng-click functionality does not work when I click the button. Below is how I create the td element and how I apply the button to it.
var ViewComments = document.createElement('td');
ViewComments.innerHTML = '<button type="button" ng-click="showHideComments()" class="r-primary-button">View Comments</button>';//apply click functionality.
When Ctrl + Shift + C clicking on the table to view the elements on the page, I can see that the row has the exact values that I specified (i.e. the type, ng-click and class). The class property is working because I have the background of the button set to blue when highlighted which gives the "clicking effect." I am not sure why the ng-click functionality is not working because I have used ng-click before in the application where buttons are not dynamically created. I'm assuming this the issue? If so, is there a way to get the ng-click functionality to work on dynamically create buttons?
I see in this stackoverflow that they are using $compile:
ng-click not working from dynamically generated HTML
Is this the only way to solve this problem?
Any help would be appreciated!
Why aren't you using angular utilities? This seems like a lot of work while it can be easily done with angular.
<table ng-if="values">
<tr ng-repeat="value in values">
<td>{{value.startTime}}</td>
<td>{{value.endTime}}</td>
<td>{{value.inUser}}</td>
<td><button type="button" ng-click="showHideComments()" class="r-primary-button">View Comments</button></td>
</tr>
</table>
Inside your controller
$http.get(someUrl).then(function(data){
$scope.values = data;
});
$scope.showHideComments = function() {
// do your thing here
};
Yes go with compile.
As the doc says that
Compiles an HTML string or DOM into a template and produces a template function, which can then be used to link scope and the template together.
You can create a reusable directive which will take the dynamic string and compiles that.
An example
<div ng-controller="Controller">
<element-create message='htmlString'></element-create>
</div>
Directive
angular.module("CompileDirective", [])
.directive('elementCreate', ['$compile', function ($compile) {
return {
restrict: 'E',
scope: {
message: "="
},
replace: true,
link: function(scope, element, attrs) {
var template = $compile(scope.message)(scope);
element.replaceWith(template);
},
controller: ['$scope', function($scope) {
...
}]
}
}])
Related
I want to dynamically create a button for each paragraph that is created inside a contenteditable div. I've been thinking a lot and can't come up with a good solution. The things I've thought about are
putting the button and paragraph together into one directive and have the content editable add a new <button+p> tag each time the user hits return. This has the benefit of having both the button and the paragraph use the same controller, but it leaves the button in the content editable div so it can be deleted...
Use the Model to maintain an array of all paragraphs in the div, then create buttons for each of the paragraphs in this array. My question here is: if I update the model with new paragraphs, will the buttons automatically be generated? If I use ng-repeat?
I'm kind of at a loss of the best way to approach this. Should I try to build the button and the paragraph together? Or is there a better way of separating them but binding them together so that when the button is clicked I can change the styling of the paragraph?
Create a directive and associate it to your div.
Ex:
Define as binding a parameter with two way data binding, the ones that will keep track of the p elements created inside the div and that will be passed from the the controller associated to your view.
Inject inside your link function of the directive the $element.
Then bind to the div with contenteditable the input event in order to detect edits in the div.
Inside this code get the total number of p children of your div, and associate it to the variable allowed from the directive.
In this way your parameter is always sync with the number of p inside your div, and it can be accessed from outside scopes because you pass it from outside.
Then inside your view, use a ng-repeat iterating over this parameter you passed in the directive, and create your dynamic content inside the ng-repeat.
HTML Code:
<div ng-app="myApp">
<div ng-controller="Controller">
<div contenteditable="true" p-inspector p-elements="pElementsNumber">
TEST
</div>
{{pElementsNumber}}
<div ng-repeat="p in returnArrayFromNumber() track by $index">
P detected
</div>
</div>
</div>
Here the JS code:
angular.module('myApp', [])
.controller('Controller', ['$scope', function($scope) {
$scope.pElementsNumber = 0;
$scope.returnArrayFromNumber = function () {
return new Array($scope.pElementsNumber);
};
}])
.directive('pInspector', function($rootScope) {
return {
restrict: 'A',
scope: {
pElements: '='
},
link: function ($scope, $element, $attrs) {
$element.on("input", function(e) {
var htmlString = $element.text();
var regex = /<p>[^<p><\/p>]*<\/p>/gi, result, count = 0;
var count = 0;
while ( (result = regex.exec(htmlString)) ) {
count++;
}
$scope.pElements = count;
$rootScope.$apply();
});
}
};
});
Here the running example: https://jsfiddle.net/a0jwmpy4/81/
Just one recommendation: if you want to detect more elements, make this directive dynamic accepting the name of the elements in the parameters and detecting all of them. Please do not create a single directive for every element you want to detect inside the div :)
Hope this helps
Have you tried to use ng-repeat for each paragraph/modal then set all your code in each repeat something like below
<div>
<p ng-repeat="paragraph in paragraphs"> {{contentsOfParagraph}} <button ng-click="editParagraph(MayBeIDOfParagraph)">Edit</button></p>
</div>
now your js code will have a function editParagraph that pass the ParagraphID
I am relatively new to Angular and this my first attempt at using a custom directive. I have a table I am working with and each cell has a data attribute. For example, "data-dept="service", and I would like to access this value before overwriting it with a directive. In my directive, I have set template = true, and this removes my data attribute.
I liked my website that I am working to better explain what I am attempting to do.
http://partnerportal-preprod.automate-webservices.com/list/#/hoursTable
The cells inside the table in the first row are clickable, but I would like to know if the user is clicking on service for example.
Update
I created plunker to better illustrate my problem.
I have a basic table that I am using a directive to replace a row in the table.
In doing so, I lose the attribute that I would need to set it as a key in my object for later use.
http://plnkr.co/edit/oXRM6lRkidnAHfBA4GsR?p=preview
HTML
<tr>
<td name="valueIneed" hello-world>John</td>
<td>Doe</td>
<td>john#example.com</td>
</tr>
Directive
app.directive('helloWorld', function($parse) {
return {
template: '<td><input type="text"></td>',
replace: true,
transclude: 'true',
scope: {
position: '='
},
link: function (scope,element,attrs,ngModel) {
console.log(attrs.attribute);
scope.clickMe = function () {
console.log('clicked');
scope.isChecked = true;
};
}
};
});
First remove replace: true.
Second to see the value of the name attribute:
console.log(attrs.name);
By using replace: true, the directive was replacing the element that had the name attribute.
replace:true is Deprecated1
From the Docs:
replace ([DEPRECATED!], will be removed in next major release - i.e. v2.0)
specify what the template should replace. Defaults to false.
true - the template will replace the directive's element.
false - the template will replace the contents of the directive's element.
-- AngularJS Comprehensive Directive API
From GitHub:
Caitp-- It's deprecated because there are known, very silly problems with replace: true, a number of which can't really be fixed in a reasonable fashion. If you're careful and avoid these problems, then more power to you, but for the benefit of new users, it's easier to just tell them "this will give you a headache, don't do it".
-- AngularJS Issue #7636
with this Angular directive every time my model changes, new HTML item appended to the page:
app.directive('helloWorld', function($compile) {
return {
restrict: 'AE',
replace: true,
scope:{
arrayItem: '=ngModel'
},
link: function ($scope, ele, attrs) {
$scope.$watch( 'ngModel' , function(){
ele.html('<div ng-click="sendLike({{arrayItem.data.timeline_content}})" class="timeline-item"> Hello {{arrayItem2.data.timeline_content}} </div>');
$compile(ele.contents())($scope);
});
}
};
});
And this is HTML view:
<hello-world ng-repeat="arrayItem in arr" ng-model="arrayItem"></hello-world>
But ng-click inside dynamically generated HTML doesn't work. actually recompiling of this new added section does not works.
UPDATE:
this is what i want to achieve:
in fact i want to create a chat Application. messages stored inside an Array and i have to bind that array to the HTML view. if i click on every message, i need to an alert() fired inside the controller. my controller is like this:
app.controller("timelineCtrl", function ($scope) {
$scope.arr={};
$scope.sendLike = function (id) {
alert(id);
};
.
.
.
}
in the jQuery way, i simply use DOM manipulation methods and add new tags for each message but in Angular way i have to bind that array as a ng-model or something like that.
at first glance, i realize that designing a directive should be a good idea and inside module i can access to main scope and do needed thing with that directive and i expect that changes inside that directive should projected to HTML view but it fails and things like ng-click doesn't work for dynamically created tags.
There are two ways that you could accomplish this, with or without a directive.
Let's start without a directive; we'll assume that you have an array in the controller.
<div ng-controller="timelineCtrl" class="timelineframe">
<div ng-repeat="post in timeline | orderBy:'-lineNumber'" class="post">
<div ng-click="sendAlert(post)">
<span class="postnumber">{{::post.lineNumber}}:</span>
<span class="message">{{::post.message}}</span>
</div>
</div>
</div>
whenever an object is added to $scope.timeline, it can be assigned a lineNumber, and we can use angular OrderBy to sort the direction in reverse lineNumber order (using -). The $scope.sendAlert(post) will send the specific post to the function. in our bindings, we use :: to indicate that these are one time bindings, i.e. not values that need to be monitored independently of the array. This can improve performance on large lists.
using a Directive, we can accomplish this in a very similar manner, by making a Directive that renders a specific post, and passing the post in as a property.
app.directive('timelinePost', function() {
return {
restrict: 'AE',
scope:{
post: '='
},
template: '<div ng-click="postCtrl.sendAlert()">
<span class="postnumber">{{::postCtrl.post.lineNumber}}:</span>
<span class="message">{{::postCtrl.post.message}}</span>
</div>',
controller: 'postController',
controllerAs: 'postCtrl',
bindToController: true
};
app.controller("postController", function(){
var self = this; //save reference to this
self.sendAlert = function(){
//the specific post is available here as self.post, due to bindToController
};
};
//usage in HTML:
<div ng-controller="timelineCtrl" class="timelineframe">
<div ng-repeat="post in timeline | orderBy:'-lineNumber'" class="post">
<timeline-post post='post'></timeline-post>
</div>
</div>
you could further wrap this timeline in a directive in a similar manner, if you so desired. Either of these will accomplish the same task, of looping through your data, ordering it so that the newest post is at the top, and updating whenever the array changes. In the non-directive method, the timelineCtrl handles the $scope.sendAlert function; in the directive method, it is handled by the directive controller postController.
Please note, this is a rough draft based on what you have described and the information from various posts over the last 2 days. I haven't created a dataset to iterate through to test thoroughly, but the logic here should get you started.
I am new to angularJS. I want to create checkboxes dynamically using templates in directives. I created controller and directives in separate files. I am creating checkbox in template in directive and want to invoke controller's function on ng-click of check box but I am unable to do so.
Here is my code sample.
Controller:
var app=angular.module('abc',[]);
app.controller('DemoCtrl', function($scope) {
$scope.ctrlFn = function(test) {
alert("hi "+test);
console.log(test);
}
});
I referred the https://github.com/iVantage/angular-ivh-treeview to create checkboxes tree view. I inlcuded all the css and js files in my sample. From the link I got the following js file which is creating the checkboxes in template in directive as shown below:
ivh-treeview.min.js:
angular.module("ivh.treeview",[]),
angular.module("ivh.treeview").directive("ivhTreeviewCheckbox",[function(){
"use strict";
return{restrict:"A",
scope:{node:"=ivhTreeviewCheckbox"},
require:"^ivhTreeview",
link:function(a,b,c,d){
var e=a.node,f=d.opts(),g=f.indeterminateAttribute,h=f.selectedAttribute;
a.isSelected=e[h],
a.ctrl=d,
a.$watch(function(){return e[h]},function(b){a.isSelected=b}),
a.$watch(function(){return e[g]},function(a){b.find("input").prop("indeterminate",a)})},
template:['<input type="checkbox"','ng-model="isSelected"','ng-change="ctrl.select(node, isSelected)" />'].join("\n")}
}]);
View:
<div class="col-sm-8" ng-controller="DemoCtrl as demo">
<div ivh-treeview="demo.bag"
ivh-treeview-selected-attribute="'isSelected'"
ivh-treeview-id-attribute="'uuid'"
ivh-treeview-expand-to-depth="0">
</div>
</div>
I want to call ctrlFn() on click of checkbox created in directive template. Please suggest a way to do the same.
Thanks In Advance
Sounds like you are looking for a two-way binding between directive and parent $scope.
// parent controller scope
$scope.person = { name:'coldstar', class:'sexy beast' };
// directive declaration
<div a-person='person'></div>
// directive code
scope: {
// the = sign is the key. could also be a function instead of object
innerPerson: "=aPerson"
},
link: function (scope, elm, attr){
// now this change will be reflect in the parent controller also
scope.innerPerson.name = "not coldstar anymore";
}
Edit:
I also noticed "'isSelected'" which should be "isSelected" if isSelected is a $scope.* entity
I am very new to angular js. I want to create an input box on click of particular div. Here I need to create element on div which repeating.
<div><div ng-repeat ng-click="create();"></div><div>
What will be the best way to do so?
DOM manipulation in Angular is done via directives (There is paragraph on 'Creating a Directive that Manipulates the DOM' here)
First, read through this excellent article: How do i think in Angular if i have a jQuery background
The Angular Team also provides a pretty neat tutorial, which definetly is worth a look: http://docs.angularjs.org/tutorial
While Angular is pretty easy and fun to use once you have wrapped your head around the concepts, it can be quite overwhelming to dive into the cold. Start slow and do not try to use each and every feature from the beginning. Read a lot.
I strongly recommend egghead.io as a learning resource. The video-tutorials there are bite-sized and easy to watch and understand. A great place for both beginners and intermediates. Start from the bottom here.
Some folks have done great things with Angular. Take a look at http://builtwith.angularjs.org/ and check out some source code.
Use an array and ng-repeat to do that. Have a look at the following code.
I crated scope variable as an empty array. Then created a function to add values to that array.
app.controller('MainCtrl', function($scope) {
$scope.inputFields = [];
$scope.count = 0;
$scope.addField = function(){
$scope.inputFields.push({name:"inputText"+$scope.count++});
}
});
I used ng-repeat with this array. and called the function on the click event of a div.
<div ng-click="addField()">Click here to add</div>
<div ng-repeat="inputField in inputFields">
<input type="text" name="inputField.name">
</div>
Check this working link
Update - Show only one text box on click
I created addField() as follows.
$scope.addField = function(){
$scope.newTextField = "<input type='text' name='myTxt'>";
}
To render this html in my view file I created a new directive called compile as follows.
app.directive('compile', function($compile) {
// directive factory creates a link function
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
// watch the 'compile' expression for changes
return scope.$eval(attrs.compile);
},
function(value) {
// when the 'compile' expression changes
// assign it into the current DOM
element.html(value);
// compile the new DOM and link it to the current
// scope.
// NOTE: we only compile .childNodes so that
// we don't get into infinite loop compiling ourselves
$compile(element.contents())(scope);
}
);
};
});
Then used this directive in my view.html file
<body ng-controller="MainCtrl">
<div ng-click="addField()">Click to Add</div>
<div compile="newTextField"></div>
</body>
click here to view the working link