I have a section called "CustomTileSection" which is having some set of components (let's say 8 components but the number of components is not static and it is dynamic). I need to show the component in Spartacus. How can I get the data which is there for every component to iterate and show it in UI?
Each CMS Page Slot can contain dynamic amount of CMS Components, to show the slot with CMS Components inside you should:
Add the CMS Slot to the Page Template via Layout Config, for example (but if the Slot is custom):
LandingPage2Template: {
slots: [
'CustomSlot10',
],
},
More details here https://sap.github.io/spartacus-docs/page-layout/.
Map CMS Component to his Angular implementation via typeCode:
ConfigModule.withConfig({
cmsComponents: {
YourComponentTypeCode: {
component: AngularComponent;
}
}
});
More here https://sap.github.io/spartacus-docs/customizing-cms-components/.
Then you can take CMS Component's by the CmsComponentData service from AngularComponent's constructor:
public constructor(
public component: CmsComponentData<CmsYourComponent>
) {}
Additional details here https://sap.github.io/spartacus-docs/customizing-cms-components/#accessing-cms-data-in-cms-components.
Please note, that if all CMS Component have the same typeCode you need to map it only once, all another work Spartacus makes by self.
Related
I am currently experiencing issues with the Configurable Product Integration of Spartacus. So far I have set up the storefront to show my configurable products, which is working quite well.
Now I am trying to customize a form element, let's say an input field to insert a custom suffix that is dynamically added by the backend. Specifically, I am trying to replace the cx-configurator-attribute-input-fieldinside https://github.com/SAP/spartacus/blob/develop/feature-libs/product-configurator/rulebased/components/form/configurator-form.component.html
So far I have tried to import it in the module:
ConfigModule.withConfig({
cmsComponents: {
ConfiguratorAttributeInputField: {
component:CustomConfiguratorComponent
}
}
})
which is not working, probably because the component is not a static CmsComponent.
I also tried importing it via outlet definition in typescript:
export class CustomConfiguratorComponent implements OnInit{
constructor(private componentFactoryResolver: ComponentFactoryResolver, private outletService: OutletService){}
ngOnInit(){
const factory = this.componentFactoryResolver.resolveComponentFactory(CustomInputFieldComponent);
this.outletService.add('cx-configurator-attribute-input-field', factory);
console.log("REGISTERED");
}
}
which is also not working.
When I add the outlet via ng-template to a template reference, I can see the component, so the import of the component should be working correctly:
<ng-template cxOutletRef="VariantConfigurationTemplate" cxOutletPos="before">
<app-custom-input-field></app-custom-input-field>
</ng-template>
How can I get this to work, so that I can replace a dynamically added Spartacus component? Specifically the ConfiguratorAttributeInputFieldComponent (https://github.com/SAP/spartacus/blob/develop/feature-libs/product-configurator/rulebased/components/attribute/types/input-field/configurator-attribute-input-field.component.ts)
In order to use a dynamically-defined single page component, we use the component tag, thusly:
<component v-bind:is="componentName" :prop="someProperty"/>
...
import DynamicComponent from '#/components/DynamicComponent.vue';
...
components: {
DynamicComponent
},
props: {
componentName: String,
someProperty: null,
}
The problem is, this isn't really very dynamic at all, since every component we could ever possibly want to use here needs to be not only imported statically, but also registered in components.
We tried doing this, in order at least to avoid the need to import everything:
created() {
import(`#/components/${this.componentName}.vue`);
},
but of course this fails, as it seems that DynamicComponent must be defined before reaching created().
How can we use a component that is truly dynamic, i.e. imported and registered at runtime, given only its name?
From the documentation: Emphasis mine
<!-- Component changes when currentTabComponent changes -->
<component v-bind:is="currentTabComponent"></component>
In the example above, currentTabComponent can contain either:
the name of a registered component,
or a component’s options object
If currentTabComponent is a data property of your component you can simply import the component definition and directly pass it to the component tag without having to define it on the current template.
Here is an example where the component content will change if you click on the Vue logo.
Like this:
<component :is="dynamic" />
...
setComponentName() {
this.dynamic = () => import(`#/components/${this.componentName}.vue`);
},
Solution for Nuxt only
As of now its possible to auto-import components in Nuxt (nuxt/components). If you do so, you have a bunch of components ready to be registered whenever you use them in your vue template e.g.:
<MyComponent some-property="some-value" />
If you want to have truly dynamic components combined with nuxt/components you can make use of the way Nuxt prepares the components automagically. I created a package which enables dynamic components for auto-imported components (you can check it out here: #blokwise/dynamic).
Long story short: with the package you are able to dynamically import your components like this:
<NuxtDynamic :name="componentName" some-property="some-value" />
Where componentName might be 'MyComponent'. The name can either be statically stored in a variable or even be dynamically created through some API call to your backend / CMS.
If you are interested in how the underlying magic works you can checkout this article: Crank up auto import for dynamic Nuxt.js components
According to the official Documentation: Starting from v2.13, Nuxt can auto import your components when used in your templates, to activate this feature, set components: true in your configuration
you are talking about async components. You simply need to use the following syntax to return the component definition with a promise.
Vue.component('componentName', function (resolve, reject) {
requestTemplate().then(function (response) {
// Pass the component definition to the resolve callback
resolve({
template: response
})
});
})
I use vue-simple-headful plugin to work with metadata in vue.js app , page title, description, keywords, etc...
I have the following setup in my vue app:
main.js
import vueHeadful from 'vue-simple-headful';
Vue.use(vueHeadful, {component: true});
component's function:
headful(vm) {
return {
title: this.category.header,
description: 'yay, a static description'
}
The page title is updated from this.category.header, but only once, during the first call to the component. Following calls to the component with the headful function do not update the page title.
I would like it to be dynamic, that is whenever component's function is called page title and other metadata is updated accordingly.
I ended up using vue-headful instead of vue-simple-headful. Following the example from How to set the Document with Vue.js has done the trick.
According to this official example, we have the ability to add nested/children routes in vuejs. But I cannot find any help/docs around a way to add these children routes dynamically. e.g only add child routes when Parent route is visited.
Currently all the routes in a Vue application are defined in a single place where we create Router instance. There is an api called addRoutes, but I don't know how to use that to add lazily loaded features of application along side their routes. If someone is familiar with Angular2+ Module system, that has this ability to define routes for the feature modules inside that module and even make them lazily loaded. Wondering if something could be achieved with VueJs?
You can use $router.addRoutes to re-add a route, specifying children.
You'll need to get the current route definition (as opposed to the $route object) by searching the $router.options.routes array for the route definition object that matches the current $route.path. Then add a children array of route definitions to the object and pass it to $router.addRoutes.
created() {
let { routes } = this.$router.options;
let routeData = routes.find(r => r.path === this.$route.path);
routeData.children = [
{ path: 'bar', component: Bar },
{ path: 'baz', component: Baz },
];
this.$router.addRoutes([routeData])
}
Here's an example fiddle of dynamically adding child routes in the created hook of a route's component definition.
I’m integrating Vue with a CMS called AEM thats works basically as component base system like Vue works too. But instead of having a webpack and imports of .vue files, every component on this CMS is a new Vue instance (new Vue({…})). So on my page I have a lot of Veu instances that communicate with each other using the same store (vuex).
This is actually working fine, but I have a scenario when I need a CMS component inside another. Since both this components are a unique vue instance and the “el” property from the parent includes the “el” from the child, the child component doesn’t work.
I know that this is not the expected use of this lib, but is there any way that I can tell or share the same “context” on both vue instances or even another approach for this scenario.
Thx,
Alexandre.
There should be only one instance of Vue.
I suggest you to create single empty Vue instance inside the body tag
All your existent Vue instances transform into components
Register all components in the root Vue instance
With this approach it will be fine to nest one component into another
You should use only one Vue instance as #shrpne mentioned.
If you keep instantiating Vue instances for every component, you'll run into issues while debugging or with component communication and overall this becomes very missy and you miss out on parent-child communication and inheritance provided by Vue.
I don't know about your Vue architecture, but I am currently working on a manual for working with Vue in AEM.
The basic premise is to use Vue's inline-template and vanilla-js, No typescript, nodeJS build, jsx or anything else at build time, just vanilla-js so that when your page is loaded and even before your js bundle is present, the DOM is already there, you just need to mount components by instantiating one Vue instance that will mount all components. This is also great for SEO (unless you plan to server-side render Vue components in java... which is possible theoretically, but good luck!)
Here is a sample AEM/Vue component:
<simple-counter inline-template>
<button v-bind:style="style" v-on:click="add">${properties.clicksText || 'clicks: '} {{ counter }}</button>
</simple-counter>
the JS:
notice how it does not have a template in the JS, because it's inlined above
Vue.component('simple-counter', {
data: function() {
return {
counter: 0,
style: {
color: 'red',
width: '200px'
}
}
},
methods: {
add: function() {
this.counter = this.counter + 1;
this.style.color = this.style.color == 'red' ? 'green' : 'red';
}
}
})
You can build more AEM components in this fashion, then at the end of your clientlib when all your Vue components have been registered, you can run:
new Vue({ el: '#app'})
This, off course, assumes that your page body or some other parent element has the id: app.
The second part of this, how do you enable re-mount of components after authoring dialog is submitted, you could just refresh the page.
I have a question about how we can re-mount components without refreshing the page here
The basic idea is to add an afteredit event to the component and run a new Vue instance only on the newly mutated component... still working on that
Solution:
Replace all new Vue(...) stuff into Vue.component(...) Vue.extend(...) etc for better interface management.
Only use ONE Vue instance witch is new Vue({...options})
Slice your vuex store into modules.
Google teacher knows everything.