So I am looking at the docs for aframe regarding component update
AFRAME.registerComponent('visible', {
/**
* this.el is the entity element.
* this.el.object3D is the three.js object of the entity.
* this.data is the component's property or properties.
*/
update: function (oldData) {
this.el.object3D.visible = this.data;
}
// ...
});
How would I use this to change only 1 property while keeping the rest the same? i.e. change value of x, but keep y and z. When I use setAttribute, it seems the properties are just replaced with whatever I passed into setAttribute.
Found the answer in the Entity portion of the docs
https://aframe.io/docs/0.2.0/core/entity.html#setattribute-attr-value-componentattrvalue after I found this question!
Related
I'm using an external library rendered using Vue3. It has the following component from a third part library [Edit: I realize the GitHub repo for that library is out of date, so updating with the actual code in my node_modules.]
<template>
<div class="socket" :class="className" :title="socket.name"></div>
</template>
<script>
import { defineComponent, computed } from "vue";
import { kebab } from "./utils";
export default defineComponent({
props: ["type", "socket"],
setup(props) {
const className = computed(() => {
return kebab([props.type, props.socket.name]);
});
return {
className
};
}
});
</script>
It renders based on a Socket object passed as a prop. When I updated the name property of the Socket, I see the title updated accordingly. However, the CSS/class does not update. I've tried $forceRefresh() on its parent, but this changes nothing.
Update: I was able to move the rendering code to my own repo, so I can now edit this component if needed.
Based on this updated code, it seems the issue is that the class is computed. Is there any way to force this to refresh?
The only time it does is when I reload the code (without refreshing the page) during vue-cli-service serve.
For reference, the | kebab filter is defined here:
Vue.filter('kebab', (str) => {
const replace = s => s.toLowerCase().replace(/ /g, '-');
return Array.isArray(str) ? str.map(replace) : replace(str);
});
Do filtered attributes update differently? I wouldn't think so.
I was also wondering if it could be a reactivity issue, and whether I needed to set the value using Vue.set, but as I understand it that's not necessary in Vue3, and it's also not consistent with the title properly updating.
Computed properties are reactive, however Vue does not expect you to mutate a prop object.
From the documentation:
Warning
Note that objects and arrays in JavaScript are passed by reference, so
if the prop is an array or object, mutating the object or array itself
inside the child component will affect the parent state and Vue is
unable to warn you against this. As a general rule, you should avoid
mutating any prop, including objects and arrays as doing so ignores
one-way data binding and may cause undesired results.
https://v3.vuejs.org/guide/component-props.html#one-way-data-flow
I know that this says, that you should not mutate it in the child, but the general rule is, that you should not mutate properties at all, but instead create new object with the modified data.
In your case the computed function will look for changes in the properties itself, but not the members of the properties, that is why it is not updating.
In my scene I'm using a script to create an entity with a raycaster component. This works great, and the event listeners are working fine colliding with my ground object. But my ground is uneven, and I need to know the height of the ground where the raycaster hits. As far as I know, this isn't possible with just what A-Frame provides. I'd like to call THREE's raycaster.intersectObject, but I'm not able to get a working reference to the THREE raycaster. The A-Frame documentation mentions a member called raycaster, but it's undefined for me. I've tried a few different things, but to no avail. My next plan was to make a new component, but I figured I'd ask here first.
var ray = document.createElement('a-entity');
ray.setAttribute('id', 'raycaster');
ray.setAttribute('raycaster', 'objects: #ground');
ray.setAttribute('position', pos);
ray.setAttribute('rotation', '-90 0 0');
scene.appendChild(ray);
var ground = document.querySelector('#ground');
ray.addEventListener('raycaster-intersection', function() {
console.log(ray.raycaster); //undefined
console.log(ray.intersectedEls); //undefined
console.log(ray.objects); //undefined
console.log(ray.object3D.id); //gives me the THREE id
console.log(ray.object3D.intersectObject(ground.object3D, false)); //intersectObject not a function
});
Any clues? I'm sure I'm doing something stupid.
It's a member of the component, which you'll have to dig for.
ray.components.raycaster.raycaster
ray - entity
.components - the entity's components
.raycaster - the raycaster component
.raycaster - the raycaster object as a member of the raycaster component
Here is a complete component in case anyone is interested. Just add "set-to-ground" to your entity. If you want objects to be standing on the ground, you can add them as children to the entity and set their relative y position to half the object height.
Should still be enhanced with a "target" selector though as currently the ground needs to be with id 'ground'
AFRAME.registerComponent('set-to-ground', {
schema: {
type: 'string'
},
init: function () {
var data = this.data;
var el = this.el;
var scene = document.getElementById('ascene');
var ray = document.createElement('a-entity');
ray.setAttribute('id', 'raycaster');
ray.setAttribute('raycaster', 'objects: #ground');
ray.setAttribute('rotation', '-90 0 0');
el.appendChild(ray);
var ground = document.querySelector('#ground');
ray.addEventListener('raycaster-intersection', function getY() {
console.log(ray.components.raycaster.raycaster.intersectObject(ground.object3D, true)[0].point.y); //groundPos to log
var groundPos = ray.components.raycaster.raycaster.intersectObject(ground.object3D, true)[0].point.y;
ray.removeEventListener('raycaster-intersection', getY);
el.setAttribute('position', {y: groundPos});
});
}
});
I have a parent/child component setup where the parent is loading data from the server and passing it down to children via props. In the child I would like to instantiate a jQuery calendar with some of the data it receives from the parent.
In order to wait for the data before setting up the calendar, I broadcast an event in the parent that I have an event listener setup for in the child.
The listener is being fired in the child but if I this.$log('theProp'), it's undefined. However, if I inspect the components with the VueJs devtools, the parent/child relationship is there and the child has received the prop in the meantime.
The prop is defined on the child as a dynamic prop :the-prop="theProp". Since the child does receive the prop in the end, I'm assuming my setup is correct but there seems to be some sort of delay. The parent sets the props in the return function of the ajax call and again: it's working, just with a slight delay it seems.
I also tried registering a watch listener on the prop in the child so I could setup the calendar then and be sure that the prop is there. However, the watch listener fires, but this.$log('theProp') is still undefined.
If I pass the data along with the the broadcast call, like this.$broadcast('dataLoaded', theData) the child receives it just fine. But it seems wrong to do it that way as I'm basically building my own prop handler.
I'm not posting any code because the components are rather large and the VueJs devtools are telling me the parent/child situation is working.
Am I missing some information? Is there a slight delay between setting a value in the parent and the child receiving it? What would be the proper way to wait for parent data in the child?
Normally, when you're just rendering the data out into the template, the timing doesn't matter so much since the data is bound to the template. But in this case, I really need the data to be there to setup the calendar or it will be wrong.
Thanks.
edit 1: here's a jsfiddle: https://jsfiddle.net/dr3djo0u/1/
It seems to confirm that the data is not available immediately after the broadcast. However, the watcher does work, though I could almost swear that sometimes this.$log('someData') returned undefined when I setup that testcase.
But I guess my problem might be somewhere else, I'll have a look tonight, don't have the project with me right now.
edit 2: did some more tests. My problem was that a) event listeners do not seem to receive the data instantly and b) I was also trying to init the calendar in the route.data callback if someData was already around (e.g. when coming from parent), but that route callback is called before the component is ready, so it wasn't working there either.
My solution is now this:
// works when the child route is loaded directly and parent finishes loading someData
watch: {
someData() {
this.initCalendar();
}
},
// works when navigating from parent (data already loaded)
ready() {
if (this.someData && this.someData.length) {
this.initCalendar()
}
}
As far as I know, you should not need events to pass data from parent to child.
All you need is, in the child component: props: ['theProp']
And when using the child component in the parent: <child :theProp="someData"></child>
Now, wherever in the parent you change someData, the child component will react accordingly.
You don't need events, you don't need "watch", you don't need "ready".
For example: after an AJAX call, in the parent's "ready", you load some data:
// at the parent component
data: function () {
return {
someData: {}
}
},
ready: function () {
var vm = this;
$.get(url, function(response) {
vm.someData = response;
});
}
Now, you do not need anything else to pass the data to the child. It is already in the child as theProp!
What you really need to do is to have, in the child, something which reacts to data changes on its own theProp property.
Either in the interface:
<div v-if="theProp.id > 0">
Loaded!
</div>
Or in JavaScript code:
// at the child component
computed: {
// using a computed property based on theProp's value
awesomeDate: function() {
if (!this.theProp || (this.theProp.length === 0)) {
return false;
}
if (!this.initialized) {
this.initCalendar();
}
return this.theProp.someThing;
}
}
Update 1
You can also, in the parent, render the child conditionally:
<child v-if="dataLoaded" :theProp="someData"></child>
Only set dataLoaded to true when the data is available.
Update 2
Or maybe your issue is related to a change detection caveat
Maybe you're creating a new property in an object...
vm.someObject.someProperty = someValue
...when you should do...
vm.$set('someObject.someProperty', someValue)
...among other "caveats".
Update 3
In VueJS 2 you are not restricted to templates. You can use a render function and code the most complex rendering logic you want.
Update 4 (regarding OP's edit 2)
Maybe you can drop ready and use immediate option, so your initialization is in a single place:
watch: {
someData: {
handler: function (someData) {
// check someData and eventually call
this.initCalendar();
},
immediate: true
}
}
It's because tricky behavior in Vue Parent and Child lifecycle hooks.
Usually parent component fire created() hook and then mount() hook, but when there are child components it's not exactly that way: Parent fires created() and then his childs fire created(), then mount() and only after child's mount() hooks are loaded, parent loads his mount() as explained here. And that's why the prop in child component isn't loaded.
Use mounted() hook instead created()
like that https://jsfiddle.net/stanimirsp5/xnwcvL59/1/
Vue 3
Ok so I've spent like 1.5h trying to find out how to pass prop from parent to child:
Child
<!-- Template -->
<template>
<input type="hidden" name="_csrf_token" :value="csrfToken">
<span>
{{ csrfToken }}
</span>
</template>
<!-- Script -->
<script>
export default {
props: [
"csrfToken"
]
}
</script>
Parent
<!-- Template -->
<template>
<form #submit.prevent="submitTestMailForm" v-bind:action="formActionUrl" ref="form" method="POST">
...
<CsrfTokenInputComponent :csrf-token="csrfToken"/>
...
</form>
</template>
<!-- Script -->
<script>
...
export default {
data(){
return {
...
csrfToken : "",
}
},
methods: {
/**
* #description will handle submission of the form
*/
submitTestMailForm(){
let csrfRequestPromise = this.getCsrfToken();
let ajaxFormData = {
receiver : this.emailInput,
messageTitle : this.titleInput,
messageBody : this.bodyTextArea,
_csrf_token : this.csrfToken,
};
csrfRequestPromise.then( (response) => {
let csrfTokenResponseDto = CsrfTokenResponseDto.fromAxiosResponse(response);
this.csrfToken = csrfTokenResponseDto.csrToken;
this.axios({
method : "POST",
url : SymfonyRoutes.SEND_TEST_MAIL,
data : ajaxFormData,
}).then( (response) => {
// handle with some popover
})
});
},
/**
* #description will return the csrf token which is required upon submitting the form (Internal Symfony Validation Logic)
*/
getCsrfToken(){
...
return promise;
}
},
components: {
CsrfTokenInputComponent
}
}
</script>
Long story short
This is how You need to pass down the prop to child
<CsrfTokenInputComponent :csrf-token="csrfToken"/>
NOT like this
<CsrfTokenInputComponent csrf-token="csrfToken"/>
Even if my IDE keep me telling me yeap i can navigate with that prop to child - vue could not bind it.
solution (testing ok)
In child component just using the props data, no need to re-assignment props's values to data, it will be cause update bug!
vue child component props update bug & solution
https://forum.vuejs.org/t/child-component-is-not-updated-when-parent-component-model-changes/18283?u=xgqfrms
The problem is not how to pass data with props, but rather how to do two things at almost the same time.
I have an user account component that can edit users (with an user id) and add users (without id).
A child component shows checkboxes for user<->company assignments, and needs the user id to prepare API calls when the user account is saved.
It is important that the child component shows before saving the user account, so that things can be selected before the user is saved and gets an id.
So it has no user id at first: the id is passed to the child component as 'null'.
It updates when the user is stored and gets an id.
But at this point, it takes a very short time for the child to get the new id into its model.
If you call a function in the child component that relies on data that was just changing, it might happen that the function executes before the data is updated.
For cases like this, nextTick() is your friend.
import { nextTick } from 'vue';
...
saveAccount() {
axios.post(URL, this.userModel).then((result)) {
// our model gets an id when persisted
this.userModel.id=result.data.id;
nextTick( () => {
this.$refs.childComponent.doSomething();
});
}
}
I did one sample Searchapp using meteor add sebdah:autocompletion package.When ever given inputs it shows drop down list.In this list how to get selected value as shown below code:
Js Code :
Friends = new Meteor.Collection('friends');
if (Meteor.isClient) {
/**
* Template - search
*/
Template.search.rendered = function () {
AutoCompletion.enableLogging = true;
var res = AutoCompletion.init("input#searchBox");
console.log("res :"+res);
}
Template.search.events = {
'keyup input#searchBox': function (e,t) {
AutoCompletion.autocomplete({
element: 'input#searchBox', // DOM identifier for the element
collection: Friends, // MeteorJS collection object
field: 'name', // Document field name to search for
limit: 0, // Max number of elements to show
sort: {name: 1}
});
}
}
}
I didn't get any idea about this.So please suggest me how to get selected drop down list values?
AutoCompletion package doesn't give any good API to read value on select. Instead you need to manually read the value of input#searchBox.
Please take a look at source code.
I would recommend to implement searching in your meteor app using Arunoda's approach : https://meteorhacks.com/implementing-an-instant-search-solution-with-meteor.html
If I want to know if an object has a particular property I can code this:
if (SomeObject.hasOwnProperty('xyz')) {
// some code
}
But some styles masquerade as properties at design time such as Button.color... How can I know what style properties are valid at runtime? ie: What is the equivalent of hasOwnProperty for getStyle/setStyle?
In other words how can I know if an object HAS A particular style variable... When I write:
MyButton.setStyle('qsfgaeWT','-33');
It won't accomplish anything, but it also doesn't error. How can I know programmatically that 'qsfgaeWT' is NOT a valid style of 'Button'??
setStyle fails silently for invalid style properties. You could try checking the style property after setting it:
MyButton.setStyle('qsfgaeWT','-33');
if (MyButton.getStyle('qsfqaeWT') == "-33") {
// Not valid
} else {
// valid
}
displayObject is a Button added to the stage.
var value:* = displayObject.getStyle("borderColor");
trace( StyleManager.isValidStyleValue(value).toString() ); // outputs true
value = displayObject.getStyle("qwerty");
trace( StyleManager.isValidStyleValue(value).toString() ); // outputs false
value = displayObject.getStyle("color");
trace( StyleManager.isValidStyleValue(value).toString() ); // outputs true