Where is the error in this code - javascript

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]);

Related

when element exists without using a setInterval

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);

Are lapsed listeners preventable in javascript?

My question is really "Is the lapsed listener problem preventable in javascript?" but apparently the word "problem" causes a problem.
The wikipedia page says the lapsed listener problem can be solved by the subject holding weak references to the observers. I've implemented that before in Java and it works nicely, and I thought I'd implement it in Javascript, but now I don't see how. Does javascript even have weak references? I see there are WeakSet and WeakMap which have "Weak" in their names, but they don't seem to be helpful for this, as far as I can see.
Here's a jsfiddle showing a typical case of the problem.
The html:
<div id="theCurrentValueDiv">current value: false</div>
<button id="thePlusButton">+</button>
The javascript:
'use strict';
console.log("starting");
let createListenableValue = function(initialValue) {
let value = initialValue;
let listeners = [];
return {
// Get the current value.
get: function() {
return value;
},
// Set the value to newValue, and call listener()
// for each listener that has been added using addListener().
set: function(newValue) {
value = newValue;
for (let listener of listeners) {
listener();
}
},
// Add a listener that set(newValue) will call with no args
// after setting value to newValue.
addListener: function(listener) {
listeners.push(listener);
console.log("and now there "+(listeners.length==1?"is":"are")+" "+listeners.length+" listener"+(listeners.length===1?"":"s"));
},
};
}; // createListenable
let theListenableValue = createListenableValue(false);
theListenableValue.addListener(function() {
console.log(" label got value change to "+theListenableValue.get());
document.getElementById("theCurrentValueDiv").innerHTML = "current value: "+theListenableValue.get();
});
let nextControllerId = 0;
let thePlusButton = document.getElementById("thePlusButton");
thePlusButton.addEventListener('click', function() {
let thisControllerId = nextControllerId++;
let anotherDiv = document.createElement('div');
anotherDiv.innerHTML = '<button>x</button><input type="checkbox"> controller '+thisControllerId;
let [xButton, valueCheckbox] = anotherDiv.children;
valueCheckbox.checked = theListenableValue.get();
valueCheckbox.addEventListener('change', function() {
theListenableValue.set(valueCheckbox.checked);
});
theListenableValue.addListener(function() {
console.log(" controller "+thisControllerId+" got value change to "+theListenableValue.get());
valueCheckbox.checked = theListenableValue.get();
});
xButton.addEventListener('click', function() {
anotherDiv.parentNode.removeChild(anotherDiv);
// Oh no! Our listener on theListenableValue has now lapsed;
// it will keep getting called and updating the checkbox that is no longer
// in the DOM, and it will keep the checkbox object from ever being GCed.
});
document.body.insertBefore(anotherDiv, thePlusButton);
});
In this fiddle, the observable state is a boolean value, and you can add and remove checkboxes that view and control it, all kept in sync by listeners on it.
The problem is that when you remove one of the controllers, its listener doesn't go away: the listener keeps getting called and updating the controller checkbox and prevents the checkbox from being GCed, even though the checkbox is no longer in the DOM and is otherwise GCable. You can see this happening in the javascript console since the listener callback prints a message to the console.
What I'd like instead is for the controller DOM node and its associated value listener to become GCable when I remove the node from the DOM. Conceptually, the DOM node should own the listener, and the observable should hold a weak reference to the listener. Is there a clean way to accomplish that?
I know I can fix the problem in my fiddle by making the x button explicitly remove the listener along with the DOM subtree, but that doesn't help in the case that some other code in the app later removes part of the DOM containing my controller node, e.g. by executing document.body.innerHTML = ''. I'd like set things up so that, when that happens, all the DOM nodes and listeners I created get released and become GCable. Is there a way?
Custom_elements offer a solution to the lapsed listener problem. They are supported in Chrome and Safari and (as of Aug 2018), are soon to be supported on Firefox and Edge.
I did a jsfiddle with HTML:
<div id="theCurrentValue">current value: false</div>
<button id="thePlusButton">+</button>
And a slightly modified listenableValue, which now has the ability to remove a listener:
"use strict";
function createListenableValue(initialValue) {
let value = initialValue;
const listeners = [];
return {
get() { // Get the current value.
return value;
},
set(newValue) { // Set the value to newValue, and call all listeners.
value = newValue;
for (const listener of listeners) {
listener();
}
},
addListener(listener) { // Add a listener function to call on set()
listeners.push(listener);
console.log("add: listener count now: " + listeners.length);
return () => { // Function to undo the addListener
const index = listeners.indexOf(listener);
if (index !== -1) {
listeners.splice(index, 1);
}
console.log("remove: listener count now: " + listeners.length);
};
}
};
};
const listenableValue = createListenableValue(false);
listenableValue.addListener(() => {
console.log("label got value change to " + listenableValue.get());
document.getElementById("theCurrentValue").innerHTML
= "current value: " + listenableValue.get();
});
let nextControllerId = 0;
We can now define a custom HTML element <my-control>:
customElements.define("my-control", class extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
const n = nextControllerId++;
console.log("Custom element " + n + " added to page.");
this.innerHTML =
"<button>x</button><input type=\"checkbox\"> controller "
+ n;
this.style.display = "block";
const [xButton, valueCheckbox] = this.children;
xButton.addEventListener("click", () => {
this.parentNode.removeChild(this);
});
valueCheckbox.checked = listenableValue.get();
valueCheckbox.addEventListener("change", () => {
listenableValue.set(valueCheckbox.checked);
});
this._removeListener = listenableValue.addListener(() => {
console.log("controller " + n + " got value change to "
+ listenableValue.get());
valueCheckbox.checked = listenableValue.get();
});
}
disconnectedCallback() {
console.log("Custom element removed from page.");
this._removeListener();
}
});
The key point here is that disconnectedCallback() is guaranteed to be called when the <my-control> is removed from the DOM whatever reason. We use it to remove the listener.
You can now add the first <my-control> with:
const plusButton = document.getElementById("thePlusButton");
plusButton.addEventListener("click", () => {
const myControl = document.createElement("my-control");
document.body.insertBefore(myControl, plusButton);
});
(This answer occurred to me while I was watching this video, where the speaker explains other reasons why custom elements could be useful.)
You can use mutation observers which
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.
An example of how this can be used can be found in the code for on-load
if (window && window.MutationObserver) {
var observer = new MutationObserver(function (mutations) {
if (Object.keys(watch).length < 1) return
for (var i = 0; i < mutations.length; i++) {
if (mutations[i].attributeName === KEY_ATTR) {
eachAttr(mutations[i], turnon, turnoff)
continue
}
eachMutation(mutations[i].removedNodes, function (index, el) {
if (!document.documentElement.contains(el)) turnoff(index, el)
})
eachMutation(mutations[i].addedNodes, function (index, el) {
if (document.documentElement.contains(el)) turnon(index, el)
})
}
})
observer.observe(document.documentElement, {
childList: true,
subtree: true,
attributes: true,
attributeOldValue: true,
attributeFilter: [KEY_ATTR]
})
}

AngularJS: Listen to events, one after the other

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
})

Creating events in Javascript

If I have a list of functions that are serving as event subscriptions, is there an advantage to running through them and calling them with .call() (B below), or more directly (A)? The code is below. The only difference I can see is that with .call you can control what this is set to. Is there any other difference besides that?
$(function() {
eventRaiser.subscribe(function() { alert("Hello"); });
eventRaiser.subscribe(function(dt) { alert(dt.toString()); });
eventRaiser.DoIt();
});
var eventRaiser = new (function() {
var events = [];
this.subscribe = function(func) {
events.push(func);
};
this.DoIt = function() {
var now = new Date();
alert("Doing Something Useful");
for (var i = 0; i < events.length; i++) {
events[i](now); //A
events[i].call(this, now); //B
}
};
})();
No, there is no other difference. If you follow the second approach though, you can extend subscribe so that the "clients" can specify the context to be used:
this.subscribe = function(func, context) {
events.push({func: func, context: context || this});
};
this.DoIt = function() {
var now = new Date();
alert("Doing Something Useful");
for (var i = 0; i < events.length; i++) {
events[i].func.call(events[i].context, now);
}
};
If you decide to go with method B, you can allow the function that subscribes to the event, to perform event specific functions.
For instance, you could use this.getDate() inside your function (you would have to create the method) instead of passing the date as a parameter. Another helpful method inside an Event class would be this.getTarget().

Javascript: Check if Element has Changed

I want to know, if it's possible, how to check in javascript if an element has changed or an attribute of it?
I mean something like window.onhashchange for an element something like:
document.getElementById("element").onElementChange = function();
As I know onchange is something like this, but will it work if I want to know in this way:
var element = {};
element.attribute = result;
element.attribute.onchange = function();
As far as I understand you want onChange on javascript object Properties. The answer is no, it doesn't exist as far as I know.
But you can make a setter function like this (As a proof of concept):
var element = {};
element.setProperty = function(property, value) {
if (typeof(element.onChange) === 'function') {
element.onChange(property, element[property], value);
}
element[property] = value;
};
element.onChange = function(property, oldValue, newValue) {
alert(property + ' changed from ' + oldValue + ' to ' + newValue);
};
element.setProperty('something', 'Hello world!');
now you get an alert box with 'something changed from undefined to Hello World!'. And (element.something === 'Hello World!') will return true.
if you now call:
element.setProperty('something', 'Goodbye world!');
you get an alert box with 'something changed from Hello World! to Goodbye World!'.
Off course you have to set the property only via the setProperty method in all of your code if you want to capture this event!
Edit:
At some time in the future, you might be able to use Object.observe().
Edit 2:
Now there's also proxies.
You might consider a mutation observer.
to do this you first create a callback (fired when the dom element changes)
assign it to an observer var observer = new MutationObserver(callback);
Then tell the observer what to watch observer.observe('<div></div>', observerOptions);
From:
Mozilla page on Mutation Observers
I guess you'd need a way to capture the event which triggered the change in attribute rather than the change in attribute. The change in attribute could only either be due to your CSS or your javascript, both being manifestations of the user's actions.
I believe there is no such event. However, you can use setInterval or setTimeout to watch for element changes and use it to react accordingly.
I did this. It works pretty well. I would have used the setProperty method if I had known.
function searchArray(searchValue, theArray, ChangeValue){
for (var i=0; i < theArray.length; i++) {
if (theArray[i].id === searchValue) {
theArray[i].changed = ChangeValue;
}
}
}
function getArrayIindex(elementid, theArray){
for (var i=0; i < theArray.length; i++) {
if (theArray[i].id === elementid) {
return i;
}
}
}
function setInputEvents(hiddenObject) {
var element;
for (var i = 0; i < document.forms[0].length; i++) {
element = document.forms[0].elements[i];
//Check to see if the element is of type input
if (element.type in { text: 1, password: 1, textarea: 1 }) {
arrFieldList.push({
id: element.id,
changed:false,
index: i,
});
element.onfocus = function () {
if (!arrFieldList[getArrayIindex(this.id, arrFieldList)].changed) {
hiddenObject.value = this.value;
this.value = '';
}
}
element.onblur = function () {
if (this.value == '') {
this.value = hiddenObject.value;
}
}
element.onchange = function () {
searchArray(this.id, arrFieldList, true);
}
}
}

Categories

Resources