I am trying to observe a change to
<div id="one" class="elem" data-result="inr"></div>
data-result attribute in this div.
Most of the older methods seem to have been depreciated. I realised that MutationObserver is the best way to proceed. and wrote one on my own which did not work. I found the onattrchange.js which is similar.
With this I am trying to solve the problem I face.
I have created this jsfiddle which is able to detect input changes (using $(document).on), but not the changes to data-result attribute.
I have tried two ways... (1) ('body').attrchange
(2) $(document).on('attrchange',...)
I am not sure why this does not work. ( I am also pretty new to js and jquery, learning by coding)
edit: I have found a similar fiddle which does exactly what I want to. I am doing something similar but I guess I have made some small mistake.
I found the problem with the code.
The onattrchange.js does not seem to identify changes made using
$("element").data("result",$(this).val());
It detects
$("element").attr("data-result",$(this).val());
I have added the working fiddle.
I was looking for same solution, but didn't wanted to use any 3rd party library.
After digging out little bit on google as well as on this site, I found solution on https://stackoverflow.com/a/58501827/6879925
// code to change image src in each 1000ms.
count = 0;
setInterval(function () {
var sourceElement = document.querySelector('#div1');
sourceElement.setAttribute('data-src', 'something' + count);
sourceElement.innerHTML = 'something' + count;
count++;
}, 2000);
function startMutationObserver(tNode, c) {
// Select the node that will be observed for mutations
const targetNode = tNode ? tNode : document;
// Options for the observer (which mutations to observe)
const config = c ? c : {
attributes: true,
childList: true,
subtree: true
};
// Callback function to execute when mutations are observed
const callback = function (mutationsList, observer) {
for (let mutation of mutationsList) {
if (mutation.type === 'childList') {
targetNode.dispatchEvent(new CustomEvent('newChild', {
detail: mutation
}));
} else if (mutation.type === 'attributes') {
targetNode.dispatchEvent(new CustomEvent('attributeChange', {
detail: mutation
}));
}
}
}
// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);
// Start observing the target node for configured mutations
observer.observe(targetNode, config);
// Later, you can stop observing
// observer.disconnect();
}
// call this function to start observing DOM element change
startMutationObserver(document);
// code to listen custom event and filter custom event as per requirement
document.addEventListener('attributeChange', function (e) {
// console.log(e);
const ele = e.detail;
if (ele.target.matches('div[data-src]') && ele.attributeName == 'data-src') {
var src = e.detail.target.getAttribute('data-src');
console.log('new src=', src);
}
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id='div1' data-src="something">Something....</div>
</body>
</html>
I think this is one of the best solution I ever got for attribute change detection in HTML/javaScript.
Related
My Doffy img just won't hide and I don't know what to do.
I've been trying to add values and remove them and all, but it just won't work.
If anyone could help me I would highly appreciate it.
HTML:
<html lang="en">
<head>
<link rel="stylesheet" href="opdracht1.css">
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=q, initial-scale=1.0">
<title>Opdracht 1</title>
</head>
<body>
<div id="buttons">
<button id="pistol">Pistol attack (3 DMG)</button>
<button id="bazooka">Bazooka attack (7 DMG)</button>
<button id="katana">Katana attack (10 DMG)</button>
<button id="punch">Punch attack (1 DMG)</button>
</div>
<br>
<img src="doffy.jpg" id="doffy">
<p>Donquixote Doflamingo:</p>
<progress min = 0 max=150 value=150 name=hel id=progress></progress>
<script src="opdracht1.js"></script>
</body>
</html>
JavaScript:
document.getElementById('progress').value-=3;
console.log('You hit Doflamingo with your pistol attack. It caused 3 damage.');
});
document.querySelector('#bazooka').addEventListener('click', () => {
document.getElementById('progress').value-=7;
console.log('You hit Doflamingo with your bazooka attack. It caused 7 damage.');
});
document.querySelector('#katana').addEventListener('click', () => {
document.getElementById('progress').value-=150;
console.log('You hit Doflamingo with your katana attack. It caused 10 damage.');
});
document.querySelector('#punch').addEventListener('click', () => {
document.getElementById('progress').value-=1;
console.log('You hit Doflamingo with your punch attack. It caused 1 damage.');
});
var progress = document.getElementById('progress') = 150;
if (progress = 0) {
document.getElementById('doffy').style.display = 'none';
}
You have both syntax and logic issues.
I'll start with the beginning of the JS file, you wrote this:
document.getElementById('progress').value-=3;
console.log('You hit Doflamingo with your pistol attack. It caused 3 damage.');
});
This is a syntax error and I believe you meant to write this:
document.querySelector('#pistol').addEventListener('click', () => {
document.getElementById('progress').value-=3;
console.log('You hit Doflamingo with your pistol attack. It caused 3 damage.');
});
Moving on to another syntax error:
var progress = document.getElementById('progress') = 150;
You're already assigning a value to your variable 'progress', you can't assign another value to it, and besides it's the value of the progress element that you need to assign this value, since you already assign a default value in the HTML, there is no need to reassign that again, so maybe just this:
var progress = document.getElementById('progress');
Now to the logic error, you're writing the following:
if (progress = 0) {
document.getElementById('doffy').style.display = 'none';
}
Your JS file will be executed only once when the HTML is loading it, meaning that this code will run, check if the value of the progress is 0(which is obviously not, cause it's 150 by default) and that's it.
What you need to do here is monitor the value of the progress and perform your needed task there.
I'm not aware of onchange event listeners for progress value changes, so you might need to do that manually(if I'm wrong, please correct me!).
This could be done in the following way:
let changeEvent = new Event('change');
So let's start with monitoring the change of the progress element, this could be done like so:
progress.AddEventListener('change', function() {
if (progress.value <= 0) {
document.getElementById('doffy').style.display = 'none';
console.log("doffy died.")
}
});
Then each time we change a value we will need to also dispatch the change event:
document.querySelector('#bazooka').addEventListener('click', () => {
document.getElementById('progress').value-=7;
console.log('You hit Doflamingo with your bazooka attack. It caused 7 damage.');
progress.dispatchEvent(chagenEvent);
});
So now we can monitor the value changes and we have no syntax errors, this should work, but we have some bad practices in our code, do you see how we repeat our lines over and over? Maybe we will declare the progress element at the beginning and use arrays and loops to improve that?
let progress = document.getElementById('progress');
let changeEvent = new Event('change');
let attacksInfo = [
{ "name": "pistol", "damage": 3 },
{ "name": "bazooka", "damage": 7 },
{ "name": "katana", "damage": 150 },
{ "name": "punch", "damage": 1 }
];
attacksInfo.forEach(weapon => {
document.querySelector(`#${weapon.name}`).addEventListener('click', () => {
progress.value -= weapon.damage;
console.log(`You hit Doflamingo with your ${weapon.name} attack. It caused ${weapon.damage} damage.`);
progress.dispatchEvent(changeEvent);
})
})
progress.addEventListener('change', (e) => {
if (progress.value <= 0) {
document.getElementById('doffy').style.display = 'none';
console.log("doffy died.")
}
});
We used an array of JSON objects specified with the weapon name and damage, and then we iterate over the array and assigning the event listener to each weapon.
Maybe there are some more advanced concepts here which you haven't learned yet, let me know if there's anything that's not clear for you.
I've been working on adding onclick event in template literals with plain javascript (without jquery only javascript). As you can see the result html knows that onclick event on each div has function which will alert as I click but when I click, it didn't respond. It seems like suppose to work but somehow it is not working.
I got lots of help from Stackoverflow but most of the anwser was using global function. but I don't personally want to use global function since sometimes it cause some trouble.
so how can I actually pass the function into onclick event by using template literals?
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="ul"></div>
</body>
<script src="/index.js"></script>
</html>
index.js
function test() {
const list = [
{ number: 1, check: () => alert("1") },
{ number: 2, check: () => alert("2") },
{ number: 3, check: () => alert("3") },
];
const $list = list.reduce((acc, item) =>
acc + `<div onclick='${item.check}'>${item.number}</div>`,""
);
const $ul = document.querySelector("#ul");
$ul.innerHTML = $list;
}
test();
result
Instead of building the DOM via HTML strings, create actual DOM elements.
const $ul = document.querySelector("#ul");
for (const item of list) {
const element = document.createElement('div');
element.textContent = item.number;
element.onclick = item.check;
$ul.appendChild(element);
}
I have a need to detect when an observable (observedEvents) has been subscribed to, and then subscribe to another observable (triggerEvent). I don't want to subscribe to triggerEvent manually, but only once and when observedEvents has a subscription.
Here is some code explaining what I am looking for:
// This just emits events
let emitting = new EventEmitter();
// This is the main Observable which someone else might
// have access to
let observedEvents = Rx.Observable.merge(
Rx.Observable.fromEvent(emitting, 'aba'),
Rx.Observable.fromEvent(emitting, 'bob')
)
// This trigger should get a subscription if observedEvents
// has one, i.e. when I subscribe to observedEvents
// that subscription activates this trigger
// I have made an attempt at this by calling skipUntil
// this however skips one event, but I don't want that
let triggerEvent = Rx.Observable.merge(
// these actions are things that can
// happen when the trigger is active
Rx.Observable.of('a').delay(200),
Rx.Observable.of('b').delay(400),
Rx.Observable.of('c').delay(600)
)
.skipUntil(observedEvents);
// Something else should be used to activate trigger
// I don't want to do this part manually
triggerEvent.subscribe(val => {
console.log(`Do something fancy with ${val}`);
});
//----------------------------------------------------
// Somewhere else in the code...
//----------------------------------------------------
observedEvents.subscribe(evt => {
console.log(`Some event: ${evt}`);
});
// At this point I want triggerEvent to become active
// because observedEvents has a subscription
setTimeout(() => {
emitting.emit('bob', 'world');
setTimeout(() => emitting.emit('aba', 'stackoverflow!'), 500);
}, 200);
<!DOCTYPE html>
<html>
<head>
<script src="https://npmcdn.com/#reactivex/rxjs#5.0.0-beta.7/dist/global/Rx.umd.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/EventEmitter/5.1.0/EventEmitter.min.js"></script>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
</body>
</html>
Is this possible?
I hope that explains what I'm looking for.
As I'm writing this, I'm thinking a solution with Subjects is probably what I need. I'm not sure, but I just need a nudge in the right direction or a solution if possible.
For rxjs > v7, see this answer
Answer
Sure enough I was right about using Subjects. The key was the observers list for Subject. Here is what I finally did:
let emitting = new EventEmitter();
let sub = new Rx.Subject();
// return this to users
let myGlobalSub = sub.merge(Rx.Observable.of(1, 2, 3));
// For internal use
let myObservers = Rx.Observable.fromEvent(emitting, 'evt');
console.log(`The number of subscribers is ${sub.observers.length}`);
// Only do something if myGlobalSub has subscribers
myObservers.subscribe(l => {
if (sub.observers.length) { // here we check observers
console.log(l);
}
});
// Somewhere in the code...
emitting.emit('evt', "I don't want to see this"); // No output because no subscribers
myGlobalSub.subscribe(l => console.log(l)); // One sub
emitting.emit('evt', 'I want to see this'); // Output because of one sub
console.log(`The number of subscribers is ${sub.observers.length}`);
<!DOCTYPE html>
<html lang=en>
<head>
<script src="https://unpkg.com/rxjs#5.5.11/bundles/Rx.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/EventEmitter/5.2.5/EventEmitter.min.js"></script>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
</body>
</html>
The answer by #smac89 use the observersproperty which has been deprecated in RxJS 7 and is scheduled to be removed in version 8.
If you only want to know if an Observable has been subscribed to, you may use the observed boolean property instead.
There is currently no real equivalent to the observers array.
let emitting = new EventEmitter();
let sub = new rxjs.Subject();
// return this to users
let myGlobalSub = rxjs.merge(sub,rxjs.of(1, 2, 3));
// For internal use
let myObservers = rxjs.fromEvent(emitting, 'evt');
console.log(`Has the subject been subscribed to ? ${sub.observed}`);
// Only do something if myGlobalSub has subscribers
myObservers.subscribe(l => {
if (sub.observed) { // here we check observers
console.log(l);
}
});
// Somewhere in the code...
emitting.emit('evt', "I don't want to see this"); // No output because no subscribers
myGlobalSub.subscribe(l => console.log(l)); // One sub
emitting.emit('evt', 'I want to see this'); // Output because of one sub
console.log(`Has the subject been subscribed to ? ${sub.observed}`);
<!DOCTYPE html>
<html lang=en>
<head>
<script src="https://unpkg.com/rxjs#%5E7/dist/bundles/rxjs.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/EventEmitter/5.2.5/EventEmitter.min.js"></script>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
</body>
</html>
I'm creating an HTML element using Polymer, and I want it to be able to work with an ES6 class I've written. Therefore, I need to import the class first and then register the element, which is what I do:
(function() {
System.import('/js/FoobarModel.js').then(function(m) {
window.FoobarModel = m.default;
window.FoobarItem = Polymer({
is: 'foobar-item',
properties: {
model: Object // instanceof FoobarModel === true
},
// ... methods using model and FoobarModel
});
});
})();
And it works well. But now I want to write a test HTML page to display my component with some dummy data:
<!DOCTYPE html>
<html lang="en">
<head>
<script src="/bower_components/webcomponentsjs/webcomponents.js"></script>
<script src="/bower_components/system.js/dist/system.js"></script>
<script>
System.config({
map:{
traceur: '/bower_components/traceur/traceur.min.js'
}
});
</script>
<link rel="import" href="/html/foobar-item.html">
</head>
<body>
<script>
(function() {
var data = window.data = [
{
city: {
name: 'Foobar City'
},
date: new Date('2012-02-25')
}
];
var view;
for (var i = 0; i < data.length; i++) {
view = new FoobarItem();
view.model = data[i];
document.body.appendChild(view);
}
})();
</script>
</body>
</html>
Which isn't working for one simple reason: the code in the <script> tag is executed before Polymer registers the element.
Thus I'd like to know if there's a way to load the ES6 module synchronously using System.js or even better, if it's possible to listen to a JavaScript event for the element registration (something like PolymerElementsRegistered)?
I've tried the following without success:
window.addEventListener('HTMLImportsLoaded', ...)
window.addEventListener('WebComponentsReady', ...)
HTMLImports.whenReady(...)
In the app/scripts/app.js script from the polymer starter kit, they use auto-binding template and dom-change event
// Grab a reference to our auto-binding template
var app = document.querySelector('#app');
// Listen for template bound event to know when bindings
// have resolved and content has been stamped to the page
app.addEventListener('dom-change', function() {
console.log('Our app is ready to rock!');
});
Also check this thread gives alternatives to the polymer-ready event.
Using Polymer 1.0 I'm trying to bind to an attribute of a custom element, and just display it.
The custom element is in fact an <iron-input> list, that has an add and a delete button. I'd like to reflect any change in that list to the host. It also has a minItemSize attribute meaning it has at least this many elements. So I added a check to the observer, adding extra elements in case it goes under this number.
But when I bind to the attribute that holds the list, things get out of sync, and I can delete all of the inputs from the ui.
I have two <dyn-inputlist> elements. In one of them I don't bind to the data
attribute, in the other I do.
The first one behaves as expected: adds and removes on the button click.
The other doesn't work, because you can remove all input boxes. Even though the data itself is updated, and filled with extra items, for some reason the UI doesn't reflect this. (Checking the data property of the element does show that it has the correct number of items)
I also expect that if I set data={{myData}} on both dyn-inputlist element, they always display the same thing. But pressing add/remove buttons randomly on either component gets them out of sync.
Am I missing something?
Thanks in advance.
index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="bower_components/webcomponentsjs/webcomponents.js"></script>
<link rel="import" href="components/dyn-inputlist.html"/>
</head>
<body>
<template is="dom-bind">
<dyn-inputlist min-item-size="4"></dyn-inputlist>
<div>{{mydata}}</div>
<dyn-inputlist min-item-size="4" data="{{mydata}}"></dyn-inputlist>
</template>
</body>
</html>
dyn-inputlist.html:
<link rel="import" href="../../polymer/polymer.html">
<link rel="import" href="../../iron-input/iron-input.html">
<dom-module id="dyn-inputlist">
<template>
<button on-click="removeItem">x</button>
<button on-click="addItem">+</button>
<template is="dom-repeat" items="{{data}}">
<div>
<span>{{index}}</span>
<input is="iron-input" bind-value="{{item.content}}">
</div>
</template>
</template>
<script>
Polymer({
is: 'dyn-inputlist',
properties: {
minItemSize: {
type: Number,
notify: true,
value: 1
},
data: {
type: Array,
reflectToAttribute: true,
notify: true,
value: function () {
return []
}
}
},
observers: ['_dataChanged(data.*)'],
addItem: function (e) {
this.unshift('data', {content: ""});
this.reflectPropertyToAttribute('data')
},
removeItem: function (e) {
this.shift('data');
this.reflectPropertyToAttribute('data')
},
_dataChanged: function (e) {
if (this.data != null) {
while (this.data.length < this.minItemSize) {
this.push('data', {content: ""})
}
} else {
this.data = [{content: ""}];
}
this.reflectPropertyToAttribute('data');
}
});
</script>
</dom-module>
EDIT:
This is the live code: http://jsbin.com/poquke/1/edit?html,output
I have played around a bit with your code and I noticed that it will work if you wrap the code in your changed handler in an async function. This fixed both issues that you described.
_dataChanged: function (e) {
this.async(function(){
if (this.data != null) {
while (this.data.length < this.minItemSize) {
this.push('data', {content: ""})
}
} else {
this.data = [{content: ""}];
}
});
}
I don't have a perfect explanation for this behaviour. I assume it is related somehow to the way Polymer handles the observation for changes. Each time you push to the data array in the changed handler, this in fact changes data and should in turn trigger the handler again.
No async is required if you simplify.
Here is the simplified code, this removes the repeated calls to _dataChanged when you push the minimum values, and allows polymer's built-in eventing system to take care of updating and notifying the other elements. A function: _createNewItem() is for creating an object. This simplifies where item object creation is handled.
http://jsbin.com/vemita/6/edit?html,output
The link and URL references have changed from the sample code in the question above to conform to the polymer element and demo page standards to be used with polyserve.
I've commented on your original code for why each line should or shouldn't be there. this includes the reason for the changes to _dataChanged
http://jsbin.com/ponafoxade/1/edit?html,output