Use constant for Ember computed property dependent key - javascript

For an Ember app, is it possible to use a constant as part of a computed property key ?
So, essentially, I have a constant as below;
MY_SECTION {
MY_FIELD: "my-field-id"
}
What I want is a computed property on "my-field-id" i.e.
myCP: function() {
console.log('Inside CP...');
}.property('section.field.my-field-id.value')
However, I want to be able to use constant for my-field-id instead of using it directly. Is that possible ?

Ola #testndtv, thanks for your question! Yes it is entirely possible to use a constant in the key for a computed property, but to make use of it you will need to use the more modern syntax that #jelhan was mentioning because .poperty() is deprecated.
Here is a working example of a controller that I have tested locally and is working as you would expect:
import Controller from '#ember/controller';
import { defineProperty, computed } from '#ember/object';
const PROPERTY_ID = 'some-random-string-that-is-too-long-to-write';
export default Controller.extend({
// this is just for the example so we can show the value in the template
// it is not needed to get this to work
PROPERTY_ID: PROPERTY_ID,
init() {
this._super(...arguments);
defineProperty(this, 'myCP', computed(PROPERTY_ID, function() {
return this.get(PROPERTY_ID);
}));
},
actions: {
addOne() {
// this is just for the example to stop the result always being NaN because
// null + 1 = NaN
let value = this.get(PROPERTY_ID) || 0;
this.set(PROPERTY_ID, value + 1);
}
}
});
As you can see we are making use of defineProperty which is being imported from '#ember/object'. You can read more about it in the API documentation
The key insight here is that you need to define the property dynamically in the init() for this Ember object.
The corresponding template for this Controller is as follows:
Property ID is: {{PROPERTY_ID}}
<br>
And the value is: {{get this PROPERTY_ID}}
<br>
<button {{action 'addOne'}}>Add One</button>

Related

Binding a Vue property to an asynchronous value

I have a Vue block that I need to bind to a boolean property:
<div class="row" v-if.sync="isThisAllowed">
To calculate that property I need to make an API call using Axios, which has to be asynchronous. I've written the necessary code to get the value:
public async checkAllowed(): Promise<boolean> {
var allowed = false;
await Axios.get(`/api/isItAllowed`).then((result) => {
var myObjects = <MyObject[]>result.data.results;
myObjects.forEach(function (object) {
if (object.isAllowed == true) {
hasValuedGia = true;
}
})
});
return allowed;
}
What I did then - I'm not very experienced with Vue - is to add a property to the Vue model and assign a value to it in created:
public isThisAllowed: boolean = false;
async created() {
this.checkAllowed().then(result => {
this.isThisAllowed = result;
});
}
This works in the sense that the value I'm expecting is assigned to the property. But Vue doesn't like it and complains
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value.
Most of the values on the model are exposed via getters:
get isSomethingElseAllowed(): boolean {
return this.productCode === ProductTypes.AcmeWidgets;
}
But I need to "await" the value of the async function, which would mean making the getter async which then, of course, makes it a Promise and I can't bind that to my model?
What's the right way to go about this?
You can't define a property that way, instead define isThisAllowed in the data object
as
data: function(){
return {
isThisAllowed: false
}
}
And make checkAllowed into a normal function and set this.isThisAllowed = allowed inside it

How can I access one attached filter from another in Vue

I have set up a file to hold filters attached to my Vue object, this is required into my App.js. However I have a filter that would ideally use another filter:
Vue.filter('formatDateTime', value => {
if (value) return moment(String(value)).format('DD-MMM-YY hh:mm')
});
Vue.filter('getActivity', value =>
(!value.lastvisited)
? 'Not visited yet'
: 'Last logged in ' + Vue.$options.filters.formatDateTime(value.lastvisited)
)
but it can't reference the global Vue object, is there a way around this?
I can of course repeat the date formatting code, but I would prefer not to.
one option is to seperate definition and global registration
(plus its now usable/importable in non-vue context)
// ie. in filters.js
export const formatDateTime = value => value
? moment(String(value)).format('DD-MMM-YY hh:mm')
: null
export const getActivity = value => !value.lastvisited
? 'Not visited yet'
: 'Last logged in ' + formatDateTime(value.lastvisited)
// ie. in in a filter-global-registration.js
import { getActivity, formatDateTime } from 'filters.js'
Vue.filter('formatDateTime', formatDateTime);
Vue.filter('getActivity', getActivity)
What I usually do is create a global reference to your main Vue object, like:
// Create a filter which calls another filter.
Vue.filter('myFilter', () => {
window.Vue.$options.filters.myOtherFilter(...);
});
// Initialize the main Vue object.
window.Vue = new Vue({
});
Then, you should be able to access your filters using window.Vue.$options.filters.myOtherFilter() inside of your filter functions.
It might not be ideal but it has helped me out on several occasions where you have to define plugins/services/components that dont give you the current Vue instance it uses.

How to rerun a Vuex Getter

Maybe I have misunderstood what a getter is in Vuex, but say I have a getter that gets the size of a DOM element, a div for example. I would do something like this :
const getters = {
getContainerWidth (state) {
return document.getElementById('main-content').clientWidth;
}
}
Now when I start my app, all the getters seem to be run straight away. What if the div isn't available at startup? How do I rerun a getter?
I run the getter like this at the moment :
import store from '#/store'
store.getters['myModule/getContainerWidth']
I thought maybe this would work :
store.getters['myModule/getContainerWidth']()
But since store.getters is an object containing properties and values, and the values not being functions, I can't rerun them.
Any ideas?
Getters should depend on state field to be reactive. It you want to observe clientWidth changes - it does not work.
If you want to use it like function then just return function from getter:
const getters = {
getContainerWidth (state) {
return () => {
let container = document.getElementById('main-content');
return container ? container.clientWidth : 0
};
}
}
and use it like getContainerWidth()

Pass properties from parent component to all transcluded children component in Vue

I would like to pass some properties from a parent to all of his children when those are transcluded (content distribution syntax). In this case, the parent doesen't know (as far as I know) his children, so I don't know how to proceed.
More specificly, I want a way to write this :
<my-parent prop1="foo" prop2="bar">
<my-children></my-children> <!-- Must know content of prop1 and prop2 -->
<my-children></my-children> <!-- Must know content of prop1 and prop2 -->
</my-parent>
Instead of having to write this :
<my-parent prop1="foo" prop2="bar">
<my-children prop1="foo" prop2="bar"></my-children>
<my-children prop1="foo" prop2="bar"></my-children>
</my-parent>
Is it possible ? Thanks.
Props allow data flow only one level. If you want to perpetuate data, you can use an event bus instead.
Instantiate an event bus with an empty Vue instance in your main file.
var bus = new Vue();
Then in your parent, emit the event with data to be passed
bus.$emit('myEvent', dataToBePassed);
Listen for myEventanywhere you want to pick up the data. In your case, it is done in your child components
bus.$on('myEvent', function(data) {
.....
});
Here is my solution, that's probably not a great deal, but that's the cleanest solution for what I want to do right now. The principle is to create computed properties that will use own component prop if they exist, or get $parent values otherwise. The real prop would then be accessible in this._prop.
Vue.component('my-children', {
props: ["prop1", "prop2"],
template: "<div>{{_prop1}} - {{_prop2}}</div>",
computed: {
_prop1: function() {
return this.prop1 || this.$parent.prop1;
},
_prop2: function() {
return this.prop2 || this.$parent.prop2;
}
}
});
Here is a mixin generator that does that in a more elegant way, and with, possibly, multiple levels :
function passDown(...passDownProperties) {
const computed = {};
passDownProperties.forEach((prop) => {
computed["_" + prop] = function() {
return this[prop] || this.$parent[prop] || this.$parent["_" + prop];
};
});
return { computed };
}
Vue.component('my-children', {
props: ["prop1", "prop2"],
template: "<div>{{_prop1}} - {{_prop2}}</div>",
mixins: [passDown("prop1", "prop2")]
});
At this point (I'm not a vue expert) I just could think in this solution.
Assign every component's props is boring I agree, so why not doing it programmatically?
// Create a global mixin
Vue.mixin({
mounted() { // each component will execute this function after mounted
if (!this.$children) {
return;
}
for (const child of this.$children) { // iterate each child component
if (child.$options._propKeys) {
for (const propKey of child.$options._propKeys) { // iterate each child's props
// if current component has a property named equal to child prop key
if (Object.prototype.hasOwnProperty.call(this, propKey)) {
// update child prop value
this.$set(child, propKey, this[propKey]);
// create a watch to update value again every time that parent property changes
this.$watch(propKey, (newValue) => {
this.$set(child, propKey, newValue);
});
}
}
}
}
},
});
This works but you will get an ugly vue warn message:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value.
I'm not sure if this is a good solution but it works, so if you decide to use just keep in mind Global-Mixin recomendations:
Use global mixins sparsely and carefully, because it affects every
single Vue instance created, including third party components.
Please see a full example at https://github.com/aldoromo88/PropsConvention
Hope it helps

Polymer- use behavior to share object between elements?

Is it possible to use behaviors to share a object between elements?
<script>
selectedBehavior = {
properties: {
selected: Object
}
}
</script>
<dom-module id="paper-menu-custom">
<style>
</style>
<template>
<paper-menu attr-for-selected="name" selected="{{selected.choice}}">
...
<script>
Polymer({
is: "paper-menu-custom",
behaviors: [selectedBehavior]
});
toolbars = document.querySelector('paper-menu-custom');
toolbars.selected.choice = "home";
Uncaught TypeError: Cannot set property 'choice' of undefined
You do not need to use a behavior to share information between elements.
You should use IronMeta like so :
Declaratively and with data-binding :
<iron-meta key="my-unique-key" value="{{mySharedInformation}}"></iron-meta>
Then use mySharedInformation the same way you would any custom element's properties. Setting it will update the value of any other <iron-meta> in your code that shares the same key.
In plain javascript :
Read
var mySharedInformation = new Polymer.IronMeta().byKey('my-unique-key');
Write
new Polymer.IronMeta({key: 'my-unique-key', value: mySharedInformation});
Take a look at my object in github (https://github.com/akc42/akc-meta), it allows one element to publish a value with a key, and other ti have multiple instances subscribe to it and get the data out again.
It does it by keeping instances in a private variable
(function(){
var private;
Polymer({element definition has access to private});
})();
I got it with this:
<script>
selectedBehavior = {
properties: {
selected: {
type: Object,
value: function() { return { choice: 'home'} }
}
}
}
</script>
It seems specifying a object is not enough. I need to return a object for the value of object. Doesn't make alot of sense because in documentation I should just be able to say foo: Object. But, maybe this is a special case sense I am using it as a behavior.
Unfortunately, two elements can not share the same obj.property through behaviors. Each will have it's own instance.
If you want to share an object between the different instances of your element, you have to avoid using a function as describe in the Documentation
So this should be working as you expect:
<script>
selectedBehavior = {
properties: {
selected: {
type: Object,
value: { choice: 'home'} }
}
}
}
</script>

Categories