Angularjs directives updating to changes in DOM attribute values - javascript

I've been working on a scrollspy module for Angularjs. I've run into the problem where if the page is dealing with dynamic content, the scrollspy data (element positions) quickly becomes outdated. What is the angularjs way of dealing with issues such as this?
Should any directive that performs DOM manipulation $broadcast an event that the scrollspy module looks out for - allowing it to refactor its position data?
Should the scrollspy module check every x seconds for a change in scrollHeight with $timeout?
Or even better, is there a way to bind and watch for DOM attribute value changes (attributes such as offsetTop, offsetHeight, scrollHeight, not data attributes)?
Update: Added code base to GitHub

Mutation Observers seem to be the facility needed, unfortunately they are only supported by the latest browsers.
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
if( MutationObserver ) {
for ( var i = 0; i < spyService.spies.length; i++ ) {
var spy = spyService.spies[i].scope.spy;
var target = document.getElementById(spy);
var config = {
attributes: true,
childList: true,
characterData: true,
subtree: true
};
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.warn('mutation observation');
});
}).observe(target, config);
}
}
else {
console.warn('no mutation observers here');
$interval(function() {
angular.element( document ).ready( function() {
console.log('refreshing');
});
}, 2000);
}
Currently searching for a polyfill that actually works.
EDIT: Added polling as a fallback if Mutation Observers aren't supported.

Something like this should work... doesn't seem to update with CSS transitions though.
scope.$watch(function(){
return elem[0].offsetLeft;
}, function(x){
console.log('ITEM AT '+x);
});

Without the code I'm stabbing in the dark a bit, but I would suggest your scrollspy module check every time $rootScope receives a $digest call. So use $rootScope.$watch.
$rootScope.$watch(function() {
// Update scrollspy data here
}

Related

Call back on window object initialization (dynamic inclusion of iframes in page)

What I need:
My requirement is to inject script in to all windows that are presented in a web page.
I know we can find all windows by using window.frames property, but that won't be sufficient in my case since new windows can be added later to the page via iframes (inclusion of iframes in to the DOM)
So I need a mechanism to track windows in a page, something like callback on new window initialization.
What I tried:
I used Object.observe API to track the window.frames object changes. But I came to know that Object.observe API is going to be removed as per this link (https://esdiscuss.org/topic/an-update-on-object-observe).
So, is it good to use this API. Or if any alternate way is there please let me know
Here is a way using MutationObserver API, you can use the api to detect any element injected into your target element, even when a text is change or element is appended somewhere in your target tree
function createIframeOnDemand(wait) {
var iframe = document.createElement('iframe')
iframe.src = 'about:blank';
setTimeout(function() {
document.body.appendChild(iframe)
}, wait || 2000);
}
var body = document.body;
var observer = new MutationObserver(function(mutations) {
for(mutationIdx in mutations) {
var mutation = mutations[mutationIdx];
for(childIndex in mutation.addedNodes) {
var child = mutation.addedNodes[childIndex];
if(child.tagName && child.tagName.toLowerCase() == 'iframe') {
console.log(child);
//child is your iframe element
//inject your script to `child` here
}
}
}
});
var config = { attributes: false, childList: true, characterData: false };
observer.observe(body, config);
createIframeOnDemand();

Highcharts event redraw/load with reflow, and Angular

I'm using Angular Highcharts, although I don't think it makes a lot of difference in this case. I'm using a responsive design, and the chart appears before it "figures out" the size of the parent element. Not a problem, except triggering reflow doesn't appear to work in the callback of redraw or load.
Here is the basic code:
events: {
redraw: function(event) {
$rootScope.$broadcast('highchartsng.reflow');
}
}
If I use "load" instead of redraw, it works even worse as it triggers first and doesn't recognize the highcharts object at all.
That's what MutationObserver is for. Available in IE11, https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver. You need to inject $element service into controller which semi-breaks DOM/controller separation, but I feel that this is a fundamental exception (ie. flaw) in angularjs. Since hide/show is async, we need on-show callback, that angularjs & angular-bootstrap-tab do not provide. It also requires that u know which specific DOM element u want to observe. I used following code for angularjs controller to trigger Highcharts chart reflow on-show.
const myObserver = new MutationObserver(function (mutations) {
const isVisible = $element.is(':visible') // Requires jquery
if (!_.isEqual(isVisible, $element._prevIsVisible)) { // Lodash
if (isVisible) {
$scope.$broadcast('onReflowChart')
}
$element._prevIsVisible = isVisible
}
})
myObserver.observe($element[0], {
attributes: true,
attributeFilter: ['class']
})

detect div change in javascript

I'm working on a small chrome extension for fun, and one thing I need it to be able to do, is to detect when the text inside a div is changed by the webpage itself.The code I'm using is:
var status = document.getElementById("status").innerHTML;
status.onchange = function() {
console.log("CHANGE DETECTED")
And this doesn't seem to work, so what should I use instead?
NOTE: I'd prefer not to use jquery, as I am not even very proficient with javascript at the moment, but if it would be that much simpler/easier, that would be fine.
use this trick
source:https://hacks.mozilla.org/2012/05/dom-mutationobserver-reacting-to-dom-changes-without-killing-browser-performance/
// select the target node
var target = document.querySelector('#some-id');
// create an observer instance
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation.type);
});
});
// configuration of the observer:
var config = { attributes: true, childList: true, characterData: true }
// pass in the target node, as well as the observer options
observer.observe(target, config);
// later, you can stop observing
observer.disconnect();
You can't do what you want using change event. On newer browsers, you can use Mutation Observers. On older browsers... well, you ask people to upgrade to newer browsers. :P

How to scan dynamically added text with jQuery

I'm trying to scan all text on a page and selectively create tooltips on pieces of text. I have working code that does this, but it only works on text that's on the page when the DOM ready event fires. Since the .live() function has been deprecated, we're supposed to use .on(), but that function only applies to elements that existed when it was called. Delegate event handlers apply to both current and future elements, but require the bound event to bubble up to the parent and the load event doesn't bubble.
So how can I scan all text--or even all the elements for that matter--as it's dynamically loaded?
UPDATE:
Per Makaze's comment, I tried several approaches. This one seems closest so far, but not quite:
$('body').on('DOMNodeInserted', '*:not("script")', function(e){
console.dir(e.target); //drill in here, I can see the nodeType==3 nodes
//var find = $(e.target);
var nodes = flattenTree(e.target.childNodes, 0); //recursively get all child nodes
for(var i in nodes){
var elem = $(nodes[i]);
var parent = elem.parent();
var txt = elem.text();
if(txt!==undefined && !txt.match(/^\s*$/)){
var refs = txt.match(versePattern);
if(refs!==null){
//var i = 0;
console.log(refs); //I never see the text node here, but I see it above when I manually drill into e.target
The versePattern matches as I expect in the static version of this code (which is working correctly), so I don't think that's the issue. Also, the '*:not("script")' doesn't seem to work as I'm still seeing <script> tags, but that's a minor thing that I can deal with later.
The MutationObserver constructor is what you want. Bind it to the parent element or document and filter your mutations from there.
// select the target node
var target = document.querySelector('#some-id');
// create an observer instance
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation.addedNodes);
});
});
// configuration of the observer:
var config = {
attributes: true,
childList: true,
characterData: true
};
// pass in the target node, as well as the observer options
observer.observe(target, config);
setTimeout(function() {
target.appendChild(document.createTextNode('There!'));
// later, you can stop observing
// observer.disconnect();
}, 1000);
<div id="some-id">Wait for it...</div>
Side note: You can use .on() on the document and use a selector to filter the targets similar to .delegate(): $(parentSelectors).on(types, childSelectors, function).
Just initialized all your textboxes that you want to put tooltip on your js file.
Sample;
//Initialize Tooltip
$('#Name').tooltip()
$('#Age').tooltip()
$('#Address').tooltip()

jQuery when element becomes visible [duplicate]

This question already has answers here:
How to check if element is visible after scrolling?
(46 answers)
Closed 1 year ago.
Basically, I am wondering if there is a way to automatically run a function when an element becomes hidden or visible, not on a user click but automatically in another script.
I don't want this to just run one time, because the elements (such as a slider) constantly change from visible to hidden.
Would this be something that jQuery can do with bind? Such as binding the element's visibility to a function (I don't know how to write this)
If you need me to elaborate more on what I'm trying to do, let me know. Thanks
Pseudocode:
$('#element').bind('display:none', function);
function(){
//do something when element is display:none
}
$('#element').bind('display:block', function2);
function2(){
//do opposite of function
}
There are no events in JQuery to detect css changes.
Refer here: onHide() type event in jQuery
It is possible:
DOM L2 Events module defines mutation events; one of them - DOMAttrModified is the one you need. Granted, these are not widely implemented, but are supported in at least Gecko and Opera browsers.
Source: Event detect when css property changed using Jquery
Without events, you can use setInterval function, like this:
var maxTime = 5000, // 5 seconds
startTime = Date.now();
var interval = setInterval(function () {
if ($('#element').is(':visible')) {
// visible, do something
clearInterval(interval);
} else {
// still hidden
if (Date.now() - startTime > maxTime) {
// hidden even after 'maxTime'. stop checking.
clearInterval(interval);
}
}
},
100 // 0.1 second (wait time between checks)
);
Note that using setInterval this way, for keeping a watch, may affect your page's performance.
7th July 2018:
Since this answer is getting some visibility and up-votes recently, here is additional update on detecting css changes:
Mutation Events have been now replaced by the more performance friendly Mutation Observer.
The MutationObserver interface provides the ability to watch for changes being made to the DOM tree. It is designed as a replacement for the older Mutation Events feature which was part of the DOM3 Events specification.
Refer: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
(function() {
var ev = new $.Event('display'),
orig = $.fn.css;
$.fn.css = function() {
orig.apply(this, arguments);
$(this).trigger(ev);
}
})();
$('#element').bind('display', function(e) {
alert("display has changed to :" + $(this).attr('style') );
});
$('#element').css("display", "none")// i change the style in this line !!
$('#element').css("display", "block")// i change the style in this line !!
http://fiddle.jshell.net/prollygeek/gM8J2/3/
changes will be alerted.
Tried this on firefox, works http://jsfiddle.net/Tm26Q/1/
$(function(){
/** Just to mimic a blinking box on the page**/
setInterval(function(){$("div#box").hide();},2001);
setInterval(function(){$("div#box").show();},1000);
/**/
});
$("div#box").on("DOMAttrModified",
function(){if($(this).is(":visible"))console.log("visible");});
UPDATE
Currently the Mutation Events (like DOMAttrModified used in the
solution) are replaced by MutationObserver, You can use that to
detect DOM node changes like in the above case.
I just Improved ProllyGeek`s answer
Someone may find it useful.
you can access displayChanged(event, state) event when .show(), .hide() or .toggle() is called on element
(function() {
var eventDisplay = new $.Event('displayChanged'),
origShow = $.fn.show,
origHide = $.fn.hide;
//
$.fn.show = function() {
origShow.apply(this, arguments);
$(this).trigger(eventDisplay,['show']);
};
//
$.fn.hide = function() {
origHide.apply(this, arguments);
$(this).trigger(eventDisplay,['hide']);
};
//
})();
$('#header').on('displayChanged', function(e,state) {
console.log(state);
});
$('#header').toggle(); // .show() .hide() supported
A catch-all jQuery custom event based on an extension of it's core methods like it was proposed by different people in this thread:
(function() {
var ev = new $.Event('event.css.jquery'),
css = $.fn.css,
show = $.fn.show,
hide = $.fn.hide;
// extends css()
$.fn.css = function() {
css.apply(this, arguments);
$(this).trigger(ev);
};
// extends show()
$.fn.show = function() {
show.apply(this, arguments);
$(this).trigger(ev);
};
// extends hide()
$.fn.hide = function() {
hide.apply(this, arguments);
$(this).trigger(ev);
};
})();
An external library then, uses sth like $('selector').css('property', value).
As we don't want to alter the library's code but we DO want to extend it's behavior we do sth like:
$('#element').on('event.css.jquery', function(e) {
// ...more code here...
});
Example: user clicks on a panel that is built by a library. The library shows/hides elements based on user interaction. We want to add a sensor that shows that sth has been hidden/shown because of that interaction and should be called after the library's function.
Another example: jsfiddle.
I like plugin https://github.com/hazzik/livequery It works without timers!
Simple usage
$('.some:visible').livequery( function(){ ... } );
But you need to fix a mistake. Replace line
$jQlq.registerPlugin('append', 'prepend', 'after', 'before', 'wrap', 'attr', 'removeAttr', 'addClass', 'removeClass', 'toggleClass', 'empty', 'remove', 'html', 'prop', 'removeProp');
to
$jQlq.registerPlugin('show', 'append', 'prepend', 'after', 'before', 'wrap', 'attr', 'removeAttr', 'addClass', 'removeClass', 'toggleClass', 'empty', 'remove', 'html', 'prop', 'removeProp');

Categories