I have a html template similar to the below code
<a-entity id="id1">
<a-entity template="src: t1.template;type:nunjucks">
</a-entity>
</a-entity>
t1.template
<a-entity id="id2">
{% for i in 4 %}
<a-entity template="src: t2.template; type: nunjucks"></a-entity>
{% endfor %}
</a-entity>
t2.template
<a-entity id="id3" myComponent="x:4">
<a-entity>...
<a-entity>...</a-entity>
</a-entity>
<a-entity>
Entity components are displayed in the screen as required. I now want to remove the entire id1 when user clicks on any of the 4 component in id3. My component code is as below
AFRAME.registerComponent('myComponent', {
schema: {
x: {type: 'number', default: 0}
},
update: function () {
//set some attribute for entities inside id3
//adding event listener to id3.
this.el.addEventListener('click', function () {
setTimeout(function () {
var categoryEl = scene.querySelectorAll('#id3');
totalCategory = categoryEl.length;
for(i=0;i<totalCategory;i++){
categoryEl[i].removeAttribute('myComponent');
removeAttributeCount++;
}
}, 1500);
});
},
remove: function () {
//To check whether component is removed from all element
if(removeAttributeCount == totalCategory){
var id1 = this.el.sceneEl.querySelector('#id1');
id1.parentNode.removeChild(id1);
}
}
});
I am getting error as
Uncaught TypeError: Cannot convert undefined or null to object
at NewComponent.remove (https://cdn.rawgit.com/donmccurdy/aframe-extras/v3.2.7/dist/aframe-extras.js:4742:16)
at HTMLElement.value (https://aframe.io/releases/0.5.0/aframe.js:71889:17)
at bound (https://aframe.io/releases/0.5.0/aframe.js:76993:17)
at Array.forEach (native)
at HTMLElement.value (https://aframe.io/releases/0.5.0/aframe.js:71567:36)
at NewComponent.remove (http://localhost:63342/myProj1HTML/Shop/ShopTrail-1/index.js:91:32)
at HTMLElement.value (https://aframe.io/releases/0.5.0/aframe.js:71889:17)
at HTMLElement.value (https://aframe.io/releases/0.5.0/aframe.js:71970:16)
at HTMLElement.value (https://aframe.io/releases/0.5.0/aframe.js:72095:14)
at HTMLElement.value (https://aframe.io/releases/0.5.0/aframe.js:72015:14)
All the elements are captured perfectly. But error occurs on removing the child from parent node.
Someone please help to get this working. Thanks in advance
Related
Hello I am new in WebVR and I will be pleased If you can help me.
I want to update my AFRAME init() variable after onchange my input in test.js and callback aframe(rebuild Aframe component). I would like do somethink like useEffectfrom React.
I have two file test.js and aframe.js
test.js
const el = document.getElementById("inputItem")
el.setAttribute("type", "date")
el.addEventListener('input', (e)=> { console.log(e.target.value) }
//other logic
aframe.js
AFRAME.registerComponent("test", {
init(){
this.input = document.getElementById("inputItem")
this.input.addEventListener('input', (e)=> { console.log("input changed", e.target.value) } );
console.log("my input value", this.input)
}
update(){
if( this.input.value != document.getElementById("inputItem").value )
{this.input = "New input"
console.log("input changed", this.input)
}
}
// some code here to build aframe element and append it to html
}
so my console.log function inside aframe work only one time. After onchange input my update function doesn't response. I know the problem is aframe component is calling only one time.
I tried this answer and read about component. I don't know how to recall/rebuild afraime component after changing my input.
Looks like you've mixed two ways of doing this:
1. Using setAttribute() to trigger the update() functions.
The update function in the custom component is called:
once after init
after any change made with setAttribute(<component_name>)
Here's an example of triggering update with setAttribute from an "external" js file (same behaviour, as long as the script is loaded after the <input> element is attached) :
const input = document.getElementById("logtext"); // grab the input
input.addEventListener("input", e => {
// on each 'input' event
const text = e.target.value; // extract the text
// set the [logger] "text" attribute, to trigger the update
document.querySelector("[logger]").setAttribute("logger", "text", text);
})
<script src="https://aframe.io/releases/1.4.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent("logger", {
schema: {
text: {
default: ""
}
},
// on any update
update: function() {
this.el.setAttribute("text", "value", this.data.text); // update the text component
}
})
</script>
<div style="position: fixed; z-index: 999">
<label for="logtext">Text to be rendered:</label>
<input type="text" id="logtext" name="logtext"><br><br>
</div>
<a-scene>
<a-text position="-1 1.75 -3" value="" color="black" logger></a-text>
<a-box position="0 1 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
</a-scene>
2. Using event listeners.
You can add any logic you want to the event listener callbacks. It can modify you own component, it can modify other components.
Here's an example of modyfing another component with setAttribute() in reaction to an event coming from the <input> element:
<script src="https://aframe.io/releases/1.4.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent("logger", {
init: function() {
// grab the input element
const input = document.getElementById("logtext");
// on "input", do whatever you need with the updates
input.addEventListener("input", e => {
const text = e.target.value; // extract the text from the event
this.el.setAttribute("text", "value", text); // update the text component
});
}
})
</script>
<div style="position: fixed; z-index: 999">
<label for="logtext">Text to be rendered:</label>
<input type="text" id="logtext" name="logtext"><br><br>
</div>
<a-scene>
<a-text position="-1 1.75 -3" value="" color="black" logger></a-text>
<a-box position="0 1 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
</a-scene>
I think the first option is similar to react since:
using the schema is similar to keeping a component "state" (as in useState)
the update function reacts to the component changes (as is useEffect)
I've reviewed this question and the solutions and am using the JS provided:
A-Frame - playing / pausing sound with a custom ('a-sound') source.
I'm trying to construct a popup that has text displayed but also a narration. I want to have a stop audio button so the user can stop the narration at will. If I use <a-sound>, I don't seem to be able to access/create child elements. If I use <a-entity> I get an error:
"Uncaught TypeError: Cannot read property 'playSound' of undefined".
This is the element:
<a-entity id="wrapper" position="0 0.5 1">
<a-entity narration id="sound" mixin="alpr" sound="src: #piano; autoplay: false; loop: false; volume: 10;">
<a-text id="close" mixin="close">
</a-text>
<a-text stopsound id="stop" mixin="stop-sound">
</a-text>
</a-entity>
This is the JS:
AFRAME.registerComponent('narration', {
init:function() {
let playing = false;
let audio = this.el.components.sound;
this.el.addEventListener('click', () => {
if(!playing) {
audio.playSound();
} else {
audio.stopSound();
}
playing = !playing;
});
this.el.addEventListener('mouseleave', () => {
audio.stopSound();
})
var stopaudio = document.querySelector('#stop');
stopaudio.addEventListener('click', () => {
audio.stopSound();
})
var closeaudio = document.querySelector('#close');
stopaudio.addEventListener('click', () => {
audio.stopSound();
})
}
})
Please let me know what I'm missing. Thanks!
The sound component is not yet initialized. Add sound to your narration component dependencies as described in the docs:
AFRAME.registerComponent(‘narration’,{
dependencies: [‘sound’],
...
}
I want to use a logo to link to an external URL (regular website, not VR) from inside the scene. This is what I have:
<a-entity link="highlighted: true; highlightedColor:#000000; portal:#ec1e1e; href: https://schatzkin.com; title: Back to website; image: assets/logo-lockup-black.png"></a-entity>
When I inspect the element, I see the image listed correctly under Link, and also as pano under Material. But in the actual portal, all I see is a solid magenta color.
Thanks for your help!
1) Using the link component. Provided the image is accessible
a) the path is correct
b) there are no CORS issues
setting the image attribute should provide background for the portal which is part of the link component.
link="highlighted: true; highlightedColor:#000000; href: https://schatzkin.com;
titleColor: black; title: Back to website;image: https://i.imgur.com/wjobVTN.jpg"
2) Making your own link. Any element can become a link with some js.
You could create your own element, which will change the window.location on click:
AFRAME.registerComponent("mylink", {
init: function() {
this.el.addEventListener("click", (e)=> {
window.location = this.data.href;
})
}
})
HTML
<a-text color="black" position="1 1 -2" value="goToSchatzkin"
mylink="href: https://schatzkin.com;"></a-text>
Check out both methods in my fiddle, or down below:
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent("mylink", {
init: function() {
this.el.addEventListener("click", (e) => {
window.location = this.data.href;
})
}
})
</script>
<a-scene cursor="rayOrigin: mouse">
<a-text color="black" position="1 1.6 -2" value="Click the text"
mylink="href: https://schatzkin.com;"></a-text>
<a-entity position="-1 1.6 -2"
link="highlighted: true;
highlightedColor:#000000; backgroundColor: red;
href: https://schatzkin.com; titleColor: black;
title: click the image below.;
image: https://i.imgur.com/wjobVTN.jpg;
visualAspectEnabled: true"></a-entity>
</a-scene>
A-Frame Mixins go into the <a-assets> element, which must be defined before the scene is rendered. This makes sense for pre-loading/caching images, videos etc, but it seems there should be a way to dynamically create and use mixins.
Just adding the mixin to <a-assets> at runtime does not seem to work. The recommendation for adding image assets at runtime is to inline the image source and set it on the material directly. Is there some similar way of defining/altering a mixin at runtime? Or do I just need to set the relevant properties on all the objects to which the mixin is applied (taking care to also account for properties having been set by other mixins later in the mixin chain or directly on the object itself)
Edit: It looks like aframe-asset-on-demand-component is designed to do this for image/video assets. Unclear if works for mixins, but it also hasn't been updated in a year. Is this a (semi-)officially recommended solution?
Sorry if I've misunderstood your question but I seem to be able to add mixins to the assets tag at runtime. A basic version would mean writing a component as follows;
// add assets at run time
AFRAME.registerComponent('addasset', {
init: function () {
var assets = document.getElementsByTagName('a-assets')[0]
var mixin = document.createElement('a-mixin')
mixin.setAttribute('id', 'makeitred')
mixin.setAttribute('material', 'color: red')
assets.appendChild(mixin)
},
});
And then attach that component to the scene as follows;
<a-scene addasset>
<a-assets>
</a-assets>
<a-cylinder
mixin="makeitred"
position="0 0.5 -3">
</a-cylinder>
</a-scene>
Here is a working glitch
To demonstrate how that could be added once the scene is running here is a version of the same component with a setTimeout to demonstrate how the mixin could be added later on.
// add assets at run time, delayed
AFRAME.registerComponent('addasset', {
init: function () {
setTimeout(function(){
var assets = document.getElementsByTagName('a-assets')[0]
var mixin = document.createElement('a-mixin')
var cylinder = document.getElementsByTagName('a-cylinder')[0]
mixin.setAttribute('id', 'makeitred')
mixin.setAttribute('material', 'color: red')
assets.appendChild(mixin)
cylinder.setAttribute('mixin', 'makeitred')
}, 2000);
},
});
and then the HTML in which the mixin attribute will be added later
<a-scene addasset>
<a-assets>
</a-assets>
<a-cylinder
position="0 0.5 -3">
</a-cylinder>
</a-scene>
Here is a glitch of that
And for the sake of exploration, here is the same set up but triggered by an example event. First the same component but with an event listener
// add assets at run time, triggered by event
AFRAME.registerComponent('addasset', {
init: function () {
document.addEventListener("testevent", function(){
var assets = document.getElementsByTagName('a-assets')[0]
var mixin = document.createElement('a-mixin')
var cylinder = document.getElementsByTagName('a-cylinder')[0]
mixin.setAttribute('id', 'makeitred')
mixin.setAttribute('material', 'color: red')
assets.appendChild(mixin)
cylinder.setAttribute('mixin', 'makeitred')
});
},
});
Then a component that emits an event for testing
// test event to trigger adding of mixin
AFRAME.registerComponent('testevent', {
init: function () {
var self = this.el
setTimeout(function(){
self.emit("testevent")
}, 3000);
},
});
Then the HTML, as before but including a test entity that emits an event
<a-scene addasset>
<a-assets>
</a-assets>
<a-cylinder
position="0 0.5 -3">
</a-cylinder>
<a-entity
testevent>
</a-entity>
</a-scene>
And here is a glitch for that
So you could mix those up, add the mixin to assets but delay/trigger on event the addition of properties or add the mixin to assets with properties but delay/trigger on event the setting of that attribute on your target elements.
I hope that helps
I want to achieve communication between child parent with Polymer element.
Here my index.html
<proto-receiver data="message">
<proto-element data="message"></proto-element>
</proto-receiver>
Both element have their respective "data" property
properties: {
data: {
value: 'my-data',
notify: true,
}
},
In proto-receiver, which is the parent I update "data" by handling simple click
<template>
<span on-tap="onClick">proto receiver: {{data}}</span>
<content></content>
</template>
onClick: function () {
this.data = 'new-message';
},
I want the change to be propagate to the child element as well, as it mentioned here.
I achieve this by passing a setter in my child element and called it like this. Which is, I guess, not the way it should be done.
Polymer.Base.$$('body').querySelector('proto-element').setData(this.data);
What I'm doing wrong
Thanks
UPDATE:
For those coming here. The proper way of doing this is by using Events.
Polymer 1.x
this.fire('kick', {kicked: true});
Polymer 2.x (simple javascript)
this.dispatchEvent(new CustomEvent('kick', {detail: {kicked: true}}));
In both case the receiver should implement the regular addEventListener
document.querySelector('x-custom').addEventListener('kick', function (e) {
console.log(e.detail.kicked); // true
})
To provide a concrete example to Scott Miles' comments, if you can wrap your parent and child elements in a Polymer template (such as dom-bind or as children to yet another Polymer element), then you can handle this declaratively. Check out the mediator pattern.
parent element:
<dom-module id="parent-el">
<template>
<button on-tap="onTap">set message from parent-el</button>
<p>parent-el.message: {{message}}</p>
<content></content>
</template>
<script>
Polymer({
is: 'parent-el',
properties: {
message: {
type: String,
notify: true
}
},
onTap: function() {
this.message = 'this was set from parent-el';
}
});
</script>
</dom-module>
child element:
<dom-module id="child-el">
<template>
<p>child-el.message: {{message}}</p>
</template>
<script>
Polymer({
is: 'child-el',
properties: {
message: {
type: String,
notify: true
}
}
});
</script>
</dom-module>
index.html:
<template is="dom-bind" id="app">
<parent-el message="{{message}}">
<child-el message="{{message}}"></child-el>
</parent-el>
</template>
<script>
(function(document) {
var app = document.querySelector('#app');
app.message = 'this was set from index.html script';
}) (document);
</script>
JS Bin
I was facing same issue and got solution for it and fixed it as below
this.fire('iron-signal', {name: 'hello', data: null});
You can refer this iron-signals you will get the solution which you are looking for its basically event fire from any element to another
Hope this will help you
Polymer iron signals