destroy directive/child scope on scope destroy - html

I have a directive that compiles another directive and attaches it to the body with the same scope passed. This will not be the same location as the "parent" directive.
When the parent directive gets destroyed is there some way to have the child directive and scope destroy as well? I ask because after inspecting the DOM the child directive is still there.
Currently I hook into the parents $destroy event but was curious if it could be handled automatically.
jsFiddle: http://jsfiddle.net/FPx4G/1/
The child stays there as you toggle the parent, but i'd like to to be destroyed. What would be the best method to do that?
html:
<div ng-app="app">
<div ng-controller="ParentCtrl">
<button data-ng-click="toggleParent()">Toggle Parent</button>
<div data-ng-switch data-on="displayDirective">
<div data-ng-switch-when="true">
<div class="parentDirective">Parent Directive</div>
</div>
</div>
</div>
</div>
javascript:
angular.module('app', [])
.directive("parentDirective", function($compile){
return {
restrict: 'C',
link: function(scope, element){
var secondDirective = angular.element(document.createElement("div"));
secondDirective.addClass("childDirective");
document.body.appendChild(secondDirective[0]);
$compile(secondDirective)(scope);
}
}
})
.directive("childDirective", function(){
return {
restrict: 'C',
template: '<div>Child Directive</div>',
link: function(scope, element){
scope.$on("destroy", function(){
alert(1);
});
}
}
});
function ParentCtrl($scope){
$scope.displayDirective = true;
$scope.toggleParent = function(){
$scope.displayDirective = !$scope.displayDirective;
}
}
Normally, I'd just have the sub element within the original directive's template so that it's positioned correctly. The issue really comes down to dealing with z-index. The parent element is in a container that can be scrolled, so the child (in one case a custom dropdown) would be hidden/cut off if it was larger then the container. To combat this I instead create the actual child in the document body and position it relative to the parent. It would also listen in on scroll events to reposition itself. I have that all working and is just fine. It's what happens when I need to delete the parent... the child is still there.

directive("childDirective", function(){
return {
restrict: 'C',
template: '<div >Child Directive</div>',
link: function(scope, element){
scope.$on("$destroy",function() {
element.remove();
});
}
}
});
updated fiddle : http://jsfiddle.net/C8hs6/

Related

Polymer - can not click event outside of element to close itself

Polymer 1.*
I had to write my own dropdown menu. I need to close the menu when the user clicks outside of the element. However, I am not able to catch the event when a user clicks outside of the element so I can close the menu.
Any ideas what I am doing wrong?
EDIT: I've studying paper-menu-button which closes paper-listbox when I click outside the element.... but I don't see anywhere where it catches that event https://github.com/PolymerElements/paper-menu-button/blob/master/paper-menu-button.js#L311
<dom-module id="sp-referrals-reservations-dropdown">
<template>
<style include="grid-dropdown-styles">
</style>
<div id="dropdown" class="grid-dropdown">
<paper-listbox>
<div class="grid-dropdown-item">Convert to stay</div>
<div class="grid-dropdown-item">Cancel reservation</div>
<div class="grid-dropdown-item">Delete reservation</div>
</paper-listbox>
</div>
</template>
<script>
(function() {
'use strict';
Polymer({
is: 'sp-referrals-reservations-dropdown',
behaviors: [Polymer.IronControlState],
properties: {
},
listeners: {
'tap': '_close',
'click': '_close',
'blur': '_close',
'focusout': '_close',
'focusChanged': '_close',
'focus-changed': '_close',
'active-changed': '_close',
'activeChanged': '_close',
'iron-activate': '_close',
'ironActivate': '_close',
},
open: function(e) {
},
_close: function() {
console.log('aaa');
this.$.dropdown.style.display = "none";
},
});
})();
</script>
</dom-module>
I am not sure, will it be enough, but if you wrap the sp-referrals-reservations-dropdown element with a parent-element then you can listen to parent-element events same as its child.
<parent-element></parent-element>
<dom-module id="parent-element">
<template>
<style>
:host {
display:block;
background:green;
width:100%;
height:100vh; }
</style>
<sp-referrals-reservations-dropdown id="spref"></sp-referrals-reservations-dropdown>
At parent's script:
Polymer({is:'parent-element', properties:{},
listeners:{ 'tap': '_taped'},
_taped:function(t){
this.$.spref._close();
}
});
this _taped functions will call child's _close function. Hope its help.
Incase of needed more. We can develop this.
Demo
EDIT
Wrap your element into paper-dialog. And at ready:function() call
this.$.dialog.open()
Then when you click outside of the element. paper-dialog will close automatically.
Just FYI, you weren't able to get this to work because custom elements don't listen for events outside of their own encapsulation unless you explicitly wire them up to do so... and if you do so, you can't use Polymer's built-in event handling.
So something like this would work:
// Insert this somewhere it'll get run once attached to the DOM
// This line keeps clicks within your element from closing the dropdown
this.shadowroot.addEventListener('click', (event) => { event.stopPropagation(); });
// And this listens for any click events that made it up to body, and closes the element
document.querySelector('body').addEventListener('click', this._close);
Or, at least, that's what I think was going on. The polymer elements either did it by binding to a different event (blur?) or by having the parent element trigger an event on click that told the child element to close.

Angular Page Height Directive updating incorrectly

I am making a directive to set the height of a div equal to the height of the browser if the height of the div is less than the height of the browser.
I have created the following directive:
(function() {
var directive = function ($log, $window) {
function evaluate(element) {
var height = element.prop("offsetHeight");
var pageHeight = $window.innerHeight;
if (height < pageHeight)
{
$log.info("element height is less than page height, changing element height.");
element.css("height", pageHeight);
}
}
return {
restrict: "A",
link: function (scope, element, attrs)
{
//call it one time on load to handle first display
evaluate(element);
$window.addEventListener("resize",
function() {
$log.info("Resize directive called: evaluating window size.");
//window size changed, check if we need to adjust the height
evaluate(element);
});
//add a watch to trigger when the inside of the div changes
scope.$watch(function() { return element[0].childNodes.length }, function(values) {
evaluate(element);
});
}
}
};
angular.module('bootstrapApp')
.directive('fullSize', ["$log", "$window", directive]);
})();
and it is used in the following way:
<body>
<fullsize-background-image-1></fullsize-background-image-1>
<navbar-1></navbar-1>
<div class="container-fluid">
<div class="row">
<left-sidebar-1></left-sidebar-1>
<right-sidebar-1></right-sidebar-1>
<div class="col-xs-12 main" id="main" full-size>
<div ng-view="" autoscroll="true"></div>
<footer-1></footer-1>
</div>
</div>
</div>
....
</body>
My problem is, on page load, thing was working perfectly (without the scope.$watch statement). However, when angular routed to a different page, that content replaced what is in the div#main element. Sometimes the height of the new page is greater than the height of the previous page, causing my hidden <left-sidebar-1> to show.
What I need to do is, on initial page load have my full-size directive evaluate what the height of main should be. Then, when the page changes, have it re-evaluate it and remove the property if the content is bigger.
thanks.

Angular Directive Children without transclude parent

I'm working with Angular directives that looks like this:
<parent>
<children></children>
<children></children>
<children></children>
</parent>
Parent directive has a
template: return "<div><ul></ul><div ng-transclude></div></div>"
And children directives will go inside that ng-transclude div.
My final HTML structure is
<newParent>
<div ng-transclude>
<child></child>
<child></child>
<child></child>
</div>
</newParent>
I wonder if it's possible to remove that ng-transclude div so that the new children are direct children of the new parent. (I have more children, a random number >1).
I have to do so to match an existing template so I cannot change its structure.
I actually have no Fiddles, if you need more information just ask. Thank you!
You can append the content yourself, without using ng-transclude, using the transclude function:
app.directive("parent", function($compile) {
return {
restrict: "EA",
transclude: true,
link: function(scope, element, attrs, ctrls, $transclude) {
$transclude(function(clone, scope) {
element.append(clone);
});
}
};
});
Here's more information about Transclusion.
Without a template, this will add the transcluded elements as only children of the directive. With the template, you'll need to properly place (e.g. insert it into a <div> after <ul>) the content yourself.

How to attach a Click handler to a DOM element

How do I get a DOM element and attach an event onClick?. I tried this code but it does not work:
<div>
<h1 id="some_id">
click here
</h1>
</div>
JavaScript:
Ext.onReady(function() {
if(Ext.getDom('some_id'))
{
var elDom = Ext.getDom('some_id');
elDom.on('click', function(){
Ext.Msg.alert('Status', 'Already get the element from the dom');
});
}
});
Fiddle: https://fiddle.sencha.com/#fiddle/2fl
Another way to do this is to get the Ext.dom.Element instead of the actual DOM node. This will allow you to use .on(). This is done by using Ext.get() instead of Ext.getDom().
var elDom = Ext.get('some_id');
if(elDom) {
elDom.on('click', function() {
Ext.Msg.alert('Status', 'Already get the element from the dom');
});
}

Hover not working on AJAX

I have the below content that loads on through AJAX.
<div class="grid">
<div class="thumb">
<img alt="AlbumIcon" src="some-image.jpg">
<div style="bottom:-75px;" class="meta">
<p class="title">Title</p>
<p class="genre"> <i class="icon-film icon-white"></i>
Genre
</p>
</div>
</div>
</div>
Additionally, I have writen the following script in jquery that applies to the above 'div.grid'.
jQuery(function ($) {
$(document).ready(function () {
$(".grid").on({
mouseenter : function () {
$(this).find('.meta').stop().animate({
bottom:'0px'
},200);
},
mouseleave : function () {
$(this).find('.meta').stop().animate({
bottom:'-75px'
},200);
}
});
});
});
The script works fine when the page loads the first time. However, the hover effect doesn't work once the above div is generated via AJAX after clicking on an 'a' tag. I can't seem to figure out what's wrong here? New to all this. Can anyone help?
To append these event handlers to dynamically generated elements, you need to bind to the document or another static parent element and then specify .grid as the second argument passed to .on.
The second argument is used as a filter to determine the selected elements that trigger the event. So when the event is fired it will propagate to the document or parent element selected by jquery. The event target will then be scrutinized using the selector provided as the second argument. If the target matches the second argument, (.grid in our case), the event is fired.
You can read more in the jQuery documentation.
Also, since your using document.ready there is no need for the short hand ready statement, jquery(function($).
$(document).ready(function () {
$(document).on({
mouseenter : function () {
$(this).find('.meta').stop().animate({
bottom:'0px'
},200);
},
mouseleave : function () {
$(this).find('.meta').stop().animate({
bottom:'-75px'
},200);
}
}, ".grid");
});
you lost your binding because of ajax that overwrite your div with class=".grid"
use parent element for binding
$('.ParentElementClass').on("mouseleave", ".grid", function(){...})
more from jquery api
Delegated events have the advantage that they can process events from descendant elements that are added to the document at a later time. By picking an element that is guaranteed to be present at the time the delegated event handler is attached, you can use delegated events to avoid the need to frequently attach and remove event handlers. This element could be the container element of a view in a Model-View-Controller design, for example, or document if the event handler wants to monitor all bubbling events in the document. The document element is available in the head of the document before loading any other HTML, so it is safe to attach events there without waiting for the document to be ready.
Not sure what you're shooting for here but a little malformed HTML may have done it...
jsFiddle Demo
<div class="grid">
<div class="thumb">
<img alt="AlbumIcon" src="some-image.jpg" />
<div style="bottom:-75px;" class="meta">
<p class="title">Title</p>
<p class="genre"><i class="icon-film icon-white"></i>Genre</p>
</div>
</div>
</div>
$(function () {
$(".grid").on({
mouseenter: function () {
alert('entered');
$(this).find('.meta').stop().animate({
bottom: '0px'
}, 200);
},
mouseleave: function () {
alert('left');
$(this).find('.meta').stop().animate({
bottom: '-75px'
}, 200);
}
}, ".thumb");
});
});
Be sure to close img tags. They're notorious for causing intermittent glitches.
You can just use the hover function:
jQuery(function ($) {
$(document).ready(function () {
$(".grid").hover(function () { /*mouseenter*/
$(this).find('.meta').stop().animate({
bottom:'0px'
},200);
},function(){ /*mouseleave*/
$(this).find('.meta').stop().animate({
bottom:'-75px'
},200);
}
});
});
Explanation:
The first parameter function does the work of mouseenter and the second does the work of mouseleave.
I'd recommend using those both, mouseenter and mouseleave in situation when you don't want an effect back when the user gets off his mouse from the element.