Customize Certain cmsComponents that seem NOT to be overridable - spartacus-storefront

There seem to be certain Components in spartacus that are not meant to be overridden.
For Example every Component used INSIDE "CartDetailsComponent".
For example I need to do a couple of html tweaks on the "cx-cart-item" Component, but unfortunately this component is not served as overridable.
Trying to Reference it in my module with
B2cStorefrontModule.withConfig({
cmsComponents: {
CartItemComponent: {
component: CUSTOMComponent
}
does not have an effect since this component is not referenced in a provider like this
providers: [
provideDefaultConfig(<CmsConfig>{
cmsComponents: {
CartComponent: {
component: CartDetailsComponent,
},
},
}),
],
So the closest Component would be CartDetails (respectively CartComponent).
But this means, that in order to achieve a slightly altered html inside CartItem, I need to override CartComponent, and then copy & paste the entire logic (ts and html) for not only this component, but also CartItemList and finally CartItem itself.
This looks some ridiculously insane amount of effort for a simple html tweak.
Is this really a shortcoming that we just have to deal with right now, or is there another to master such requirements?

You can't customize such components via remapping it like a CMS Component, you should customize it over higher lever CMS Component.
And here you can find GitHub issue https://github.com/SAP/spartacus/issues/4441, which relates to above problem and similar Stack Overflow question - How to customize lower level components in Spartacus?.

Related

Nuxt (SSR/Vuex): Dispatch an action once all of the components have been created

Consider the following Widget component.
<template>
<component :is="name" />
</template>
<script>
import { mapActions } from 'vuex';
export default {
props: {
name: {
type: String,
required: true
}
},
created() {
this.addWidget(this.name);
},
methods: {
...mapActions({
addWidget: 'widgets/add'
}) // adds widget name to an array
}
};
</script>
I want to have multiple components like this one all over the page.
I need to be able to gather all of the component names so I can fetch the relevant data for them.
My current idea is to add the component name to the store when the created hook fires.
That works just fine, but my problem is that I can't find a way to dispatch a Vuex action to fetch the data once all of the components have been created. I tried using various hooks to no avail since none of them seems to have Vuex access.
Am I missing something? Is there some kind of hook that fires after all of the components have been created (SSR) that has Vuex access. Or maybe there's some better way to achieve what I've been trying to do?
Why do you want to wait until all the widgets have been loaded to fetch the data? Instead, what I'd do is fetch the data for each Component as they get added in the page. Using this approach, each component would require a specific piece of data, and nothing better than to load each of them in a different request. Easier to test, easier to trace, adheres to the single responsibility principle.
If you are worried that two components may require the same data, then you can route all requests through an Object that hashes the request (endpoint + verb if using REST) and keeps a pool of active connections, so that if two requests come in one reuses the Promise of the other, and only one of them reaches the server.
Now, if you really want to do it using your original approach, make it so that each widget emits an event once it's been registered. The events are collected by the parent component (maybe the Page that loads all the widgets). The page knows which Widgets are being loaded. Every time an event is received it sets a flag on that specific widget to indicate it's been loaded. Once all widgets get a flag, the Page triggers a request to fetch the data.

path-to-regexp Find regular expression matching the route

I am adding dynamic child components while the page loads. Adding child routes in OnCreated of parent does not make the page work when we refresh the page.
Hence, I am parsing the page templates (as I know them when the page loads).
I am now looking for a way to know the route which matches the href. Vue-js uses path-to-regexp and my question is very simple.
I want to know the matching component
const router = new VueRouter({
routes: [
// dynamic segments start with a colon
{ path: '/user/:id', component: User },
{ path: '/foo/bar', component: FooBar },
]
})
// Reverse of this
var matchingComponent = howDoIDothis(/foo/bar) // this should give me the matching
I need this so that I can remove from the path and then add the child component to the parent dynamically.
You need to use Vue router's router.getMatchedComponents method. However, this method needs that your router is fully initialized with all the routes. Otherwise, there is no way. Vue-router doesn't expose underlying parsed Regular expressions against which you can compare your href.
Also, what you are trying to do is not the most idiomatic way of doing things in Single Page Applications. A good practice is to declare all your routes upfront in some JS file using which you should initialize your router. Of course, you will want to protect certain routes for which you should use Route guards.
Finally, when you have all your routes declared upfront means you have all the components bundled upfront in one big JS file. To avoid this, wrap your component in async wrappers and bundler like Webpack would be smart enough to split the bundle into multiple smaller files.

With VueJS, How can I intelligently retain Vuex state between components?

First, think of a table that has a search, sort, and pagination. Items (rows) have details, which loads a route to show the detail component. Within this detail component, there's another table with a search, sort, and pagination, that of which also has a detail view to drill down further.
I'm using vuex to retain the state of most components, at least where it makes sense. In this case, the search query, sort, and pagination are all stored in the store. Also keeping in mind, I'm trying to decouple my components as much as possible to make testing easier and provide better maintainability.
Imagine this routing structure:
/Properties Shows a list of property locations
/Properties/location/1 Shows a list of buildings within that location
/Properties/location/1/building/1 Shows a list of offices within that building
Let's say I'm on the first route, and I search for a location, that of which produces 3 records that match the search query. Great! I click on the first one, view some details... and click Go Back. My search is still intact as it was stored in vuex so my state is retained. Perfect.
I navigate away to another route, entirely separate from the locations section, and then navigate back. Yuck, my search is still there. This isn't what I would expect (intuitively), but would expect programmatically.
Here's how I solved it. Please tell me if there's a better way.
In my routes, I always use props, and in my main navigation component, I set it up to pass a clearState property.
So the component has this:
props: {
clearState: {
type: Boolean,
default() {
return false;
}
}
},
created() {
if (this.clearState) {
this.$store.dispatch("properties/setSearch", "");
}
this.$store.dispatch("fetchLocations");
}
And my route in the main navigation menu has this:
<router-link :to="{ name: "Locations", params: { clearState: true }}">Locations</router-link>
This works great. But, I have several layers of detail components as we drill down, so now I feel like I'm getting tightly coupled:
created() {
if (this.clearState) {
this.$store.dispatch("properties/setSearch", "");
this.$store.dispatch("buildings/setSearch", "");
this.$store.dispatch("offices/setSearch", "");
}
this.$store.dispatch("fetchLocations");
}
I played around with router guards, but access to this within the component isn't immediately available, which slows the request to my API. It also forced me to use meta properties to determine what to and from routes should force the clearing of state.
My Question
I've solved my issue, but I have a gut feeling there's a better design pattern out there that I haven't figured out yet.
Is my solution prone to unseen error?
Is there a better way to do this that I'm obviously missing?
I hope this makes sense.

Can Vue-Router handle clicks from normal anchors rather than router-link?

I have a scenario where there are two major components on a page; a frame-like component that contains common functionality for many applications (including a bookmark/tab bar) and my actual application code.
Since the frame doesn't actually own the page that it's included on, it seems like it would be incorrect for it to define any routes, however the current page may define their own routes that may match one of those links. In that case, I'd like vue-router to handle those anchor clicks and navigate appropriately rather than doing a full page reload.
Here's a simplified template of what this looks like:
Frame (an external dependency for my app):
<Frame>
<TabStrip>
</TabStrip>
<slot></slot>
<Frame>
App1:
<Frame>
<App>You're looking at: {{ pageId }}!</App>
</Frame>
So when any of the app1 domain links are clicked from that tab strip, I want my route definitions in app1 to pick that up rather than it causing a page load. Since that component is owned by the frame, I don't have access to write <router-link> since links to many different apps may co-exist there.
Any thoughts?
Whoo, this is an old one! However, since this question was high in my search results when I was researching this problem, I figured I should answer it.
My use-case was similar to the one in the comments: I needed to capture normal <a> links within rendered v-html and parse them through the router (the app is rendering Markdown with a light modification that generates internal links in some cases).
Things to note about my solution:
I'm using Vue3, not Vue2; the biggest difference is that this is the new Vue3 composition-style single page component syntax, but it should be easy to backport to Vue2, if necessary, because the actual things it's doing are standard Vue.
I stripped out the markdown logic, because it doesn't have anything to do with this question.
Note the code comment! You will very likely need to design your own conditional logic for how to identify links that need to be routed vs. other links (e.g. if the application in the original question has same-origin links that aren't handled by the Vue app, then copy/pasting my solution as-is won't work).
<script setup>
import { useRouter } from "vue-router"
const router = useRouter()
const props = defineProps({
source: {
type: String,
required: true,
},
})
function handleRouteLink(event) {
const target = event.target
// IMPORTANT! This is where you need to make a decision that's appropriate
// for your application. In my case, all links using the same origin are
// guaranteed to be internal, so I simply use duck-typing for the
// properties I need and compare the origins. Logic is inverted because I
// prefer to exit early rather than nest all logic in a conditional (pure
// style choice; works fine either way, and a non-inverted conditional is
// arguably easier to read).
if (!target.pathname || !target.origin || target.origin != window.location.origin) {
return
}
// We've determined this is a link that should be routed, so cancel
// the event and push it onto the router!
event.preventDefault()
event.stopPropagation()
router.push(target.pathname)
}
</script>
<template>
<div v-html="source" #click="handleRouteLink"></div>
</template>

How to properly fetch data from API in vuex?

I'm working on my Vue.js application and having a trouble with fetching data via API with vuex-router-sync.
As I saw in every tutorial or sample, it is common thing to dispatch the store action on created component hook. In my case it doesn't seem to be an option and here's why:
I use the standard vue-router for my routing, and when I navigate between pages not only my content should change, but also my sidebar and header. Thus I implemented the named router-view concept, such as
routes: [{
path: '/',
components: {
page: Home,
sidebar: GeneralSidebar,
header: HomeHeader
}
}, {
path: '/game/:id',
name: 'game',
components: {
page: Game,
sidebar: GameSidebar,
header: GameHeader
}
}]
But the Game, GameHeader and GameSidebar should share the same getter for the currently selected game. It's impossible to decide, which one of those components should be dispatching the action to fetch the data.
I tried to hook on the router itself, on beforeEnter, but faced the issue, that navigating between the same routes (in my case from /game/1 to /game/2) does not trigger the beforeEnter.
Is there any way that I can hook on any route navigation? Or maybe a better pattern for dispatching fetch-actions?
Thanks in advance!
There are many ways to do this. For example You could fetch data inside some component which is loaded by route change and after set data to vuex by dispatching change. This does the job perfectly. Also keep in mind that there are few ways to fetch data inside any component - You can hook that in any of Vue lifecycle hooks. Take a look here https://router.vuejs.org/en/advanced/data-fetching.html
Also sometimes You need some data upfront of any route change and here You can use the same approach - make some request when Vue app is loaded.