Obtaining cms component data inside slot - spartacus-storefront

We are trying to style an existing slot (SiteLinks) that contain a number of CMSLinkComponents. We are failing to obtain the full data of the components, because we are only receive the uid and typeCode when we use let-[var] syntax on the ng-template.
Are there example of how to access the full data for these components, without requesting them separately based on the uid?
<ng-template cxOutletRef="SiteLinks" let-data>
<pre>{{ data.components$ | async | json }}</pre>
</ng-template>

The context model depends on where outlets are used. In your case context is a Slot model, and on this level of hierarchy you have only list of components and not their data, because data you should get separately.
I believe, this approach will work:
<ng-template cxOutletRef="SiteLinks" let-slot>
<ng-container *ngFor="let component of (slot.components$ | async)">
<ng-container [cxComponentWrapper]="component"></ng-container>
</ng-container>
</ng-template>
cxComponentWrapper also can be helpful in cases when you need handle nested components.

Related

Using computed value in v-for (Vue.js)

I'm currently making bitcoin trading web app (personal project. Not a business one)
I have a bunch of cryptocurrencies prices from API, and showing it all by using v-for loop.
here's part of my code:
<tbody class="text-sm">
<tr v-for="coin in props.coins" :key="coin.code" class="hover:bg-zinc-600">
<td class="p-1">
<div class="font-semibold">{{ coin.name }}</div>
<div class="text-xs text-gray-400">{{ coin.code }}</div>
</td>
<td
class="text-right font-bold align-top p-1"
>{{ Number(coin.trade_price).toLocaleString() }}</td>
<td
:class="{ 'text-blue-400': isNegative(coin.signed_change_rate), 'text-red-400': isPositive(coin.signed_change_rate) }"
class="text-right p-1"
>
<div>{{ Number(coin.signed_change_rate).toFixed(2) }}%</div>
<div class="text-xs">{{ coin.signed_change_price }}</div>
</td>
<td class="text-right align-top p-1">{{ convertTp24h(coin.acc_trade_price_24h) }}</td>
<td class="text-right p-1">
<button class="bg-red-700 rounded-lg hover:bg-red-600 p-1">매수</button>
</td>
</tr>
</tbody>
As you can see I have many methods and expressions that converts raw value from API to human-readables.
I'm wondering if this is a bad practice. I heard that methods are called everytime so, rather use computed().
But as you can see I have my values from API in Object(props.coins) and I'm looping this object in v-for. Can I still use computed() methods to convert a value inside an Object that is also looped by v-for?
You can't use computed for items in a loop, since they don't take in arguments. Computed is used for .. computed properties and methods take in arguments e.g. formatChangeRate()
You are right that methods are called everytime, but that's totally fine and normal practice.
So creating a component with methods like formatChangeRate, formatTradePrice is totally fine.
There are ways to make it work with computed properties but in my opinion its not worth it.
1)
You coould make another component that takes in the item as a prop, uses computed property and displays it in componenent's template but that's a total overhead.
2)
You could map the array and reference it by index. Something like:
computed: {
changeRate() {
this.coins.map(coin => {
return Number(coin.signed_change_rate).toFixed(2) + '%'
})
}
}
Now changeRate is an array of formatted change rates so you could in your v-for do something like
v-for="(coin, index) in coins)">
<td>changeRate[index]</td>
So now you're looping through the array multiple times, and the code is less testable and less readable.
In my opinion using methods is totally fine and the right way to go in this example. The performance of calling a simple formatting method multiple times is negligible.
Also the cache you're refering to is that computed properties are cached based on their reactive dependency. E.g. you have in your data, firstName and lastName. You could then make a computed property called fullName that concats those two. The caching would work so that fullName doesn't change unless firstName or lastName changes.
Since you're looping through an array the value that would be computed is always changing.
you are right, methods are called everytime and in theory thats not really good, but in practice if your method just concat strings or other simple operations, that's really not a big deal. Your computer can do millions of operations like that per seconds.
In the other hand if you do complex operations then the solution is to precalculate before.
After you receive your data construct an Array looking something like bellow and use this one in your template
(pseudo code)
[{
name,
code,
changeRate: Number(signed_change_rate).toFixed(2),
...
},
...
]

vue application best practice/pattern to display data with people-friendly label?

A high-level vue application question:
A little background:
small team, still learning vue
creating a new large reasonably complex vue app from scratch
vue-cli scaffolded (vue 2, veux, router, i118n, bootstrapVue)
there are many different application entities – e.g. "user", “product”, etc.
there are many of each item, and each has a data record (from a db) that includes a number of boolean fields such as "active", “enabled”, “is_sensitive”.
item data displays in many different contexts, including: search results, table lists (e.g. browse), individual landing views for each (an item detail page), as well as ancillary lists (e.g. “A related item:”
The key problem:
In every display situation, the boolean values should to be translated from machine-friendly to people-friendly terminology.
The appropriate human-friendly term depends on the field itself (so there is no single translation for all fields).
Example:
Machine-friendly data:
[
{
name: “Megaphone”,
is_embarrassing: false,
active: false,
},
{
name: “Wankel Rotary Engine”,
is_embarrassing: true,
active: true,
},
]
Human-friendly list:
+----------------------+----------+---------------------+
| Name | Active? | Embarrassing? |
+----------------------+----------+---------------------+
| Megaphone | Inactive | Not embarassing |
| Wankel Rotary Engine | Active | Item is Embarassing |
+----------------------+----------+---------------------+
The key question:
What is the best way to solve this in a scalable, efficient, elegant, sensible way?
I have thought of a couple of options…neither of these feel scalable nor elegant,
and are brittle.
(1) sequence of in-line v-if conditions within the component view template
<p v-if=“item.property.is_embarrassing">
Item is Embarrassing
</p>
<p v-else>
Not embarassing
</p>
(2) computed properties in the component
<p>
{{ detailsPropertyEmbarrassing }}
</p>
detailsPropertyEmbarrassing() {
return item.property.is_embarrassing ? “Item is Embarrassing : “Not Embarrassing”;
},
I have also been noodling over the idea of some sort of Map that is imported along with the data and used to get the right labels, but I haven’t completely worked that out yet.
What is a solution for transforming data fields to people-friendly labels across an entire application, for a variety of different display situations?
(Side note: I may also need to transform the field in other ways, such as truncating length…)
And is there a way to establish this globally in the app in a manner that is scalable, both technically and organizationally,
so that new components can display as desired, near-automatically?
This seems like a basic fundamental need in any app of size, not just vue,
so I feel like this has been/has to have been solved before, but either I cannot find the right research keywords or am missing something obvious.
Thanks in advance!
You can think of the VueComponent as a ViewModel (or Controller if you like MVC). It's indented purpose is to fetch information from the model (backend) and transform it to something that your view can use.
Thus, the change from a boolean to a string should be done in the VueComponent and not in the view.
In your view component, declare something like myItems = []. When you fetch it, traverse the result:
backendResult.forEach(item => {
this.myItems.push({
name: item.Name,
embarrassing: item.IsEmbarrassing ? "Oh yes!" : "No"
});
Then the view is simplified:
<table>
<tbody>
<tr v-for="item in myItems">
<td>{{item.Name}}</td>
<td>{{item.embarrassing}}</td>
</tr>
</tbody>
</table>
Another solution is to use filters which requires no modification of the vue component and just a small change of the view:
<table>
<tbody>
<tr v-for="item in myItems">
<td>{{item.Name}}</td>
<td>{{item.isEmbarrassing|embarrassing}}</td>
</tr>
</tbody>
</table>
Notice the |embarrassing part in the view. It uses a filter defined as:
Vue.filter('filter', function (value) {
if (!value) return 'not embarrassing'
return 'embarrassing';
})

In Vue, how can I reference multiple similar elements from server rendered template?

I have a table of number inputs for different products. Each row looks like this:
<tr>
<td><input type="number" name=quantity1 value="0"></td>
</tr>
At the bottom, I'd like to show a sum of quantities based on what the user inputs on each row. All html code is server rendered and I only use Vue for the interactive part, so I'm not using v-for to generate the list on the client.
My question is: Is there a way to annotate the server template (or another technique) so that I can easily reference the input value of each row with Vue (aside from something like vanilla querySelector)? I imagine there must be something more in the lines of v-model that would perhaps collect all values in a data array or similar. Thanks!

Vue-Tables-2: How to pass a VueJS custom filter?

How would one go about applying a VueJS filter (i.e. {{ price | currency }}) to data displayed using vue-tables-2?
I tried playing around with slots with one of the demo tables to no avail: https://jsfiddle.net/jfa5t4sm/11/
This is a bit annoying, as 'custom filters' mean different things in different context, so searching the docs is not bearing fruit.
Anyone have ideas?
Since it's a scoped slot you have to access the value through the props object and give it the correct slot name.
<template slot="price" scope="props">
<div>
<p>{{ props.row.price | currency }}</p>
</div>
</template>
Working JsFiddle

Naming the model and initliaze it dynamically in Vue.js

I am aware in Vue, every reactive data properties need to be declared in the Vue instance.
I have a situation where I need pull sets of data from the server, say tickets, and I need to have a model associated to each of these data when using v-for
Is there any way I can name the v-model using the ticket variable properties, ie the id. Is this possible?
v-model="{{ ticket.id }}"
If it is possible, how I declare the reactive data properties in vue instance dynamically?
Something like this:
<div id="example-1">
<div v-for="ticket in tickets">
<p> {{ticket.name }} </p>
<input type="number" v-model="{{ ticket.id }}" min="0" placeholder="0">
</div>
</div>
Answer of second question
You can use vm.$set for your case. This can add new reactive properties dynamically.
Set a property on an object. If the object is reactive, ensure the property is created as a reactive property and trigger view updates.
So once you receive response from server, you can apply vm.$set on the received object.
Answer of first question
Yes, you can name the v-model using the ticket variable properties using v-model, however syntax you are using is little wrong, it should be:
v-model="ticket.id"
Assuming you populate the tickets array in your Vue components' data object once received from the server, you can then iterate the array of tickets as you laid out in your example.
You can then tie the input model to the ticket object property directly:
<input type="number" v-model="ticket.id" min="0" placeholder="0">