Computed property throws error at initialization - ractivejs

(ref http://jsfiddle.net/kapLv0mt/4/)
I have
Member = Ractive.extend({
template : "<div>{{name}}</div>",
computed : {male : function(){this.get('gender')=='m'}}
})
ff = new Ractive({
el : '#container',
template : "there are {{male_count}} males{{#family}}<member/>{{/family}}",
components : {member : Member},
data : {family : [
{name:'Fred',gender:'m'},
{name:'Wilma',gender:'f'},
{name:'Rocky',gender: 'm'},
{name:'Bubbles',gender: '?'}
]},
computed : { male_count : function(){
return _(this.findAllComponents('member')).filter(function(mem){return mem.get('male')}).length;
}
}
})
An error is thrown during ractive initialization, and also the computed property "male_count" does not return the correct value. The initialization error results from the property being included in the template, but even then the computed property does not return the correct value, probably since it was incorrectly computed at initialization.
How can I initialize a Ractive object attribute that depends on components?
Edit:
Based on a responder's suggestion, I'll mention here that the code example is very much simplified from my actual code. The component filter algorithm in the actual code is based on about 9 comparison criteria, including dates, text matches, numerical ranges, where the thresholds are input via the UI for a list filter. For this reason, I would like to stick with the idea (as in the example) of the filter test being implemented in the component.

First of all, you don't even need to inquire the member component to count the males in the data. The data is already in the parent component, the computed is also in the parent component. Why not just inquire to itself?
computed: {
male_count: function () {
return this.get('family').filter(function (familyMember) {
return familyMember.gender === 'm'
}).length;
}
}
It's also a good thing to keep in mind that in Ractive, you operate on the data, not on DOM (or in this case, components). Once you start using DOM operations, or finding components, your design needs some rethinking.

Related

nuxt component : computed vs data

In my nuxt component, I can't understand the difference between computed and data. I get the difference between data and asyncData but there is nothing regarding those two attributes.
<template>
{{computedMessage}}
{{dataMessage}}
</template>
<script>
export default {
computed: {
computedMessage(){
return this.$store.state.whatever;
}
},
data() {
return {
dataMessage: "Hi there"
}
}
}
</script>
If data is 100% static, then why make it a function?
If I want to have process.env in the function, should it be in computed or in data?
The difference between computed and data is not reactivity. Both are fully reactive, as seen here. The real difference between the two is essentially this:
data is made up of properties
computed is made up of getters.
They both serve very different purposes, but together give you some powerful tools for data manipulation.
For example:
export default {
mounted() {
console.log(this.adults)
}
data() {
return {
users: [
{ name: 'Jack', age: 12 },
{ name: 'Jill', age: 53 },
{ name: 'Smith', age: 29 },
{ name: 'Matt', age: 18 }
]
}
},
computed: {
adults() {
return this.users.filter(user => user.age >= 18)
}
}
}
This example will return 3 users from this.adults: Jill, Smith, and Matt. Without a computed property, you'd need to call a method to compute this, and you'd need to re-call it every single time you need to access that data again.
What's great about using computed properties is that their results are cached, similar to Vuex getters. This can obviously have some huge benefits when working with larger data sets.
So in summary, data is used for storing data, and computed is used for calculating new results based on data without needing to change the original state.
So now to your points:
If data is 100% static, then why make it a function?
This is because Vue shares references to all its properties, including data, between instances of the same component. So instead of declaring a plain data object, we declare a function that returns a fresh version each time it's instantiated.
If I want to have process.env in the function, should it be in computed or in data?
You're able to access process.env in either computed or data. In general, if you have access to the Nuxt instance using this, then chances are you also have access to the process.env properties.
Well the difference between data and computed is that computed is reactive and data is static. So if you want to use data that gets automatically updated, you have to use computed.
computedis for example often used when you have to wait for data (e.g. from REST api), but you don't want to block your UI. So you assign a computedvariable and the part of your UI is updated when the data has arrived.
To understand, why data needs to be a function, you should have a look at this.

Updating model in UI5, two-way data binding becomes one-way when using formatter

In my UI5-app, I have a table where each row contains a sap.m.Switch, which is bound to the model via formatter, since the data is coming from the DB as 1/0, rather than true/false, and that, probably, breaks the default two-way data binding.
To update a data model upon the editing value of this switch, I implemented the following change-event:
onChangeSwitch: function onChangeSwitch(oEvent) {
let context = oEvent.oSource.getBindingContext();
let itemIndex = context.sPath.substr(1);
let oModel = this.getView().byId("idTablePersons").getModel();
oModel.oData[itemIndex].isPersonActive = (oEvent.mParameters.state) ? 1 : 0;
oModel.refresh();
}
It does work, but I'm not sure if it is a proper way to implement such logic. Is there a standard way to update a model after changing sap.m.Switch value?
I think you're approaching this the wrong way around. sap.m.Switch already has an attribute to indicate state which you can directly bind to a model.
<Switch state="{IsPersonActive}" />
Assuming you bound the items in the table to an unnamed model, that'll set the IsPersonActive flag on the bound line to true or false depending on the state of the switch.
This also means it'll come out with the switches in the correct state if certain IsPersonActive flags are already set to true or false in your entity sets.
(…) the data is coming from the DB as 1/0, rather than true/false (…).
Is there a standard way to update a model after changing sap.m.Switch value?
The two-way data binding fix from https://embed.plnkr.co/wwQXf8bTuiTP4RlP:
NumericBoolean.js (minimal example):
sap.ui.define([
"sap/ui/model/SimpleType",
], Type => Type.extend('demo.model.type.NumericBoolean', {
constructor: function() {
Type.apply(this, arguments);
},
formatValue: iValue => !!+iValue,
parseValue: bValue => bValue ? 1 : 0,
validateValue: vValue => { /*validate...*/ },
}));
<Switch xmlns="sap.m" xmlns:core="sap.ui.core"
core:require="{ NumericBoolean: 'demo/model/type/NumericBoolean' }"
state="{
path: '/1or0',
type: 'NumericBoolean'
}"
/>
Important note:
It's mandatory to keep the validateValue declaration even if the implementation is not provided, otherwise sap.m.Switch will not work correctly.
Instead of the formatter, use expression binding wherever possible, i.e. for switch attribute „state“ to map 0/1 to true/false.
https://sapui5.hana.ondemand.com/1.34.9/docs/guide/daf6852a04b44d118963968a1239d2c0.html
But overall, I suggest to use a custom type (see above), as the same is the two-way binding solution without the need of an implemented change-event.

Binding custom element in initial loading

In aurelia: I have a string interpulation over object property that works fine in the app.html - it shows number of accounts:
ALL ACCOUNTS (${userAccountsData.length})
In the initial loading, I see that the value changes after few milliseconds from 0 to the actual value (data is retrieving from the service), but - when trying to show aggregate data (count number of active accounts) over the same data in a template (custom element) - the data stays as 0 and not updated as the userAccountsData.length
*When refreshing again after the initial loading - the data is shown as it should be.
This is the custom element instance in the app.html:
<account-status-selection-bar accounts-data.bind="userAccountsData"></account-status-selection-bar>
And this is part of the HTML of the custom element itself:
<template>
<div ref="active"
class="selection">${accountActivationDistribution.numberOfActiveAccounts}
This is the relevant part of the custom element VM:
"use strict";
import { bindable} from 'aurelia-framework';
export class accountStatusSelectionBar {
#bindable accountsData;
constructor() {
this.accounts = [];
this.accountActivationDistribution = { numberOfActiveAccounts: 0,
numberOfInactiveAccounts : 0,
numberOfTotalAccounts : 0
}
get activeAccounts() {
var activeAccounts = this.accounts.filter(function(account) {
return account.IsApproved;
});
return activeAccounts.length;
}
attached()//bind()
{
this.accounts = this.accountsData;
this.accountActivationDistribution.numberOfActiveAccounts =
this.activeAccounts
}
In the app.js I use observerLocator - here is the code related to the working part of userAccountsData.length:
constructor() {
this.userAccountsData = [];
....
this.subscribe = this.observerLocator.getObserver(accounts, "all")
.subscribe((value) => {
if (!value)
return;
this.userAccountsData = value;
**A work around I found (although I'm not sure this is the best way) is to do the aggregation in the app.js (in the observer part) in object and bind the already aggregated object to the custom element - this is working. I'm still looking for the mentioned above solution.
It looks like the problem is you're binding userAccountsData to accountsData on your custom control; then assigning this.accounts = this.accountsData; and finally later you're reassigning userAccountsData in app.js.
Because accounts is not observing or bound to the original userAccountsData, it maintains the reference to the original array (which is set to an empty array) and never gets updated.
There is a race condition on refresh, where some cache probably means that userAccountsData gets the updated value before the binding occurs, which is why it works sometimes.
The solution is to remove some of the reassignment and just bind directly to accounts and forget the intermediate accountsData.
I created a gist here showing the different behaviour.

Vuefire Firebase update issues

I'm having som issues with updating Firebase from VueFire. I m trying to use the following method, but it yells at me if I leave any field blank (which is supposed to happen often in setup) Any idea why this gets mad if .update with a blank field?
Error: Uncaught Error: Firebase.update failed: First argument contains undefined in property 'businesses.somebusiness.video'
updatePost(post) {
postsRef.child(post['.key']).update({
name: post.name,
video: post.video,
story: post.story,
cover: post.cover,
card: post.card
})
},
At one point I had the above re-written like so:
updatePost: function (post) {
const businesschildKey = post['.key'];
delete post['.key'];
/* Set the updated post value */
this.$firebaseRefs.posts.child(businesschildKey).set(post)
},
It worked amazingly but deleting the key seemed to cause weird ordering issues in Vue. I would prefer to stick with the top method if I can find a way to not have it trow an error if one is left blank.
According to this post,
When you pass an object to Firebase, the values of the properties can
be a value or null (in which case the property will be removed). They
can not be undefined, which is what you're passing in according to the
error.
Your error message suggests that post.video's value is undefined. You can use logical-or to provide a fallback value like so:
video: post.video || null,
That means whenever post.video has a false-y value, the expression will evaluate to null. That could catch empty string or numeric 0, though. To be more precisely correct, you should use
video: typeof post.video === 'undefined' ? null : post.video,
If you need to do this check for many values, you can write a function for it:
function nullIfUndefined(value) {
return typeof value === 'undefined' ? null : value;
}
then your expression would just be
video: nullIfUndefined(post.video),

Identity in ractive data arrays

I have an object of message streams that looks like this:
ractive.data.messages:
{
stream_id1: {
some_stream_metadata: "foo",
stream: [
{id: "someid1", message: "message1"},
{id: "someid2", message: "message2"}
]
},
stream_id2: {
some_stream_metadata: "bar",
stream: [
{id: "someid3", message: "message3"},
{id: "someid4", message: "message4"}
]
}
}
main_template:
{{#messages[ current_stream_id ]}}
{{>render_message_stream}}
{{/messages[ current_stream_id ]}}
render_message_stream:
{{#stream}}
<div class="stream">
...someotherstuff...
{{>render_message}}
</div>
{{/stream}}
render_message:
<div class="message">
...someotherstuff...
{{message}}
</div>
I change "current_stream_id" to change the rendered stream of messages.
On updates, i change the contents of the message streams like this:
ractive.merge(
"messages.stream_id1.stream",
new_message_stream,
{
compare: function ( item ) { return item.id; }
});
I also tried the compare: true option instead of the function, with the same results:
Ractive always thinks that these two messages belong effectively to the same DOM element, even though they live in a completely different message stream:
ractive.data.messages[ "stream_id1" ].stream[1].message
ractive.data.messages[ "stream_id2" ].stream[1].message
Problems:
When there are intro/outro animations ractive animates always just the end of the messages stream, even when a message in the middle of the stream was deleted, i need help to make ractive understand which messages are identical.
When i change the current_stream_id, ractive does not rerender the complete {{>render_message_stream}} partial, but goes inside the existing dom and changes the {{message}} field in all existing messages, though this might be good for dom element reuse, this triggers a lot of animations that are wrong. (Eg. it triggers intro/outro animations for the last message in the stream if stream1 has one message more than stream2).
One of these issues has a straightforward answer; unfortunately the other one doesn't.
I'll start with the easy one - the fact that
ractive.data.messages[ "stream_id1" ].stream[1].message
ractive.data.messages[ "stream_id2" ].stream[1].message
belong to the same DOM element. You're correct in that Ractive updates the existing elements rather than removing them and creating new ones - this is a core part of its design. In this case that's undesirable behaviour, but you can work around it like so:
// instead of immediately switching to a new stream ID like this...
ractive.set( 'current_stream_id', 'stream_id2' );
// you can set it to a non-existent ID. That will cause the existing DOM
// to be removed. When you set it to an ID that *does* exist, new DOM
// will be created:
ractive.set( 'current_stream_id', null );
ractive.set( 'current_stream_id', 'stream_id2' );
// or, if you'd like the initial transitions to complete first...
ractive.set( 'current_stream_id', null ).then(function () {
ractive.set( 'current_stream_id', 'stream_id2' );
});
The other issue - that merge() isn't merging, but is instead behaving as though you were doing ractive.set('messages.stream_id1.stream', new_message_stream) - is tougher. The problem is that while you and I know that {{#messages[ current_stream_id ]}} equates to messages.stream_id1 when current_stream_id === 'stream_id1, Ractive doesn't.
What it does know is that we have an expression whose value is determined by messages and current_stream_id. When the value of either of those references changes, the expression is re-evaluated, and if that value changes, the DOM gets updated - but using a standard set(). When you do ractive.merge('messages.stream_id1.stream', ...), Ractive updates all the things that depend on keypaths that are 'upstream' or 'downstream' of messages.stream_id1.stream - which includes messages. So that's how the expression knows that it needs to re-evaluate.
It's possible that a future version of Ractive will be able to handle this case in a smarter fashion. Perhaps it could make a note of arrays that are subject to merge operations, and check evaluator results to see if they're identical to one of those arrays, and if so use merge() rather than set(). Perhaps it could analyse the function in some way to see if the {{#messages[ current_stream_id ]}} section should register itself as a dependant of messages.stream_id1 for as long as current_stream_id === 'stream_id1', rather than the internally-generated ${messages-current_stream_id-} keypath.
None of that helps you in the meantime though. The only way to use merge() in your current situation is to have a separate reference that doesn't use an expression, and a bit of magic with pattern observers:
main_template:
{{#current_messages}} <!-- rather than `messages[ current_stream_id ]` -->
{{>render_message_stream}}
{{/current_messages}}
render_message_stream:
{{#current_message_stream}} <!-- rather than `stream` -->
<div class="stream">
{{>render_message}}
</div>
{{/current_message_stream}}
code:
ractive.observe( 'current_stream_id', function ( id ) {
var current_messages = this.get( 'messages.' + id );
this.set( 'current_messages', current_messages );
// hide existing stream, then show new stream
this.set( 'current_message_stream', null ).then(function () {
this.set( 'current_message_stream', current_messages.stream );
});
});
// when ANY message stream changes, we see if it's the current one - if so, we
// perform a merge on the top-level `current_message_stream` array
ractive.observe( 'messages.*.stream', function ( new_stream, old_stream, keypath, id ) {
// the value of any * characters are passed in as extra arguments, hence `id`
if ( id === this.get( 'current_stream_id' ) ) {
this.merge( 'current_message_stream', new_stream, {
compare: function ( item ) {
return item.id;
}
});
}
});
I've set up a JSFiddle demonstrating this. I hope it makes sense, let me know if not - and sorry I didn't get round to answering this question much sooner.

Resources