Upgrade to 20.0.0
Why you should upgrade
This version has a lot of overall SEO improvements and also introduces full flexibility in creating your own widgets.
See release notes for more information
If you are using GTM and update to this version you will need to implement and start using the @geins/ralph-module-gtm module instead, and change your GTM accordingly.
Pre-requisites
Before upgrading to 20, make sure you have already upgraded to 19. See guide here
Steps (required updates)
Update all list pages
The way Ralph UI fetches listPageInfo has completely changed and needs us to update all our list pages.
List pages with static list info
For list pages with static list info (like _search.vue and all.vue), update CaListPage to use a static object for listInfo.
<template>
<CaListPage :type="listType" :list-info="staticListInfo" />
</template>
<script>
export default {
...
data: () => ({
listType: 'all'
}),
computed: {
// Create the object to send to CaListPage. This is the same as the object you would get from the query
staticListInfo() {
const title = this.$t('ALL_PAGE_TITLE');
return {
name: title,
meta: {
title,
description: title
}
};
}
},
...
}
</script>
<template>
<CaListPage :type="listType" :list-info="staticListInfo" />
</template>
<script>
export default {
...
data: () => ({
listType: 'search'
}),
computed: {
// Create the object to send to CaListPage. This is the same as the object you would get from the query
staticListInfo() {
const title = this.$t('SEARCH_RESULTS_PAGE_TITLE', {
search: this.$route.params.search
});
return {
name: title,
meta: {
title,
description: title
}
};
}
},
...
}
List pages with dynamic list info
For list pages with dynamic list info (like for example pages/list/_category.vue and pages/list/_list.vue), update CaListPage to use MixListInfo
// The same update goes for brand and discountCampaign
<template>
<CaListPage
:type="listType"
:list-info="listInfo"
:current-alias="currentAlias"
/>
</template>
<script>
import MixListInfo from 'MixListInfo';
export default {
...
mixins: [MixListInfo],
data: () => ({
listType: 'category'
}),
computed: {
currentAlias() {
return this.$route.params.category.split('/').pop();
}
},
...
</script>
<template>
<template>
<CaListPage :type="listType" :list-info="staticListInfo" />
</template>
<script>
import MixListInfo from 'MixListInfo';
export default {
...
mixins: [MixListInfo],
data: () => ({
listType: 'list'
}),
...
}
</script>
Use productImages instead of images
If you have overriden or ralph-ride any GraphQL queries, you will need to update them to use productImages instead of images.
Everywhere product.images is used, change it to use product.productImages instead. Note that this is an array of objects instead of an array of strings and you will find the filename in product.productImages[i].fileName.
Example:
<template>
...
<CaImage
v-if="product.productImages && product.productImages.length > 0"
...
:filename="product.productImages[0].fileName"
...
/>
...
</template>
Unchanged example from launchpad with version ralph-ui 20.0.0 :
<template>
<component :is="baseTag" class="ca-product-card">
<div v-if="productPopulated" class="ca-product-card__image-wrap">
<NuxtLink
class="ca-product-card__image-link"
tabindex="-1"
:to="product.canonicalUrl"
:data-alias="product.alias"
@click.native="productClickHandler"
>
<CaImage
v-if="product.productImages && product.productImages.length > 0"
class="ca-product-card__image"
type="product"
:size-array="
$config.imageSizes.product.filter(
item =>
parseInt(item.descriptor) < 1150 &&
parseInt(item.descriptor) > 186
)
"
:ratio="$config.productImageRatio"
:filename="product.productImages[0].fileName"
:alt="product.brand.name + ' ' + product.name"
sizes="(min-width: 1360px) 248px, (min-width: 1024px) 18.23vw, (min-width: 768px) 30.73vw, 48vw"
/>
<CaImage
v-else
class="ca-product-card__image"
:ratio="$config.productImageRatio"
:src="require('~/assets/placeholders/product-image-square.png')"
:alt="product.brand.name + ' ' + product.name"
/>
</NuxtLink>
<CaToggleFavorite
class="ca-product-card__favorite"
:prod-alias="product.alias"
:prod-id="product.productId"
/>
</div>
<CaSkeleton
v-else
class="ca-product-card__image-wrap"
:ratio="$config.productImageRatio"
:radius="false"
/>
<div class="ca-product-card__info">
<NuxtLink
v-if="productPopulated"
:to="product.canonicalUrl"
@click.native="productClickHandler"
>
<CaBrandAndName
:brand="product.brand.name"
:name="product.name"
name-tag="h2"
/>
<CaPrice class="ca-product-card__price" :price="product.unitPrice" />
<CaCampaigns
v-if="product.discountCampaigns && product.discountCampaigns.length"
class="ca-product-card__campaigns"
:campaigns="product.discountCampaigns"
/>
<CaStockDisplay
class="ca-product-card__stock-display"
:stock="product.totalStock"
/>
</NuxtLink>
<div v-else>
<CaSkeleton width="30%" />
<CaSkeleton width="70%" />
<CaSkeleton width="50%" />
</div>
<CaButton
class="ca-product-card__buy-button"
type="full-width"
:loading="addToCartLoading"
@clicked="addToCartClick"
>
{{
skuId && product.totalStock.totalStock > 0
? $t('ADD')
: $t('READ_MORE')
}}
</CaButton>
</div>
</component>
</template>
<script>
import MixProductCard from 'MixProductCard';
export default {
name: 'CaProductCard',
mixins: [MixProductCard],
props: {},
data: () => ({}),
computed: {},
watch: {},
created() {},
methods: {}
};
</script>
<style lang="scss">
@import 'organisms/ca-product-card';
</style>
Update props for CaWidgetArea in CaListPage
CaWidgetArea is no longer fetched by the MixListPage mixin. Update the props of your widget area so that it fetches itself internally.
Example:
<template>
...
<CaWidgetArea
class="ca-list-page__widget-area"
family="Productlist"
area-name="The top part of the product list"
:filters="widgetAreaFilters"
:list-page-url="currentPath"
/>
...
</template>
Unchanged example from launchpad with version ralph-ui 20.0.0:
<template>
<div class="ca-list-page" :class="modifier">
<CaContainer>
<CaBreadcrumbs v-if="listInfo" :current="breadcrumbsCurrent" />
<CaSkeleton v-else class="ca-breadcrumbs" width="30%" />
<CaListTop v-if="!hideListInfo" :type="type" :list-info="listInfo" />
<CaImage
v-if="type === 'category' && listInfo && listInfo.primaryImage"
class="ca-list-page__image"
size="1280w"
type="categoryheader"
:alt="listInfo.name"
:filename="listInfo.primaryImage"
:ratio="271 / 1280"
sizes="(min-width: 1360px) 1320px, 96vw"
/>
<CaSkeleton
v-else-if="!listInfo"
class="ca-list-page__image"
:ratio="271 / 1280"
:radius="false"
/>
</CaContainer>
<CaWidgetArea
class="ca-list-page__widget-area"
family="Productlist"
area-name="The top part of the product list"
:filters="widgetAreaFilters"
:list-page-url="currentPath"
/>
<CaContainer>
<CaListFilters
v-if="showControls && selection"
:filters="filters"
:selection="selection"
/>
<CaActiveFilters
v-if="showControls && selection && $store.getters.viewportComputer"
:selection="selection"
:selection-active="filterSelectionActive"
@selectionchange="filterChangeHandler"
@reset="resetFilters"
/>
<LazyCaFilterPanel
:filters="filters"
:selection="selection"
:selection-active="filterSelectionActive"
:total-products="totalCount"
:total-filters-active="totalFiltersActive"
@selectionchange="filterChangeHandler"
@reset="resetFilters"
/>
<CaListSettings
v-if="showControls"
:active-products="totalCount"
:active-filters="totalFiltersActive"
:current-sort="selection.sort"
@sortchange="sortChangeHandler"
/>
<CaListPagination
v-show="currentMinCount > 1"
direction="prev"
:showing="showing"
:total-count="totalCount"
:min-count="currentMinCount"
:max-count="currentMaxCount"
:all-products-loaded="allProductsLoaded"
:loading="$apollo.queries.products.loading"
@loadprev="loadPrev"
@reset="resetCurrentPage"
/>
<CaProductList
:skip="currentMinCount - 1"
:page-size="pageSize"
:products="productList"
:products-fetched="productsFetched"
/>
<CaListPagination
v-if="showControls"
direction="next"
:showing="showing"
:total-count="totalCount"
:min-count="currentMinCount"
:max-count="currentMaxCount"
:all-products-loaded="allProductsLoaded"
:loading="$apollo.queries.products.loading"
@loadmore="loadMore"
@reset="resetCurrentPage"
/>
</CaContainer>
<CaWidgetArea
class="ca-list-page__widget-area"
family="Productlist"
area-name="The bottom part of the product list"
:filters="widgetAreaFilters"
:list-page-url="currentPath"
/>
</div>
</template>
<script>
/*
CaListPage is the main component for the product list pages.
*/
import MixListPage from 'MixListPage';
export default {
name: 'CaListPage',
mixins: [MixListPage],
props: {},
data: () => ({}),
computed: {},
watch: {},
created() {},
methods: {}
};
</script>
<style lang="scss">
@import 'organisms/ca-list-page';
</style>
Use GTM module instead of @nuxtjs/gtm
If you are using GTM, you will need to implement the @geins/ralph-module-gtm module instead of the @nuxtjs/gtm module to keep using GTM.
Install the module
npm install @geins/ralph-module-gtm
Add the module to nuxt.config.js
...
// Remove this
gtm: {
id: process.env.GTM_ID,
debug: ralphEnv !== 'prod',
respectDoNotTrack: false,
pageViewEventName: 'Page Impression',
pageTracking: false
},
modules: [
...
// Remove this row
'@nuxtjs/gtm',
// Add the new module
[
'@geins/ralph-module-gtm',
{
gtm: {
id: process.env.GTM_ID,
debug: process.env.RALPH_ENV === 'dev',
respectDoNotTrack: false
}
}
],
...
],
...
Steps (non-required but recommended updates)
Use MixBrandPage on brands page
Remove all functions and data on brands page and use the MixBrandPage mixin instead.
Example:
...
<script>
import MixBrandPage from 'MixBrandPage';
export default {
name: 'BrandsPage',
mixins: [MixBrandsPage],
data: () => ({}),
computed: {},
watch: {},
methods: {},
meta: {
pageType: 'Brands Page'
}
}
</script>
Unchanged example from launchpad with version ralph-ui 20.0.0:
<template>
<div class="ca-brands-page">
<CaContainer>
<h1 class="ca-brands-page__title">{{ $t('BRANDS_TITLE') }}</h1>
<div v-if="brands && brands.length > 0" class="ca-brands-page__content">
<ul class="ca-brands-page__filter">
<li
v-for="(brandGroup, index) in brandsTree"
:key="index"
:class="{
'ca-brands-page__filter-item--active':
brandGroup.group === activeGroupFilter,
'ca-brands-page__filter-item--passive':
brandGroup.group != activeGroupFilter && isGroupFilter
}"
class="ca-brands-page__filter-item"
>
<CaClickable @clicked="setGroupFilter(brandGroup.group)">
{{ brandGroup.group }}
</CaClickable>
</li>
</ul>
<button
v-if="activeGroupFilter"
class="ca-brands-page__reset"
@click="setGroupFilter(activeGroupFilter)"
>
{{ $t('RESET_FILTERS') }}
</button>
<ul class="ca-brands-page__group">
<li
v-for="(brandGroup, index) in brandsTree"
:key="index"
:aria-hidden="[
brandGroup.group != activeGroupFilter && isGroupFilter
]"
class="ca-brands-page__group-item"
>
<span class="ca-brands-page__group-label">{{
brandGroup.group
}}</span>
<ul class="ca-brands-page__list">
<li
v-for="(brand, i) in brandGroup.brands"
:key="i"
class="ca-brands-page__list-item"
>
<NuxtLink class="ca-brands-page__link" :to="brand.canonicalUrl">
{{ brand.name }}
</NuxtLink>
</li>
</ul>
</li>
</ul>
</div>
<p v-else class="ca-brands-page__empty">
{{ $t('BRANDS_EMPTY') }}
</p>
</CaContainer>
</div>
</template>
<script>
import MixBrandsPage from 'MixBrandsPage';
/*
Renders the brands page.
*/
export default {
name: 'BrandsPage',
mixins: [MixBrandsPage],
data: () => ({}),
computed: {},
watch: {},
methods: {},
meta: {
pageType: 'Brands Page'
}
};
</script>
<style lang="scss">
@import './styles/pages/brands-page';
</style>
Fetch products only client side on start page
To improve loading speeds on start page, you can add a prop to the widget area to only fetch products client side.
The prop is called fetch-products-only-client-side and should be set to true.
<template>
...
<CaWidgetArea
family="Frontpage"
area-name="The front page area"
:fetch-products-only-client-side="true"
@dataFetched="$store.dispatch('loading/end')"
/>
...
</template>
Add .js to all store imports
For example, change
import { state, getters, mutations, actions } from 'checkout';
to
import { state, getters, mutations, actions } from 'checkout.js';
Example:
import { state, getters, mutations, actions } from 'checkout.js';
export default {
state,
getters,
mutations,
actions
};
Tips & tricks
This version comes with a new way of overriding standard CMS widget components. You can in your publicRuntimeConfig set which component should render upon each requested widget type. For example, if you want to create your own "info block" widget based on the JSON widget, you could add widgetRenderTypesComponents to your publicRuntimeConfig like so:
return {
...
publicRuntimeConfig: {
...
widgetRenderTypesComponents: {
'JSON': 'CaInfoBlockWidget'
}
...
}
...
}
And then with npm run ralph-create in your storefront, you can create the CaInfoBlockWidget component and use it to render the widget. This widget must have the prop "configuration" which is the widget configuration object.
...
<script>
export default {
name: 'CaInfoBlockWidget',
props: {
// Widget configuration object
configuration: {
type: Object,
required: true
}
}
...
};
...
</script>
Instead of building your own, use the Geins CMS module as a base for creating an array of custom widgets. Read more in the documentation of the module.