ngRepeat removes tooltip styling - javascript

Thanks in advance for any help.
I have a jquery tooltip that is styled, but when it is used in an ngRepeat the custom styling of the tooltip is removed and I'm not sure why.
Examples:
Tooltip shows up with correct styling:
<div class="toggle">
<input type="checkbox" id="amount_{{i.ProductCode}}" name="amount" data-ng-model="$parent.amount" value="{{i.ProductCode}}" data-ng-required="1" />
Hello <span class="info-tip" title="World!"></span>
</div>
Tooltip shows up with default styling:
<div data-ng-repeat="i in options.amounts" data-ng-cloak="">
<div class="toggle">
<input type="checkbox" id="amount_{{i.ProductCode}}" name="amount" data-ng-model="$parent.amount" value="{{i.ProductCode}}" data-ng-required="1" />
Hello <span class="info-tip" title="World!"></span>
</div>
</div>
I've spent quite a while looking into it, and from what I can gather from my research it seems to be an ngRepeat scoping issue. However I'm not certain that this is the issue and I'm not sure how to go about fixing it if it is (hence coming here). Ideally I would like to use ngRepeat and maintain my custom tooltip styling.
Any guidance is appreciated, thanks!

The solution was to "refresh" the dynamic elements in the repeater after the repeater has been initialised and rendered.
Create new directive to broadcast when the ngRepeat has finished rendering:
app.directive('onFinishRender', ["$timeout",function ($timeout) {
return {
restrict: 'A',
link: function(scope, element, attr) {
if (scope.$last === true) {
$timeout(function() {
scope.$emit('ngRepeatFinished');
});
}
}
};
}]);
In the controller create a listener for the broadcast that then updates the parent of the tooltip (which in turn updates the tooltip):
$scope.$on('ngRepeatFinished', function (e) {
window.hbf.angular.components.byName("components/Tooltip")
.update($('.parentClass'));
});
Add 'on-finish-render="ngRepeatFinished"' to the ngRepeat in the ascx:
<div data-ng-repeat="i in items" data-ng-cloak="" on-finish-render="ngRepeatFinished">
Reasoning: On the page load event the jQuery binds the custom tooltip class to tooltip function. However, when it's used in the repeater the event is fired after the page load and there is no existing binding. Therefore it is necessary to bind newly created elements to the tooltip again to "refresh" them and have them display.
Basically the jQuery is initialised and rendered, but the ngRepeat is on the client side. Each repeat is dynamically generated with the scope of the child, not the parent, so all original bindings are out of scope and need to be re-binded after the ngRepeat has been created.
If anyone wants more info on how I went about this I posted it on my development blog which you can find here.

Related

Why does div with ng-if prevent its sibling from running Angular?

I have just spent hours trying to discover why the Angular in one div suddenly stopped working. I finally tracked it down to a sibling div which has an ng-if="someVar". Currently someVar is true and the div displays and functions properly, including some ng-mouseovers, some ng-clicks and plenty of live updating scope {{variables}}.
However for some reason having this ng-if there causes its sibling's functions to completely stop functioning. {{otherVars}} just remain completely static and all ng-mouseovers and ng-clicks just completely stop doing anything. Why the heck is this happening??
This is a compacted version of my code:
<div class="first-div" ng-if="!showFAQ">
<p class="title">
<span ng-mouseover="message = messages.default; defaultHover = true" ng-mouseleave="defaultHover = false">{{defaultHover}}</span>
</p>
<span class="enter-button-inner" ng-mouseover="defaultHover = true; message = messages.enterButton" ng-mouseleave="defaultHover = false" ng-click="enterApp()">
Click here to start
</span>
</div>
<div class=" second-div show-message-{{defaultHover}}">
{{message}}
</div>
All the Angular directives and functions in the first-div work perfectly, whereas all the code in the second-div simply doesn't work at all.
Why is this?
Build Understanding:
ng-if will remove elements from DOM. This means that all your handlers or anything else attached to those elements will be lost. For example, if you bound a click handler to one of child elements, when ng-if evaluates to false, that element will be removed from DOM and your click handler will not work any more, even after ng-if later evaluates to true and displays the element. You will need to reattach the handler.
ng-show/ng-hide does not remove the elements from DOM. It uses CSS styles to hide/show elements (note: you might need to add your own classes). This way your handlers that were attached to children will not be lost.
ng-if creates a child scope while ng-show/ng-hide does not
Depending on what you want to achieve, you might want to use ngShow instead.
The difference with ngIf is that ngIf completely removes your element from the DOM, where ngShow just hides it. When ng-if evaluates to true again, it creates a clone of your element and adds it back to the DOM.
Seems like your setting scope variables in the <div> is failing (probably due to primitives in `ng-if's scope).
<div class="first-div" ng-if="!model.showFAQ">
<p class="title">
<span ng-mouseover="hoverHndl(true, messages.default)"
ng-mouseleave="hoverHndl(false)">Hover me!: {{defaultHover}}
</span>
</p>
<span class="enter-button-inner"
ng-mouseover="hoverHndl(true, messages.enterButton)"
ng-mouseleave="hoverHndl(false)"
ng-click="enterApp()">
Click here to start
</span>
</div>
<div class=" second-div show-message-{{defaultHover}}">
message: {{message}}
</div>
And JS:
$scope.showFAQ = true;
$scope.messages = {
default: "defaulMsg",
enterButton: "enterButton"
};
$scope.message = 'initialMsg';
$scope.hoverHndl = function(hover, message) {
$scope.defaultHover = hover;
$scope.message = message;
};
See it is working here if you do the assignment in a function: https://jsbin.com/lahoyevuqo/1/edit?html,js,output

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

AngularJS : DOM manipulation of ng-repeat elements in a directive

I am new to angularJS. I am trying to implement the a simple functionality wherein there are a number of checkbox options and one specific function needs to be called on click of these checkbox options something similar to
$(parentDiv).on("click",'input[type="checkbox"]', functionToBeCalled});
I am trying to use a directive for this section with content similar to
<div>
<span ng-repeat="subsection in section"><input type="checkbox" value="subsection.name"></input> {{subsection.name}} </span>
</div>
Directory
app.directive('myDir', function() {
return {
restrict: 'AE',
replace: true,
templateUrl: 'filter.html',
link: function(scope, elem, attrs) {
elem.find("input").bind('click', function() {
//do some data manipulation
});
});
};
});
When i use the link function I find that if I use elem.find('input[type="checkbox"]) gives me an empty object so it is not compiled before that. Kindly let me know how to address this situation. I did a bit of a research and found out about compile function which manipulates DOM before linking,but i am not able to think of the approach ahead. Any help will be appreciated. Thanks in advance.
Use ngClick or ngChange Directive inn angularjs
<div>
<span ng-repeat="subsection in section">
<input type="checkbox" value="subsection.name" ng-click="myFunc(subsection.name)" /> {{filter.name}} </span>
</div>
here in the example i used ng-click = "myFunc" and pased the value in that function
It might help for us to know how you directive looks like and specifically what element that directive is attached to, since you're speaking about elem.
Nevertheless, if you just need functions to be called when the checkbox is clicked, you can use built-in angular functions for this:
ngChange
ngClick

Best method of accessing a child div inside a directive?

alright so, I'm constantly running into this problem where I need to select/access a wrapping container between a parent directive and its child directive.
pseudo code:
<div parent-directive>
<!-- selecting the viewport becomes hard. making an extra directive seems weird -->
<div class="wrapping-div">
<div child-directive ng-repeat="page in pages" ></div>
</div>
</div>
I don't want to use jQuery for selecting wrapping-div by class. And creating a extra directive forwrapping-div element makes the templates so thin and it reads weird? Lastly, using element.children() from parent-directive seems really fragile?
Question:
What is the preferred method of selecting a child element from a template of a directive? Is there Any way to keep it angular w/o making every part a directive?
For a better context, I'm making a basic carousel directive called wa-carousel.
Main.html
wa-carousel is an sort-of API:
<div wa-carousel class="wa-carousel"></div>
wa.carousel.html
this template has a directive: wa-pages.
<div class="wa-carousel-viewport">
<ul class="transition-fast wa-page-collection" ng-style="carousel.pagedOffseter">
<li wa-pages page="page" class="wa-pages" ng-repeat="page in carousel.pages"></li>
</ul>
</div>
As you can see there are 2 wrappers that are only classes not directives wa-carousel-viewport and wa-page-collection.
The wa-page-collection will be using ng-touch but I'd like to control it from wa-carousel link function but I'm forced to use el.children().children() which I dont like.
wa.carousel.js
link: function(scope, el, attrs){
var viewport = el.children().children();
$swipe.bind(viewport, {
'start': function(coords) {}
})
}
**What is the prefered method of selecting wa-page-collection in angular? Is it considered bad practice to do the manipulation inside of wa-carousel's link function? **
Use a data- attribute or similar on the element you want to specifically target. Then:
link: function (scope, el, attrs) {
var viewport = angular.element(el[0].querySelector('[data-my-attr]'));
$swipe.bind(viewport, {
'start': function(coords) {}
})
}
If using jQuery, you can shorten the selector to var viewport = el.find('[data-my-attr]');

knockout.js visibility not working when css style is applied

I have an issue where knockout.js 2.0 isn't showing my item when a CSS style is applied to it. It won't update the display with the style applied to it. If it is off it works.
CSS
.success { display:none }
HTML
<div data-bind="visible: site.signUp.success()" class="success">
Thanks for signining up. You will recieve an email from us in the near future.
</div>
JS
app.viewModel.site.signUp.success(true);
In the period of time before Knockout.js applies bindings, you can prevent the initial rendering/flashing effect by setting the default display style to none.
<div style="display: none" data-bind="visible: site.signUp.success">
Thanks for signining up. You will recieve an email from us in the near future.
</div>
I created a fiddle that shows how you can use the css binding in Knockout to do this. http://jsfiddle.net/johnpapa/vwcfT/
Here is the HTML:
Success Flag: <input type="checkbox" data-bind="checked:site.signUp.success"></input>
<div data-bind="visible: site.signUp.success" >
Thanks for signining up. You will recieve an email from us in the near future.
</div>
<br/><br/>
<span data-bind="text:site.signUp.success"></span>
<div data-bind="css: { success: site.signUp.success}" >
Thanks for signining up. You will recieve an email from us in the near future.
</div>
The first DIV in the example just uses the visible binding, since you dont really need a css class to do this. The second DIV in the example binds to a css class named "success" if the site.signUp.success observable is true. This is more verbose than the first, but could be useful if you needed your css class to do more than just set visibility.
Hope this helps.
Here is the javascript:
var viewModel = {
site: {
signUp: {
success: ko.observable(true)
}
}
};
ko.applyBindings(viewModel);
That's because the success style is defined as display:none, which is equivalent to visible = false. Your CSS class is cancelling out your site.signUp.success() call.
If you want your DIV to show up only when site.signUp.success() == true, just do this:
<div data-bind="visible: site.signUp.success">
Thanks for signining up. You will recieve an email from us in the near future.
</div>
It might be a bit late but I found the following useful. Instead of fixing every element with a visibility control, just wrap a div around all your pre-hidden elements as follow:
<div style="display:none" data-bind="visible: true">
Some pre-hidden elements
<div data-bind="visible: myVisibleFoo">...</div>
<div data-bind="visible: myVisibleBar">...</div>
Other pre-hidden elements
...
</div>
The whole section of elements is hidden initially and is only shown after KO has applied bindings. I usually wrap the whole page with it to avoid any flashing problem.
Just run into this myself; I can see why the did it this way, but it is handy to set a default visibility of none on late loaded elements on the page so they don't flash as scripts are loaded. The nicest way I could find of doing this was just to create a simple custom binding:
ko.bindingHandlers.forceVisible = {
update:
function(el, f_valueaccessor, allbindings, viewmodel, bindingcontext)
{
if(ko.unwrap(f_valueaccessor()))
el.style.display = 'inherit';
else
el.style.display = 'none';
}
};
You have to be a little bit careful when setting up your styles; if you are using a div and your CSS sets it to display:inline-block then this code will not work - it will have block display when the inherit is applied. This simple solution was fine for my use case, however.

Categories