Create DOM elements and set a click event in AngularJS directive - javascript

how can I create in an AngularJS directive some DOM elements and set on them a click event? In my directive I create my elements in this way:
var list = document.createElement("div");
list.classList.add('myList');
for(var i = 0; i < n; i++) {
var item = document.createElement("div");
item.classList.add('myItem');
list.appendChild(item);
}
so I have an external div container that contains some div elements.
This is my generated HTML:
<div class="myList">
<div class="myItem">
<div class="myItem"></div>
<div class="myItem"></div>
<div class="myItem">
</div>
In the same directive I have to set a click event on those elements, in jQuery I can do:
$(".myItem" ).on( "click", function() {
// Do something
});
I try to that in Angular in many ways but I have problems to set the on click event:
var list = document.querySelector('.myList');
_.forEach(list.children, function(value, index){
var item = document.querySelector(value);
item.bind("click",function(){
// Do something
});
});
I get an error:
Failed to execute 'querySelector' on 'Document': '[object HTMLDivElement]' is not a valid selector.
Also, if I want get all myItem directly (without list.children) I write use:
var item = document.querySelector('.myItem');
I get:
item.bind is not a function (caused by "undefined")
I can set an ng-click in the directive... how?
item.on( "click", function() {
// Do something
});
If I use .on() method it's undefined like .bind().
Anyone can help me? Thanks in advice :)

I believe what you need is
<div class="myList">
<div class="myItem" ng-click="yourClickFn()">
<div class="myItem" ng-click="yourClickFn()"></div>
<div class="myItem" ng-click="yourClickFn()"></div>
<div class="myItem" ng-click="yourClickFn()">
Then in your Angular controller:
$scope.yourClickFn = function(){
//the code you want to execute here
}

Do you have a real reason to create your elements this way? It's really not the angular-way of doing things. You should use an ng-repeat on to create your html.
Also if you have a chance to update to anguar 1.5+ you could use components instead of directives, this would make your life easier.
Update
Alternatively you could do it with jQuery after the elements are created actually. I think it's easier to read than the plain js version.
Put a class on them and do something like:
$('.myClass').on('click', function() {
//your code
if (!$scope.$$phase) { // this checks whether you are already in a digest cycle or not - you probably won't be at this phase.
$scope.$apply(); // this will update the html if you did something to the model above this if
}
});
If you have a directive put this code to the link function, if you have a component then put it into the $postLink lifecycle hook (works after 1.5.3 I think) as these functions are called after your html has been already generated.
Usually these are the places for "messy" or "non-angular" code ^_^

Related

ng-click added by filter to image not working

I have data being added in through a json feed and that data goes through a filter to tidy it up / add some extra elements which all works fine. However I've added a new filter to find images and add ng-click to the markup to do full screen modal images, annoyingly the click event never fires.
.filter('html_filters', function ($sce) {
return function (text) {
var htmlObject = document.createElement('div');
htmlObject.innerHTML = text;
var imgs = htmlObject.getElementsByTagName('img');
for (var i = 0; i < imgs.length; i++) {
var link = imgs[i].getAttribute('src');
imgs[i].setAttribute('ng-click', 'loadImage("' + link + '")');
}
return $sce.trustAsHtml(htmlObject.outerHTML);
}
})
HTML:
<p class="postcon arrow-list" ng-if="content" ng-bind-html="content | html_filters"></p>
Rendered HTML:
<img class="aligncenter" src="http://www.sdssdsdsdsd.co.uk/connect/wp-content/uploads/2016/08/sdsdsd-August-20162-1.jpg" alt="Exhibit 1" ng-click="loadImage("http://www.sdssdsdsdsd.co.uk/connect/wp-content/uploads/2016/08/sdsdsd-August-20162-1.jpg")">
Click Event:
$scope.loadImage = function (url) {
console.log("Loading Image");
}
That log event never fires
DOM manipulation should be done inside a directive and not a filter. You can still use a filter inside a directive and leverage the compile phase which will cause your html to be rendered correctly with events and everything.
here is a good explanation of their difference and how to use them:
http://www.c-sharpcorner.com/UploadFile/cd7c2e/directive-and-filter-service-in-angularjs/
UPDATE:
Alright let's make this a bit simpler by looking at your problem from a different angel.
What you are trying to do is iterate over a list of objects you are getting from your json feed and create a bunch of html elements (p and img and ...).
I am going to write this using an ng-repeat (which is a directive right?):
<div ng-repeat="content in jsonList">
<p class="postcon arrow-list" ng-if="content" ng-bind="content.text"></p>
<img class="aligncenter" ng-src="content.url" alt="{{contnt.alt}}" ng-click="loadImage(content.url)">
</div>
So now we have our list of elements when the page is rendered. So what happens to your tidying up logic here. Now is the time to look at filters. Let's suppose you have a filter like below which gets your list and returns the tided up one:
app.filter('myFilter', function () {
return function (jsonList) {
var modifiedList = [];
//your logic goes here
return;
};
});
Now if you want to use this to modify your initial array before parsing the html you can use it like this (by adding it as a filter after your ng-repeat):
<div ng-repeat="content in jsonList | myFilter">
Hope this makes since.
BTW: I didn't want you to change your logic or anything. This is just to let you know the differences and where to use each of them.

Adding an element with directives in Angular

I am adding an element dynamically in Angular. The code is as follows.
myelement.after('<input ng-model="phone" id="phone">');
The element gets added and all works fine. However I think I'm missing a step and Angular does not recognize the new element because when I collect data later, the value of the dynamically added element is not defined.
The reason for adding the element on fly is that the number of inputs is not known from the beginning and user can add as many of them as they want. The actual code is like:
myelement.after('<input ng-model="phone"' + counter + ' id="phone' + counter + '">');
I'm sorry, I should have provided complete sample from the beginning. Please see the following jsfiddle, add some phones (with values) and list them: http://jsfiddle.net/kn47mLn6/4/
And please note that I'm not creating any new directive. I'm only using Angular standard directives, and I prefer not to create a custom directive for this work (unless required).
Assuming that you are adding element to the DOM using directive. You can use built in angular service $compile.
Firstly inject the $compile service to your directive. Then in your link function of the directive, get your myElement after which you want to append your element. Then create your element using angular.element(). Then compile it using $compile service and pass the scope in which you are in now.(Here this scope is the directive scope). After getting the compiled dom element you can just append it after your myElement.
here is an example about how you can add element dynamically from directive:
var elementToBeAdded = angular.element('<input ng-model="phone" id="phone">');
elementToBeAddedCompiled = $compile(elementToBeAdded)(scope);
myElement.append(elementToBeAddedCompiled);
If you add your element using $compile service in your directive, angular will recognize your dynamically added element.
I was trying to accomplish this without using directives, but it seems the best way (and probably the only proper way) of adding multiple elements to DOM in Angular is by defining a custom directive.
I found a very nice example here http://jsfiddle.net/ftfish/KyEr3/
HTML
<section ng-app="myApp" ng-controller="MainCtrl">
<addphone></addphone>
<div id="space-for-phones"></section>
</section>
JavaScript
var myApp = angular.module('myApp', []);
function MainCtrl($scope) {
$scope.count = 0;
}
//Directive that returns an element which adds buttons on click which show an alert on click
myApp.directive("addbuttonsbutton", function(){
return {
restrict: "E",
template: "<button addbuttons>Click to add buttons</button>"
}
});
//Directive for adding buttons on click that show an alert on click
myApp.directive("addbuttons", function($compile){
return function(scope, element, attrs){
element.bind("click", function(){
scope.count++;
angular.element(document.getElementById('space-for-buttons')).append($compile("<div><button class='btn btn-default' data-alert="+scope.count+">Show alert #"+scope.count+"</button></div>")(scope));
});
};
});
//Directive for showing an alert on click
myApp.directive("alert", function(){
return function(scope, element, attrs){
element.bind("click", function(){
console.log(attrs);
alert("This is alert #"+attrs.alert);
});
};
});
in the angular mind you should manipulate the dom from the JS code.
and use ng-* directive
So i don't know you code but i think you just need to do something like :
View
<button ng-click="showAction()">Show the phone input</button>
<input ng-show="showPhone" ng-model="phone" id="phone">
Controller
app.controller('ctrl', [function(){
$scope.showPhone = false;
$scope.showAction = function() {
$scope.showPhone = true;
};
}]);
Outside of Angular you would need to use the event handler to recognize new elements that are added dynamically. I don't see enough of your code to test, but here is an article that talks about this with $.on():
Jquery event handler not working on dynamic content
Here is a good article that will help with directives and may solve your problem by creating your own directive:
http://ruoyusun.com/2013/05/25/things-i-wish-i-were-told-about-angular-js.html#when-you-manipulate-dom-in-controller-write-directives

How to get td values from dynamically created table

I see a lot of similar questions but not one that directly targets my problem. The business logic of my problem is that I allow the user to open a jQuery Dialog where I create table loaded with a data from a database and when the user make a choise I load the selected data info fields from the main screen.
My current problem is with collecting the data from the <tr> which happens on button click. If it was a hard coded table I would just:
$(selector).on('click', function(){
var $item = $(this).closest("tr").find('td');
})
and then do something with $item however the table is created dynamically (from Ajax request) everytime the Ajax request is made the table is destroyed and recreated so basically I can't or at least I don't know a way to use some sort of selector to which to bind the event so I can reproduce the above code.
Instead in the dynamic table I have this:
<td><button onclick="getData();return false">Select</button>
The problems with this (at least how I see it) are two - first, the using of onclick inside HTML element. From what I know it's not a good practice and there are better alternatives and I would appreciate answer showing this. Also, even though I go with this code I'm yet unable to extract the text from each <td> in:
function getData() {
...
}
I tried several approaches including the one which was working with the static table and the binded event handler.
At the end here is a JS Fiddle example where I think I made it clear what I can and what I can not do, so you can refer to it.
Check this fiddle
$(selector).on('click', function(){
var $item = $(this).closest("tr").find('td');
})
Using the above code you are binding a direct event but the one which you want is delegated event
To use delegated event you should use like
$(document).on('click',selector, function(){
var $item = $(this).closest("tr").find('td');
})
so your final code will look something like
$(document).on('click','.get-data' ,function(){
var $item = $(this).closest("tr").find('td');
$.each($item, function(key, value){
alert($(value).text());
})
});
document can be anything which is parent to the table which is going to be created.
Dont forget to add the selector while adding a new table element
I had the same problem and solved it that way.
You can create your table with the database results like this:
for (var i = 0; i < results.length; i++) {
// create table row and append it to the table using JQuery
// next create a td element, append it to the created tr
// and attach a listener to it
$('<td/>').html(results[i].textProperty)
.appendTo($(tr))
.on('click', getData);
}
where getData() is your function.
You can pass arguments to your getData like this:
.on('click', {info: results[i].data}, getData);
Then you can access them in your function:
function getData(event) {
console.log(event.data.info);
}
Hope this helps!
Edit: This way you are creating a listener for each td. An optimization could be to create a listener for the whole class of td elements and to pass data to it via HTML attributes or text value like in the approved answer.
or you can use this pass object in getdata method
$('#build-table').on('click', function(){
$('#temp-table').append('<table><thead><tr><th>Select</th><th>Name</th> </tr></thead>' +
'<tbody><tr><td><button class onclick="getData(this);return false">Select</button></td><td>Name1</td></tr>' +
'<tbody><tr><td><button onclick="getData(this);return false">Select</button></td><td>Name2</td></tr>' +
'</tbody></table>')
});
function getData(ob) {
var $item = $(ob).closest("tr").find('td');
$.each($item, function(key, value){
alert($(value).text());
})
}

get the text of div using angularjs

i want to get the text of div using angularjs . I have this code
<div ng-click="update()" id="myform.value">Here </div>
where as my controller is something like this
var myapp= angular.module("myapp",[]);
myapp.controller("HelloController",function($scope,$http){
$scope.myform ={};
function update()
{
// If i have a textbox i can get its value from below alert
alert($scope.myform.value);
}
});
Can anyone also recommand me any good link for angularjs . I dont find angularjs reference as a learning source .
You should send the click event in the function, your html code should be :
<div ng-click="update($event)" id="myform.value">Here </div>
And your update function should have the event parameter which you'll get the div element from and then get the text from the element like this :
function update(event)
{
alert(event.target.innerHTML);
}
i just thought i put together a proper answer for everybody looking into this question later.
Whenever you do have the desire to change dom elements in angular you need to make a step back and think once more what exactly you want to achieve. Chances are you are doing something wring (unless you are in a link function there you should handle exactly that).
So where is the value comming, it should not come from the dom itself, it should be within your controller and brought into the dom with a ng-bind or {{ }} expression like:
<div>{{ likeText }}</div>
In the controller now you can change the text as needed by doing:
$scope.likeText = 'Like';
$scope.update = function() {
$scope.likeText = 'dislike';
}
For angular tutorials there is a good resource here -> http://angular.codeschool.com/
Redefine your function as
$scope.update = function() {
alert($scope.myform.value);
}
A better way to do it would be to use ng-model
https://docs.angularjs.org/api/ng/directive/ngModel
Check the example, these docs can be a bit wordy

MooTools Fx.Slide throwing this.element is null

The following code is throwing the error "this.element is null". However, the wid_cont is definitely grabbing an element.
window.addEvent('domready',function(){
var min = $(document.body).getElements('a.widget_minimize');
min.addEvent('click',
function(event){
event.stop();
//var box = ;
var wid_cont = ($(this).getParents('.widget_box').getElement('.widget_box_content_cont'));
var myVerticalSlide = new Fx.Slide(wid_cont);
myVerticalSlide.slideOut();
}
);
});
It's moo tools 1.2.4 and has the fx.slide included....
it does not return a single element but an array due to getParents() and possible other similarly marked up elements, Fx.Slide requires you pass it a single element.
here it is at least partially working when passing first item of the array: http://www.jsfiddle.net/KFdnG/
however, this is imo ineffective and difficult to manage if you have a long list of items and need a particular content layer to unfold only, you want to keep the lookup to the content layer more local.
something like this:
http://www.jsfiddle.net/KFdnG/4/
// store an instance into each content div and set initial state to hidden.
$$("div.widget_box_content_cont").each(function(el) {
el.store("fxslide", new Fx.Slide(el).hide());
});
$$('a.widget_minimize').addEvent('click', function(event) {
event.stop();
// can't use this.getNext() due to wrapper by Fx.Slide which does not have the instance.
this.getParent().getElement("div.widget_box_content_cont").retrieve("fxslide").toggle();
});
which works on the markup of:
<div class="widget_box">
<div class="widget_box_content">
link
<div class="widget_box_content_cont">
some content
</div>
</div>
<div class="widget_box_content">
link 2
<div class="widget_box_content_cont">
some content 2
</div>
</div>
</div>
this is also better as you won't be making a new instance of the Fx.Slide class on every click but will reference the ones already attached to the element.

Categories