How to override a specific Spartacus component - spartacus-storefront

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?

Related

Is it possible to have dynamic element names in a Vue template?

I need to have a component, where I get the type of another component that this component should create. (it could be one of n different elements) Doing this in the component's render function is not a problem, but since I am new to Vue and I am trying to do things the Vue way, not the way I would do it in React.
I have not been able to find any solution with just using a Vue template. Is there one?
This is how I would like to be able to use the component:
<factory elm="first-component"></factory>
The factory should then internally in some way result in this:
<first-component></first-component>
(it should also be able to add attributes to the component, but that I know how to do, so a suggested solution does not need to care about attributes)
There is <component :is="myCoolComponent"></component> that will basically generate <my-cool-component></my-cool-component>.
Is it what you're looking for?
From the documentation: https://v2.vuejs.org/v2/guide/components-dynamic-async.html#keep-alive-with-Dynamic-Components
You could also totally create a Factory.vue component and put that kind of logic inside of it (<component :is="" ...>).

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

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.

Vuejs component with html attribute

I am using single file components and I am wondering if something like this is possible:
<my-component param1='comp1'></my-component>
<my-component param1='comp2'></my-component>
<my-component param1='comp3'></my-component>
So when later methods for component my-component are called, each component can read param1 attribute. Is this possible with vuejs?
Just to clarify things little bit more:
I am writing component for table pagination - everything is cool, except for the case when there are several tables on the same page. In that case I would also need several pagination components. So I need a method which will connect table and pagination component through table id.
Something like this:
<table id="tbl1"></table>
<pagination-comp tblid="tbl1"></pagination-comp>
<table id="tbl2"></table>
<pagination-comp tblid="tbl2"></pagination-comp>
This exactly what props did. Please follow this guide.

VueJs - Deleting a File-input component from a list

I'm still pretty new to vue.js.
I recently created some vue components to tidy up my html. The components are different inputs like text and checkbox. They update the parents data using $emit('input', val). And make sure that the components are up-to-date using props and watch() on those props.
Now there is a problem with using watch() on <input type="file />, since you cannot set the input value like you can on other input types.
Using this example:
https://jsfiddle.net/minde281/nyu73dz6/25/
I have a list of, in this case, items on a shoppinglist. You can add an image for each item.
The image is loaded and added to the list. This works fine.
To get my problem:
add an image to item1
delete that item by clicking the X-button
The result is now that item2 will have that image on the <input type="file" />. The preview works as expected since this can be set through script and therefore use watch(). But somehow vue removes the wrong part of the html markup causing the last one to be removed.
Is there a different way to solve this problem? Or is there a way to tell vue to remove the correct part of the html markup?
-Minde
Vue tries to reuse the component and thats why the selected file is in the second input after deleting the component.
To prevent that you should use the key binding like this:
<li v-for="(item, index) in items" :key="item.name">
Now the hole component gets removed and everything should work as expected.

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.