I have 2 dynamic sub-modals that appear/expand within a parent modal. (parent modals or pre-expanded modal doesn't need the below update).
Within two sub modals, that become expanded from parent modal, I am simply trying to add/invoke a 'back button' and 'close button' in the .both_submodals__heading of those two modals; I am achieving this with the below solution, but I hate the fact I am using a set interval, this also creates console errors when the setInterval is checking for the relevant .both_submodals__heading until it's found.
on(footWrap, 'click', function (event) {
let visible = coordinateWidget.visible;
coordinateWidget.visible = !visible;
var checkExists = setInterval(function() {
const coordWid = document.getElementsByClassName('both_submodals__heading')[0];
if (typeof(coordWid) != 'undefined' && coordWid != null) {
const closeBlock = document.getElementById('closeCoord');
const headerTitleTxt = document.getElementsByClassName('esri-widget__heading')[0];
headerTitleTxt.insertAdjacentHTML('afterend', '<div id="closeCoord">X</div>');
on(closeBlock, 'click', function (event) {
let visible = coordinateWidget.visible;
coordinateWidget.visible = !visible;
});
}
}, 100);
});
Via the comment about mutation observers; I found the below solution to be an alternative... but looks like a ton of code.
(function(win) {
'use strict';
var listeners = [],
doc = win.document,
MutationObserver = win.MutationObserver || win.WebKitMutationObserver,
observer;
function ready(selector, fn) {
// Store the selector and callback to be monitored
listeners.push({
selector: selector,
fn: fn
});
if (!observer) {
// Watch for changes in the document
observer = new MutationObserver(check);
observer.observe(doc.documentElement, {
childList: true,
subtree: true
});
}
// Check if the element is currently in the DOM
check();
}
function check() {
// Check the DOM for elements matching a stored selector
for (var i = 0, len = listeners.length, listener, elements; i < len; i++) {
listener = listeners[i];
// Query for elements matching the specified selector
elements = doc.querySelectorAll(listener.selector);
for (var j = 0, jLen = elements.length, element; j < jLen; j++) {
element = elements[j];
// Make sure the callback isn't invoked with the
// same element more than once
if (!element.ready) {
element.ready = true;
// Invoke the callback with the element
listener.fn.call(element, element);
}
}
}
}
// Expose `ready`
win.ready = ready;
})(this);
Related
When I try the code referenced in SO #1, I get the console logging a blank string:
installChoices() {
var choices = this.game.page.options;
for (var i = 0; i < choices.length; i++) {
var choice = choices[i];
var choiceDiv = document.createElement("choice" + i);
choiceDiv.innerText = choice[0];
choiceDiv.onclick = function() {
console.log(this.id);
}
this.choicesContainer.appendChild(choiceDiv);
}
}
I want to bind to my class function clicked
installChoices() {
var choices = this.game.page.options;
for (var i = 0; i < choices.length; i++) {
var choice = choices[i];
var choiceDiv = document.createElement("choice" + i);
choiceDiv.innerText = choice[0];
choiceDiv.onclick = this.clicked;
this.choicesContainer.appendChild(choiceDiv);
}
}
clicked(e) {
console.log(e.parentNode); //this is undefined
console.log(e.srcElement);
}
But that shows undefined. When I log srcElement, I get the full element
<choice0>path 1</choice0>
I want to get just the div id when I click, so I can parse that and do logic.
I'd recommend the following approach, as it is the standard:
//assign the event
choiceDiv.addEventListener('click', clicked)
//define the listener
function clicked(event) {
console.log(event.currentTarget)
}
update:
I'm tempted to offer a fix to your code, because I don't think you're achieving what are you trying to actually do:
function installChoices() {
var choices = this.game.page.options;
for (var i = 0; i < choices.length; i++) {
var choice = choices[i];
var choiceDiv = document.createElement("div");
choiceDiv.id = "choice" + i;
choiceDiv.innerText = choice[0];
choiceDiv.addEventListener("click", clicked);
this.choicesContainer.appendChild(choiceDiv);
}
}
function clicked(ev) {
console.log(ev.currentTarget.id); //this will log "choice0"
}
Your "clicked" function are receiving an Event rather than a HTML Element. This is the default behavior when an onClick event triggered.
The click event have a srcElement property, indicating the source element the click event occurred upon. This event object have no parentNode property.
Use e.srcElement.parentNode instead.
BTW, in the SO #1 example, it assign "showIt(this)" to onClick, so browser pass "this", the target element rather than the event object, to the onClick function.
I have the following piece of code:
var i = 0;
for (var anchor in window.globals.html.anchors) {
var value = window.globals.html.anchors[anchor];
value.addEventListener('click', function(i) {
var currData = window.globals.data[i],
data_clientId = 0;
if (currData.client_product_id !== undefined) {
data_clientId = currData.getAttribute('data_clientId');
} else if (currData.product_id !== undefined) {
data_clientId = currData.product_id;
}
Statistics.send(data_clientId);
window.open(window.globals.data[i].url, '_blank');
}(i));
i++;
}
Which means I want to access global array by interator inside the click event listener. If I don't pass no i to the click event I get the maximum number possible in each iteration, as i would be a global variable.
But right here it seems that all iterations of click events are invoke before clicking anything, onload.
Why is that so, what's wrong?
Looks like you are calling the handler whilst trying to add it to addEventListener. You need to wrap it in a function thus creating a closure that encapsulates your i variable.
var i = 0;
for (var anchor in window.globals.html.anchors) {
var value = window.globals.html.anchors[anchor];
value.addEventListener('click', (function(i) {
return function() {
var currData = window.globals.data[i],
data_clientId = 0;
if (currData.client_product_id !== undefined) {
data_clientId = currData.getAttribute('data_clientId');
} else if (currData.product_id !== undefined) {
data_clientId = currData.product_id;
}
Statistics.send(data_clientId);
window.open(window.globals.data[i].url, '_blank');
}
}(i)));
i++;
}
Notice now that the function you are passing to addEventListener is invoked immediately and returns a function itself where your variable i is scoped.
Is it possible to simply add event listeners to certain elements to detect if their height or width have been modified? I'd like do this without using something intensive like:
$(window).resize(function() { ... });
Ideally, I'd like to bind to specific elements:
$("#primaryContent p").resize(function() { ... });
It seems like using a resize handler on the window is the only solution, but this feels like overkill. It also doesn't account for situations where an element's dimensions are modified programatically.
I just came up with a purely event-based way to detect element resize for any element that can contain children, I've pasted the code from the solution below.
See also the original blog post, which has some historical details. Previous versions of this answer were based on a previous version of the blog post.
The following is the JavaScript you’ll need to enable resize event listening.
(function(){
var attachEvent = document.attachEvent;
var isIE = navigator.userAgent.match(/Trident/);
var requestFrame = (function(){
var raf = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame ||
function(fn){ return window.setTimeout(fn, 20); };
return function(fn){ return raf(fn); };
})();
var cancelFrame = (function(){
var cancel = window.cancelAnimationFrame || window.mozCancelAnimationFrame || window.webkitCancelAnimationFrame ||
window.clearTimeout;
return function(id){ return cancel(id); };
})();
function resizeListener(e){
var win = e.target || e.srcElement;
if (win.__resizeRAF__) cancelFrame(win.__resizeRAF__);
win.__resizeRAF__ = requestFrame(function(){
var trigger = win.__resizeTrigger__;
trigger.__resizeListeners__.forEach(function(fn){
fn.call(trigger, e);
});
});
}
function objectLoad(e){
this.contentDocument.defaultView.__resizeTrigger__ = this.__resizeElement__;
this.contentDocument.defaultView.addEventListener('resize', resizeListener);
}
window.addResizeListener = function(element, fn){
if (!element.__resizeListeners__) {
element.__resizeListeners__ = [];
if (attachEvent) {
element.__resizeTrigger__ = element;
element.attachEvent('onresize', resizeListener);
}
else {
if (getComputedStyle(element).position == 'static') element.style.position = 'relative';
var obj = element.__resizeTrigger__ = document.createElement('object');
obj.setAttribute('style', 'display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; pointer-events: none; z-index: -1;');
obj.__resizeElement__ = element;
obj.onload = objectLoad;
obj.type = 'text/html';
if (isIE) element.appendChild(obj);
obj.data = 'about:blank';
if (!isIE) element.appendChild(obj);
}
}
element.__resizeListeners__.push(fn);
};
window.removeResizeListener = function(element, fn){
element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1);
if (!element.__resizeListeners__.length) {
if (attachEvent) element.detachEvent('onresize', resizeListener);
else {
element.__resizeTrigger__.contentDocument.defaultView.removeEventListener('resize', resizeListener);
element.__resizeTrigger__ = !element.removeChild(element.__resizeTrigger__);
}
}
}
})();
Usage
Here’s a pseudo code usage of this solution:
var myElement = document.getElementById('my_element'),
myResizeFn = function(){
/* do something on resize */
};
addResizeListener(myElement, myResizeFn);
removeResizeListener(myElement, myResizeFn);
Demo
http://www.backalleycoder.com/resize-demo.html
Here is a jQuery plugin with watch and unwatch methods that can watch particular properties of an element. It is invoked as a method of a jQuery object. It uses built-in functionality in browsers that return events when the DOM changes, and uses setTimeout() for browsers that do not support these events.
The general syntax of the watch function is below:
$("selector here").watch(props, func, interval, id);
props is a comma-separated string of the properties you wish to
watch (such as "width,height").
func is a callback function, passed the parameters watchData, index, where watchData refers to an object of the form { id: itId, props: [], func: func, vals: [] }, and index is the index of the changed property. this refers to the changed element.
interval is the interval, in milliseconds, for setInterval() in browsers that do not support property watching in the DOM.
id is an optional id that identifies this watcher, and is used to remove a particular watcher from a jQuery object.
The general syntax of the unwatch function is below:
$("selector here").unwatch(id);
id is an optional id that identifies this watcher to be removed. If id is not specified, all watchers from the object will be removed.
For those who are curious, the code of the plugin is reproduced below:
$.fn.watch = function(props, func, interval, id) {
/// <summary>
/// Allows you to monitor changes in a specific
/// CSS property of an element by polling the value.
/// when the value changes a function is called.
/// The function called is called in the context
/// of the selected element (ie. this)
/// </summary>
/// <param name="prop" type="String">CSS Property to watch. If not specified (null) code is called on interval</param>
/// <param name="func" type="Function">
/// Function called when the value has changed.
/// </param>
/// <param name="func" type="Function">
/// optional id that identifies this watch instance. Use if
/// if you have multiple properties you're watching.
/// </param>
/// <param name="id" type="String">A unique ID that identifies this watch instance on this element</param>
/// <returns type="jQuery" />
if (!interval)
interval = 200;
if (!id)
id = "_watcher";
return this.each(function() {
var _t = this;
var el = $(this);
var fnc = function() { __watcher.call(_t, id) };
var itId = null;
if (typeof (this.onpropertychange) == "object")
el.bind("propertychange." + id, fnc);
else if ($.browser.mozilla)
el.bind("DOMAttrModified." + id, fnc);
else
itId = setInterval(fnc, interval);
var data = { id: itId,
props: props.split(","),
func: func,
vals: []
};
$.each(data.props, function(i) { data.vals[i] = el.css(data.props[i]); });
el.data(id, data);
});
function __watcher(id) {
var el = $(this);
var w = el.data(id);
var changed = false;
var i = 0;
for (i; i < w.props.length; i++) {
var newVal = el.css(w.props[i]);
if (w.vals[i] != newVal) {
w.vals[i] = newVal;
changed = true;
break;
}
}
if (changed && w.func) {
var _t = this;
w.func.call(_t, w, i)
}
}
}
$.fn.unwatch = function(id) {
this.each(function() {
var w = $(this).data(id);
var el = $(this);
el.removeData();
if (typeof (this.onpropertychange) == "object")
el.unbind("propertychange." + id, fnc);
else if ($.browser.mozilla)
el.unbind("DOMAttrModified." + id, fnc);
else
clearInterval(w.id);
});
return this;
}
Yes it is possible. You will have to track all of the elements on load and store it. You can try out the demo here. In it, you don't have to use any libraries, but I used jQuery just to be faster.
First thing first - Store their initial size
You can do that by using this method:
var state = []; //Create an public (not necessary) array to store sizes.
$(window).load(function() {
$("*").each(function() {
var arr = [];
arr[0] = this
arr[1] = this.offsetWidth;
arr[2] = this.offsetHeight;
state[state.length] = arr; //Store all elements' initial size
});
});
Again, I used jQuery just to be fast.
Second - Check!
Of course you will need to check if it has been changed:
function checksize(ele) {
for (var i = 0; i < state.length; i++) { //Search through your "database"
if (state[i][0] == ele) {
if (state[i][1] == ele.offsetWidth && state[i][2] == ele.offsetHeight) {
return false
} else {
return true
}
}
}
}
Simply it will return false if it has not been change, true if it has been change.
Hope this helps you out!
Demo: http://jsfiddle.net/DerekL/6Evk6/
is there an angular-way to listen for events that occur one after the other? For example, I want to listen on a $rootScope for the $routeChangeSuccess and the $viewContentLoaded event. When the first event occurs, and after that, the second event occurs, I want to call a callback.
Is this possible? Or do I have to write it on my own? It would also be nice to configure if the order of the events is important or not. Or any ideas, how to implement such a behaviour?
Update
Because I haven't found anything on the web, I came up with my own solution. I think it works, but I don't know if there are any drawbacks with this method.
And any suggestions how to integrate this global into an AngularJS project? Or even as a bower component? Should I attach the function to a scope, or to the rootScope? Any help is appreciated!
Here is the Plunker link and the code: http://plnkr.co/edit/slfvUlFCh7fAlE4IPt8o?p=preview
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.events = [];
var successiveOn = function(events, eventScope, callback, orderImportant) {
// array for the remove listener callback
var removeListenerMethods = [];
// events array that is passed to the callback method
var eventsArr = [];
// how many events are fired
var eventCount = 0;
// how many events should be fired
var targetEventCount = events.length;
// track the next event, only for orderImportant==true
var nextEvent = events[0];
// iterate over all event strings
for (var i = 0; i < events.length; i++) {
var event = events[i];
// attach an $on listener, and store the remove listener function
var removeListener = eventScope.$on(event, function(evt) {
if (evt.name == nextEvent || !orderImportant) {
++eventCount;
nextEvent = events[eventCount];
eventsArr.push(evt);
// if all events has fired, call the callback method and reset
if (eventCount >= targetEventCount) {
callback(eventsArr);
nextEvent = events[0];
eventCount = 0;
eventsArr = [];
}
}
});
removeListenerMethods.push(removeListener);
}
// the return function is a anonymous function which calls all the removeListener methods
return function() {
for (var i = 0; i < removeListenerMethods.length; i++) {
removeListenerMethods[i]();
}
}
}
// order is unimportant
var removeListeners = successiveOn(["orderUnimportant1", "orderUnimportant2", "orderUnimportant3"], $scope, function(events) {
var str = "Events in order of trigger: ";
for (var i = 0; i < events.length; i++) {
str += events[i].name + ", ";
}
$scope.events.push(str);
}, false);
$scope.$broadcast("orderUnimportant1");
$scope.$broadcast("orderUnimportant2");
$scope.$broadcast("orderUnimportant3"); // Events were triggered 1st time
$scope.$broadcast("orderUnimportant3");
$scope.$broadcast("orderUnimportant2");
$scope.$broadcast("orderUnimportant1"); // Events were triggered 2nd time, order doesn't matter
removeListeners();
// order is important!
var removeListeners = successiveOn(["OrderImportant1", "OrderImportant2", "OrderImportant3"], $scope, function(events) {
var str = "Events in order of trigger: ";
for (var i = 0; i < events.length; i++) {
str += events[i].name + ", ";
}
$scope.events.push(str);
}, true);
$scope.$broadcast("OrderImportant1");
$scope.$broadcast("OrderImportant2");
$scope.$broadcast("OrderImportant3"); // Events were triggered
$scope.$broadcast("OrderImportant1");
$scope.$broadcast("OrderImportant3");
$scope.$broadcast("OrderImportant2"); // Events were NOT triggered
removeListeners();
});
Use the $q service aka the promise.
var routeChange = $q.defer();
var contentLoaded = $q.defer();
$rootScope.$on("$routeChangeSuccess", function() {
routeChange.resolve();
});
$rootScope.$on("$viewContentLoaded", function() {
contentLoaded.resolve();
});
$q.all([contentLoaded.promise, routeChange.promise]).then(function() {
//Fire your callback here
});
Specific Order:
routeChange.then(function() {
contentLoaded.then(function () {
//Fire callback
});
});
Without the nasty callback soup:
var routeChangeHandler = function() {
return routeChange.promise;
}
var contentLoadedHandler = function() {
return contentLoaded.promise
}
var callback = function() {
//Do cool stuff here
}
routeChangeHandler
.then(contentLoadedHandler)
.then(callback);
Thats pretty damn sexy...
More info on chained promises
I think Stens answer is the best way to tackle this if you want to do it with pure AngularJS facilities. However, often enough such cases indicate that you have to deal with more complex event stuf on a regular basis. If that's the case, I'd advice you to take a look at the Reactive Extensions and the rx.angular.js bridging library.
With the Reactive Extensions for JavaScript (RxJS) you can simplify the code to this:
Rx.Observable.combineLatest(
$rootScope.$eventToObservable('$routeChangeSuccess'),
$rootScope.$eventToObservable('$viewContentLoaded'),
Rx.helpers.noop
)
.subscribe(function(){
//do your stuff here
})
Here is my code snippt. But the code is breaking after inner for loop. But getting no error message.
Any idea?
Thanks.
var lastnames = document.getElementsByClassName('box_nachname');
var firstnames = document.getElementsByClassName('box_vorname');
var teilnehmer = document.getElementsByClassName('select');
observers = [];
// iterate over nachname array.
for (var i = 0; i < lastnames.length; i++) {
// Create an observer instance.
observers[i] = new Observer();
// Subscribe oberser object.
for(idx in teilnehmer) {
if(teilnehmer[idx].id.split("_")[0].toLowerCase() !== "zl") {
var anynum = function(element) {
observers[i].subscribe(element, updateTeilnehmerSelectbox);
}(teilnehmer[idx]);
}
}
//on blur the Observer fire the updated info to all the subscribers.
var anynumNachname = function(j, element, value, observer) {
cic.addEvent(lastnames[j], 'blur', observer.fire(element, value));
} (i, lastnames[i], lastnames[i].value, observers[i]);
cic.addEvent(firstnames[i], 'blur', function(element, value, observer) {observer.fire(element, value)}(lastnames[i], lastnames[i].value, observers[i]));
}
You're using the loop variable "i" in the "addEvent" call. That won't work properly because every one of the event handlers will share the same "i" and so each will only see the last value that "i" was set to.
cic.addEvent(firstnames[i], 'blur', (function(index) {
return function(element, value, observer) {
observer.fire(element, value)}(lastnames[index], lastnames[index].value, observers[index]);
};
})(i));
Also, though I'm not sure this is necessary, I'd put the function you're calling for "anynumNachname" in parenthesis:
var anynumNachname = (function(j, element, value, observer) {
cic.addEvent(lastnames[j], 'blur', observer.fire(element, value));
})(i, lastnames[i], lastnames[i].value, observers[i]);