aurelia - computedFrom - aurelia

I have the following:
.ensure('baseContent.ValidFromDate', (config) => { config.computedFrom(['baseContent.ValidFromDate', 'baseContent.ValidFromTime', 'baseContent.ValidToDate', 'baseContent.ValidToTime']) })
.if(() => {
return this.baseContent.ValidFromDate !== null && this.baseContent.ValidFromTime !== null && this.baseContent.ValidToDate !== null && this.baseContent.ValidToTime !== null })
.passes( () => { return this.datetimeformat.format(this.baseContent.ValidFromDate, this.baseContent.ValidFromTime) < this.datetimeformat.format(this.baseContent.ValidToDate, this.baseContent.ValidToTime) })
.withMessage('< Valid To')
.endIf()
I believed that config.computedFrom(['baseContent.ValidFromDate', 'baseContent.ValidFromTime', 'baseContent.ValidToDate', 'baseContent.ValidToTime']) - meant that if one of these values changed it would re-run the validation? However it only does this on 'baseContent.ValidFromDate', what am I missing / not understanding?
UPDATE
Seems the issue is to do with computedFrom doesn't support paths, don't really know another way round this barring adding this validation to the four items

This feature was added last week (aurelia-binding 1.0.0-beta.1.3.0). See https://github.com/aurelia/binding/issues/149
Syntax when used as a decorator is:
#computedFrom('obj.firstName', 'obj.lastName')

Related

Correct way to implement drill-down tags in vue with vuetify

I am using a v-chip-group with v-chips to represent a tag cloud for records in my database. I have an object array with records that look something like { key:'device', count:100}. A record could have multiple tags, so as you click on a tag, a new query is made that filters on that tag, the result will then have a new tag cloud with a subset of the previous.
It looks something like this:
tag1 (1000), tag2 (100), tag3 (100)
When you click on tag1 you end up with:
tag1 (1000), tag3 (15) (no tag2 because there is no overlap between tag1 and tag2).
Here is the relevant template code:
<v-chip-group v-model="selectedTag" multiple #change="refresh">
<v-chip v-for="tag in tags" :key="tag.key" active-class="primary">
<v-avatar left class="grey">{{ tag.count }}</v-avatar>
{{ tag.key }}
</v-chip>
</v-chip-group>
The problem I have is that in the typescript I do something like this:
refresh() {
// get simple array of tag strings
const selectedTags = this.selectedTag.map((value: any) => {
if (this.tags && this.tags[value]) {
return this.tags[value].key
} else {
return null
}
}).filter((value: any) => {
return value != null
})
Promise.all([
...
ApiCall('GET', 'tags', {limit: 1000, tags: selectedTags}),
...
).then((values) => {
// decode response from server into new tags
this.tags = values[2].series['0'].values.map((item: any) => {
return {key: item.bucket, count: item.doc_count}
})
const newTags: number[] = []
this.tags.forEach((tag, index) => {
// find the new index of the previously selected tags and save them
if (selectedTags.find(st => {
return st === tag.key
})) {
newTags.push(index)
}
})
// update selectedTag with the new value
this.$set(this, 'selectedTag', newTags)
// did not work this.selectedTag = newTags
})
}
What I'm seeing is that when I click a chip, it correctly fires the #change event and calls refresh, but then when the refresh finishes, I see an additional refresh get called with an empty selectedTag, which clears my filters and recalls the above functionality.
Is there a way to get #change to fire when a chip is changed, but not fire (or filter it out) when the event is generated by changing the data referenced by v-model?

Am I overwriting computed property filter in Vue?

I am trying to create a reactive filter for an array in Vue. My starting array comes from an API call which returns this.features (geojson features). I am filtering on a nested array. This works -- but when I enter a search term and then backspace back out to an empty string, and enter another string, I am not filtering the original array but appear to be filtering the already-filtered array. How could I filter again on the original array from the API call?
computed property:
filteredFeatures() {
if (this.searchTerm == '') {
return this.features
}
// filter on nested array
let filtered = this.features.filter(feature => {
feature.properties.site_observations = feature.properties.site_observations.filter(
el => JSON.stringify(el).match(this.searchTerm, 'i')
)
return feature.properties.site_observations.length > 0
})
return filtered
}
I have looked at Vue filtering objects property but I cannot make that code work (it uses Object.assign()). Thanks for any ideas.
Your computed property is mutating feature.properties.site_observations, that's a nono. Computed properties should be read only.
filteredFeatures() {
if (this.searchTerm == '') {
return this.features
}
// filter on nested array
let filtered = this.features.filter(feature => {
const site_observations = feature.properties.site_observations.filter(
el => JSON.stringify(el).match(this.searchTerm, 'i')
)
return site_observations.length > 0
})
return filtered
}
It seems here is your problem:
feature.properties.site_observations = feature.properties.site_observations.filter(
el => JSON.stringify(el).match(this.searchTerm, 'i')
)
Because this code filter feature and alter the proprieties of feature.properties.site_observations. Then, in the next read the value is alter. We say that your function it is not pure, because it alter the state of feature.
So, what you should do is:
let anotherVariable = feature.properties.site_observations.filter(
el => JSON.stringify(el).match(this.searchTerm, 'i')
)
Therefore, on a function, avoid alter state of objects, this lead to bugs.
On further checking, the above answer returns all site_observations, not just the ones that match the search. A much better solution is the following, using map to avoid overwriting the data, and the object spread operator to perform an object assign, and drilling down through the nested objects as follows:
filteredFeatures() {
return this.features
.map(feature => ({
...feature,
properties: {
site_observations: feature.properties.site_observations.filter(
element => {
return JSON.stringify(element).match(new RegExp(this.search, 'i'))
}
)
}
}))
.filter(feature => feature.properties.site_observations.length)
}

Property "CDbCriteria.:centerId" is not defined in yii

I am using select method in yii it gives error "Property "CDbCriteria.:centerId" is not defined"
if (0 < self::model()->countByAttributes(
'centerId = :centerId AND qTypeId = :qTypeId',
array(
':centerId' => $centerId,
':qTypeId' => $qTypeId,
)
)) {
throw new Exception('Duplicate Entry for center and que type');
}
You're using this method in a wrong way. You skipped first argument, which should be list of active record arguments used as filter (see documentation). You probably need something like:
if (0 < self::model()->countByAttributes([
'centerId' => $centerId,
'qTypeId' => $qTypeId,
]) {
throw new Exception('Duplicate Entry for center and que type');
}
Or use count():
if (0 < self::model()->count(
'centerId = :centerId AND qTypeId = :qTypeId',
[
':centerId' => $centerId,
':qTypeId' => $qTypeId,
]
)) {
throw new Exception('Duplicate Entry for center and que type');
}

How can I show days by group like Whatsapp chat screen?

How can I excatly do a similar Date system like the one in the Whatsapp chat screen?
As you can see the messages are in a group by date, I mean they are separated by date.
Here is a ScreenShot that i found for better explanation:
I do this in a FlatList, while rendering the messages one by one.
Here is what i did
let previousDate = "";
if (index > 0) {
previousDate = moment(this.state.messages[index - 1].created_at).format(
"L"
);
} else {
previousDate = moment(this.state.messages.created_at).format("L");
}
let currentDate = moment(item.created_at).format("L");
So, i created a functional component for renderItem prop of the FlatList, so item and index comes from the actual data from the FlatList.
What i'm trying to do here is, basically grabbing the current rendering item's created_at and compare it with the previous item's created_at, and to do that, i'm using the original data which is stored in the state. But unfortunately when the FlatList rendering the very first item which has index number 0 there is no previous element to compare in the original data in state, that's why i checking if is greater than 0 go and grab date from previous indexed item. And in the Else case, which means when rendering the first item, do not look for previous item and just get the created_at.
And below i check if the currentDate and previousDates are NOT the same, render a custom component else do not render anything.
{previousDate && !moment(currentDate).isSame(previousDate, "day") ? ( // custom component) : null}
It's should work like that, but the major problem is, i used inverted FlatList for to able to messages go from bottom of the screen to the top. But now, becouse of it's a inverted flatlist the items being rendering from bottom to the top and it gives me result like this:
NOTE: At the beginning the messages were coming also reversed but i fixed this with sending them also reversed from the DB.
So, i don't know how do i able to achieve my goal, and do it like on the first picture.
Thank you!
I use a helper function (generateItems) to address the problem that you are describing. Here is the code that I use to group my messages by day and then render either a <Message /> or a <Day /> in the renderItem prop. This is using an inverted FlatList as you described.
import moment from 'moment';
function groupedDays(messages) {
return messages.reduce((acc, el, i) => {
const messageDay = moment(el.created_at).format('YYYY-MM-DD');
if (acc[messageDay]) {
return { ...acc, [messageDay]: acc[messageDay].concat([el]) };
}
return { ...acc, [messageDay]: [el] };
}, {});
}
function generateItems(messages) {
const days = groupedDays(messages);
const sortedDays = Object.keys(days).sort(
(x, y) => moment(y, 'YYYY-MM-DD').unix() - moment(x, 'YYYY-MM-DD').unix()
);
const items = sortedDays.reduce((acc, date) => {
const sortedMessages = days[date].sort(
(x, y) => new Date(y.created_at) - new Date(x.created_at)
);
return acc.concat([...sortedMessages, { type: 'day', date, id: date }]);
}, []);
return items;
}
export default generateItems;
For reference here is my list as well as the renderItem function:
<MessageList
data={generatedItems}
extraData={generatedItems}
inverted
keyExtractor={item => item.id.toString()}
renderItem={renderItem}
/>
function renderItem({ item }) {
if (item.type && item.type === 'day') {
return <Day {...item} />;
}
return <Message {...item} />;
}
This is how i did it in react,
Create a new Set() to store dates uniquely
const dates = new Set();
When looping through chats array, check if date already exists in unique Set before rendering date
chats.map((chat) => {
// For easier uniqueness check,
// Formated date string example '16082021'
const dateNum = format(chat.timestamp, 'ddMMyyyy');
return (
<React.Fragment key={chat.chat_key}>
// Do not render date if it already exists in set
{dates.has(dateNum) ? null : renderDate(chat, dateNum)}
<ChatroomChatBubble chat={chat} />
</React.Fragment>
);
});
Finally, when date has been rendered, add date num into array so it doesn't render again
const renderDate = (chat, dateNum) => {
const timestampDate = format(chat.timestamp, 'EEEE, dd/MM/yyyy');
// Add to Set so it does not render again
dates.add(dateNum);
return <Text>{timestampDate}</Text>;
};

_.filter array of objects using a property with either value

I'd like to filter an array of objects I only want objects with status equals to 0 or 1 this is my code.
_.filter(array, { status: 1 || 0 });
but its not working it only fetches objects with status equals to 1.
_.filter(array, function (a) { return a.status === 1 || a.status === 0 });
Works but I'd like to know the shorthand method. How do I accomplish this without using the function method?
Edit:
Okay, got it. What I was actually looking for is arrow function.
_.filter(array, a => a.status === 1 || a status === 0);
your shorthand is not valid, read more about _.matches predicate, in your case use function
_.filter(array, item => _.includes([0, 1], item.status))
Use function as predicate:
_.filter(array, function(i) { return i.status === 1 || i.status === 0; }
https://lodash.com/docs/4.17.2#filter