Is there any support of custom AbstractCMSComponentContainer in Spartacus (SmartEdit)? - spartacus-storefront

I had the following data model in backend:
AccordionComponentContainer extends CMSTabParagraphContainer
AccordionItemComponent extends SimpleCMSComponent
The container extending the CMSTabParagraphContainer because extending the
AbstractCMSComponentContainer is pain in the ass (generated jalo classes has
to be adapted but this isn't important for this case, only for
understanding.
Now I have a component in Spartacus CmsAccordionComponent. I introduced a
component mapping:
AccordionComponentContainer: {
component: CmsAccordionComponent,
},
In my component html file I have something like this:
<h2>{{headline$ | async}}</h2>
<ul>
<ng-container *ngFor="let component of components$ | async; let i = index">
<li>{{component.content}}</li>
</ng-container>
</ul>
I used the files in
projects/storefrontlib/src/cms-components/content/tab-paragraph-container as
reference for my implementation (e.g. component implementation). Expect of ng-template (cxOutlet):
<ng-template [cxOutlet]="component.flexType" [cxOutletContext]="{}">
<ng-container [cxComponentWrapper]="component"></ng-container>
</ng-template>
Before, I tried the same solution as CMSTabParagraphContainer. For some reason this won't work in my project. I introduced an own component and a mapping for the children (AccordionItemComponent) but it didn't work. The child components aren't shown.
So I used my solution described above. With my solution the components are displayed (also the child components) but I cannot edit them in SmartEdit. Maybe it's related with this issue: https://github.com/SAP/spartacus/issues/1484.
For testing purpose I added the 'normal' CMSTabParagraphContainer with
CMSParagraphComponent's to my content slot in the backoffice. And I can edit
the first CMSParagraphComponent that is shown in SmartEdit. Unfortunately I
cannot add new paragraphs to the CMSTabParagraphContainer. Therefore I think
that the ng-template (cxOutlet) solution is the better one as mine.
Can you please explain how the TabParagraphContainerComponent and the snippet
ng-template (cxOutlet) works? Also I think that this should considered in the
github issue ticket (https://github.com/SAP/spartacus/issues/1484) so that
CMSTabParagraphContainer (AbstractCMSComponentContainer) are better supported
in Spartacus (SmartEdit).
Thanks for your help!

The most important piece is the cxComponentWrapper. This directive takes the component slot data and renders the component inside.
The cxComponentWrapper requires the following data set for each component:
{
flexType: item.typeCode,
typeCode: item.typeCode,
uid: item?.uid
}
A typical container component template implementation would iterate over the various components and applies the directive:
<ng-container *ngFor="let item of items$ | async">
<ng-container
[cxComponentWrapper]="{
flexType: item.typeCode,
typeCode: item.typeCode,
uid: item?.uid
}"
>
</ng-container>
</ng-container>
The problem that you'll likely face, is the lack of the component type in the container component cms data. The cms api will only expose the component UIDs for the various nested components. You need to fetch the component type from the backend, using CmsService.getComponentData. You need to do this for each component uid. If you do this in a loop, Spartacus will actually merge the various calls to CmsService.getComponentData and do a single call to the backend.
An example of such an implementation can be found at https://github.com/SAP/spartacus/blob/746a15c1b63998065b0ceea96f4da052829533fb/projects/storefrontlib/src/cms-components/content/banner-carousel/banner-carousel.component.ts#L25.

Related

Vue component communication between header component and components in the router-view

Im facing a problem for my VUE app, Im using the vue Router to navigate to my component
In my Header component I use router-link to navigate to a Home component
The problem is :
In my Header component I would like a checkBox (a boolean variable) that change the content of my Home component (rendered in the router-view) like a v-if that would check the boolean variable in the Header
Here is my App.vue template I was trying to solve the problem through emits but Im Kinda stuck for passing data inside a component (inside the router-view)
<template>
<div class="content">
<HeaderComponent #eventCheckBox="handleCheckBox" />
<router-view />
<FooterComponent />
</div>
Do you guys have already faced this issue, is there a way to do it the classic way or should I try plugins like Portal or Teleport ?
Portal and Teleport are the same, just a different name (teleport in Vue3, being the official name).
As of the rest of the question, this explains it very well: https://stackoverflow.com/a/49702934/8816585
Mainly, you need to see if you need to use a store like Pinia (recommended one) and Vuex. Most of the time, you could use a parent/child relationship. Here is another article explaining when you would need something like that: https://markus.oberlehner.net/blog/should-i-store-this-data-in-vuex/#alternatives-to-storing-data-in-vuex
In Vue3, you could even not use a store and rely solely on the singleton pattern + composables (again, a lot of articles are available for that one).
TLDR: use your Vue devtools and inspect the state flowing.
If you need more, reach for more powerful tools.

How to override a specific Spartacus component

I am using the same component twice in Spartacus but with different data.
I am trying to override this component but if I override it with the type code they will both be identical while I need them to still be different.
Is there a way to use a specific id to target a specific instance of the component instead of overriding all of them ?
In your case, since the two components are the same but have different data, I assume the data would come as input.
In this case, I would suggest using outlets in the app.component.html file. For example:
<ng-template cxOutletRef="section1" >
<component [inputData]="data1"></component>
</ng-template>
<ng-template cxOutletRef="section2" >
<component [inputData]="data2"></component>
</ng-template>
In this case, they would be rendered in their respective sections but with different data. Does this answer your question?

CMS Outlet References is not clear how to work

In this doc I found that I can customize our CMS templates, slots and components:
Data-driven outlets are provided by the CMS structure. There are three types, as follows:
CMS Page layout name: Each page layout is available as an outlet reference.
CMS page slot positions: Each slot position is an outlet reference. Since slot positions are not necessarily unique throughout the CMS structure, an outlet template might occur more then once. There is currently no standard technique available to limit the outlet for a specific position or page.
CMS Component type: Each component type is available as an outlet. While component type-driven outlets can be used, it is generally considered best practice to leverage Customizing CMS Components for introducing custom component UI.
Could you please provide any example how to do that with outlets if I have next custom structure:
<main>
<cx-page-layout class="AccountDetailsPageTemplate">
<cx-page-slot class="BottomHeaderSlot"></cx-page-slot>
<cx-page-slot class="AccountMenuSlot"></cx-page-slot>
<cx-page-slot class="AccountContentSlot"></cx-page-slot>
</cx-page-layout>
</main>
As result I would like to have next layout view:
I know how to do that using global styles only, but preferably is to use templates (outlets), because it can be the case when we require to add some extra parent div's over the slots and so on.
To build such structure with provided set of Slots, we can do next:
app.component.html (REQUIRED)
<ng-template cxOutletRef="AccountDetailsPageTemplate">
<app-account-details-page></app-account-details-page>
</ng-template>
account-details-page.component.html
<h1>
Hello AccountDetailsContentSlot
</h1>
<div class="d-flex">
<div class="w-50">
<cx-page-slot position="AccountMenuSlot"></cx-page-slot>
</div>
<div class="w-50">
<cx-page-slot position="AccountContentSlot"></cx-page-slot>
</div>
</div>
I didn't put here BottomHeaderSlot, because it should't be here, it should be implemented in cx-header component.

Basic custom element does not seem to work

I'm trying to create a standard user status widget for my Aurelia app, and I'm not sure what I'm doing wrong. As a starting point I followed the docs, but my results aren't what they tell me to expect and I'm not getting errors either in build nor in the browser.
Relevant files are as follows:
<!-- nav-bar.html -->
<template bindable='router'>
<require from="./user-status "></require>
<!-- various nav buttons -->
<p class="navbar-collapse collapse navbar-text">
Test <user-status></user-status>
</p>
user-status.html
<template>
${status}
</template>
user-status.js
export default class UserStatusCustomElement {
constructor() {
this.status = 'Be sure to drink your Ovaltine!';
}
}
if I change the require in nav-bar.html to look for ./user-status.html it appears to have an effect (additional aurelia-looking attributes are added to the user-status element in the rendered html) but does not render the message (one assumes b/c it's not picking up the class and rendering as an html-only thing). If I leave as-is, it doesn't error but those attributes are not added and nothing is rendered, even static text.
I played around with your code and found that removing default from the user-status.js module fixed the problem. I suspect the reason has something to do with how Aurelia utilizes module-loaders (System.js, webpack, ...) when importing modules. Unfortunately I don't know enough about the internals of Aurelia to give a more in-depth answer.

Aurelia: How can I modify sidebar content from inside a router view?

I'm trying to wrap my head around how "inner components" can adjust the content of "outer components". Let's say I have an application template that looks something like this:
<template>
<div class="sidebar">
<div>Some app-wide content</div>
<div>
<!-- I want to put some view-specific content here -->
</div>
</div>
<div class="main-body">
<router-view></router-view>
</div>
</template>
Each subview wants to render different content to the sidebar. Obviously this would be easy if the subview included the sidebar area itself, but let's say it is important to preserve the structure and we don't want to replicate the boilerplate of the sidebar across every view.
Is there any way for a child view to declare "export this extra component for display in another place?" I imagine something like injecting the parent view and calling a method on it, but I can't figure it out from the documentation.
Simple demo:
It's fairly simple, actually. Just import and inject your sidebar or any other viewmodel and call a method or update a property.
https://gist.run/?id=745b6792a07d92cbe7e9937020063260
Solution with Compose:
If you wanted to get more elaborate, you could set a compose view.bind variable to that your sidebar would pull in a different view/viewmodel based on the set value.
https://gist.run/?id=ac765dde74a30e009f4aba0f1acadcc5
Alternate approach:
If you don't want to import, you could also use the eventAggregator to publish an event from the router view and subscribe to listen to that event from your sidebar and act accordingly. This would work well for a large app setting where you didn't want to tie them too closely together but wanted the sidebar to react correctly to unpredictable routing patterns by always responding when triggers were published.
https://gist.run/?id=28447bcb4b0c67cff472aae397fd66c0
#LStarkey's <compose> solution is what I was looking for, but to help others I think it's worth mentioning two other viable solutions that were suggested to me in other forums:
View ports. You can specify multiple named router views in a template and then populate them by passing in a viewPorts object to the router instead of specifying a single moduleId. The best source of documentation is a brief blurb in the "Cheat Sheet" of the Aurelia docs.
Custom elements. It's a little more "inside-out" but you could define most of the outer content as a custom element that has slots for the sidebar and the main body; each child view would define this custom element and pass in the appropriate pieces.