Angular Js - Manipulate DOM and add click behaviour - javascript

I am using a leaflet map with angular and added a legend. To change the appearance of the map I manipulated the DOM after the view has loaded with a $timeout-method. The legend should be clickable to select which items should appear on the map. So I tried to make an alert, when one item in the legend is clicked. For that I changed the item to a button just to be sure. But the button click doesn't work because it doesn't know the method.
Does anybody know how to call a ng-click method on a manipulated DOM object?
Here is some of my code:
controller.js
//initiating of the map happend before
//now loading of the markers and manipulating of the map
function activate() {
projectService.getMarkers(vm, 'projectsData');
bindEventListener();
$timeout( function() {
getLegendElement();
});
}
//manipulating of the legend element to make a button with a ng-click
function getLegendElement() {
var element = document.getElementsByClassName("legend leaflet-control");
element[0].children[0].innerHTML = '<button ng-click="showAlert()">ok</button>';
}
//showing the alert
$scope.showAlert = function(){
$window.alert("foo");
}

Your DOM manipulation SHOULD be moved to a directive. However, if you wanted to keep it in your controller, you need to inject $compile into your controller and invoke it on your dynamically generated html.
function getLegendElement() {
var element = document.getElementsByClassName("legend leaflet-control");
element[0].children[0].innerHTML = '<button ng-click="showAlert()">ok</button>';
$compile(element)($scope);
}
The reason for this is because Angular looks for event bindings that it needs to generate when it first parses your template. Since you are applying a late event that needs binding to html that did not exist when your template was originally parsed, you need to tell Angular to update its understanding of the template. The documentation calls this out:
$compile
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.

Related

ng-click not working when using append function

I am trying to call a function using ng-click when dynamical elements are created but with no success so far. Here is my code
$scope.myfunc = function(){
alert("anything")
}
var divtoappend=angular.element( document.querySelector('#slist'));
divtoappend.append("<button class = 'optv' ng-click='myfunc()'>" +mybutton+ "</button>");
...
No error is thrown and nothing happens on click
You have to compile DOM with $compile API, before injecting into DOM tree, like below.
divtoappend.append($compile("<button class = 'optv' ng-click='myfunc()'>" +mybutton+ "</button>")($scope));
By compiling DOM angular will put all HTML level bindings in $$watchers array of $scope to make sure UI is up to date on every digest cycle.
You need to compile the dynamically generated HTML so that it is under the scope of AngularJS.
Just compile inside append method like
divtoappend.append($compile("<button class = 'optv' ng-click='myfunc()'>" +mybutton+ "</button>")($scope));
Before this inject $compile in your controller.
I have created a plunkr see this plunkr link
You need to compile your DOM elements as follow using $compile
$compile(divtoappent)(scope);

jQuery not detecting dynamic html

I have a series of folders that are represented as links using a VueJS for-loop, wit data pulled from API. For each of those links, I am using a library called x-editable to allow for the editing of the names. Essentially, when you click on the link with defined attributes (assuming you instantiate via editable(), it will bring up a pop-over).
I find that if I create a dummy link (outside of the for loop) and include the instantiation in a document.ready block, it works just fine:
EDIT: Note that the ready: function() is a custom document.ready function, defined in a Vuejs router.
ready: function() {
jQuery.fn.editable.defaults.mode = 'popup';
jQuery('.editable').editable();
},
However, none of the dynamically generated links work when they are clicked. My guess is that jQuery is simply not detecting them, as they were not made when the document was loaded.
I tried to call editable after the data was received and assigned to Vue:
//Folders
this.$http.get('/api/folders').then(function(response) {
this.state.folders = response.data;
jQuery('.editable').editable();
});
However, it seems that this doesn't work either. I read that you can bind events using .on() which will work with generated elements. However, .editable() isn't really an event (I think). Is there a way to get around this?

adding element with ng-click using angular.element

I am trying to add a couple of custom buttons to the right part of the header of fullcalendar (http://arshaw.com/fullcalendar/docs/).
I am using angular.element to add the buttons like this:
var calendarHeaderRight = angular.element(".fc-header-right");
if (calendarHeaderRight)
calendarHeaderRight.html('<div>CalendarList </div>');
The buttons do get appended to the calendar and are displayed. The problem is that nothing is happening when I click the buttons.
So the question is, how do I append elements which can be clicked ?
thanks
Thomas
var element = $compile(angular.element('<div>CalendarList </div>'))(scope);
then you can wrapped this element in your div. Here is angular document about compile.
: ) enjoy it.
var html = angular.element('<div>CalendarList </div>')
var compiled = $compile(html)
calendarHeaderRight.html(html)
compiled($scope)
View compilation in Angular basically traverses the entire DOM tree of whatever node you give it, creating a single function, that when called will execute every linking function from every directive it finds in that DOM tree, with the appropriate scope, element, attributes and (optionally) controllers.
Pretty much the same thing Tyler did just broken down.

backbone not rendering the $el but can reference element directly

This is killing me, being reading the examples on this site but can't figure out why it works like this.
I want to pass back values to my view, which has buttons that you can use to change the values.
If I use the following
this.$el.empty().html(view.el)
View.el contains the correct html, but those not render on the screen. If I use the following
$("#handicap").html( view.el);
The values get displayed on screen but the events no longer get picked up eventhough if I put an onclick function in the html code it kicks off.
Ideally I would like to get this.$el.empty().html(view.el) working. It has to do with context but can't see why.
I have created a jsbin here http://jsbin.com/iritex/1/edit
If I have to use $("#handicap").html( view.el), do I need to do something special to unbind events. I have tried undelegate everything but that didn't do the trick either.
thanks
A Backbone View's el property will always contain a reference to a valid DOM object. However, that DOM object may or may not be in your display tree. It's up to you to make sure it's in the display tree when you need it to be. This functionality lets Backbone maintain the state of it's View element without it being rendered to the screen. You can add and remove a view from the screen efficiently, for example.
There are a few ways to get your View's element into the display tree.
1) Associate the view with an existing DOM element on the page by passing in a jquery selector to the initializer as the "el" property.
var view = new MyView({el: '#MyElementSelector'});
2) Associate the view with an existing DOM element on the page by hardcoding the jQuery selector it into the view's "el" property.
var MyView = Backbone.View.extend({
el: '#MyElementSelector'
});
3) Render it to the page from within another view
var view = new MyView();
view.render();
this.$el.empty().html(view.el);
If you're interested, I show examples in a Backbone Demo I put together.
You need to put both views into the DOM. Wherever you create the view that above is this needs to be inserted into the DOM. If you do that, then the first line will work fine this.$el.empty().html(view.el).

knockout.js: update bindings?

when I inject any new elements into the DOM after ko.applyBindings(); was called, then knockout won't recognize these new elements.
I can understand why this is happening - they are just not indexed by knockout.
So, at first I thought this would be solved by just calling ko.applyBindings() again, after adding my new elements, BUT then I realized that for every ko.applyBindings() call you make, the according events get fired multiple times. So after applying five times, a click: binding will be fired five times, so this is not a desireable solution ;)
Is there anything like ko.updateBindings() or something else, to tell knockout to, well... update the element bindings?
greetings,
Chris
Each time you invoke ko.applyBindings the entire DOM is inspected for bindings. As a result you will get multiple bindings for each element if you do this more than once. If you just want to bind a new DOM element you can pass this element as a parameter to the applyBindings function:
ko.applyBindings(viewModelA, document.getElementById("newElement"));
See this related question:
Can you call ko.applyBindings to bind a partial view?
Without knowing what you're up to exactly, it seems like you're going the wrong way about this. Your view should be driven by your view model. So you shouldn't be directly adding DOM elements you then need to apply knockout bindings to.
Instead you should be updating your view model to reflect the change in the view, which then causes your new element to appear.
So for example, for your $('body').append('Click me!');, rather than adding the DOM element when the button should be visible, control the button visibility using the view model.
So your view model includes
var viewModel = { clickMeAvailable: ko.observable(false) }
And your HTML includes
Click me!
When the application state changes so the click me button is available, you then just viewModel.clickMeAvailable(true).
The point of doing this, and a big part of knockout, is to separate business logic from presentation. So the code that makes click me available doesn't care that click me involves a button. All it does is update viewModel.clickMeAvailable when click me is available.
For example, say click me is a save button that should be available when a form is filled in validly. You'd tie the save button visibility to a formValid view model observable.
But then you decide to change things so after the form is valid, a legal agreement appears which has to be consented to before saving. The logic of your form doesn't change - it still sets formValid when the form is valid. You would just change what occurs when formValid changes.
As lassombra points out in the comments on this answer, there are cases when direct DOM manipulation may be your best approach - for example a complex dynamic page where you only want to hydrate parts of the view as they are needed. But you are giving up some of the separation of concerns Knockout provides by doing this. Be mindful if you are considering making this trade-off.
I just stumbled upon a similar problem. I tried to add new elements to container and give those a onclick function.
At first tried the things you did, and even tried the approach ColinE recommended. This wasn't a practical solution for me so I tried SamStephens approach and came up with that, which works perfectly for me:
HTML:
<div id="workspace" data-bind="foreach:nodeArr, click:addNode">
<div class="node" data-bind="attr:{id:nodeID},style:{left:nodeX,top:nodeY},text:nodeID, click:$parent.changeColor"></div>
</div>
JavaScript:
<script>
function ViewModel() {
var self = this;
var id = 0;
self.nodeArr = ko.observableArray();
self.addNode = function (data, event) {
self.nodeArr.push({
'nodeID': 'node' + id,
'nodeX' : (event.offsetX - 25) + 'px',
'nodeY' : (event.offsetY - 10) + 'px'
})
id++;
}
self.changeColor = function(data, event){
event.stopPropagation();
event.target.style.color = 'green';
event.target.style.backgroundColor = 'white';
}
}
ko.applyBindings(new ViewModel());
</script>
You can play with it in the JS Fiddle I made.

Categories

Resources