Skip to main content

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

Note!

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.

/pages/list/all.vue
<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>
/pages/list/_search.vue
<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

/pages/list/_category.vue
// 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>
/pages/list/_list.vue
<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

Remember!

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:

/components/organisms/CaProductCard/CaProductCard.vue
<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 :

/components/organisms/CaProductCard/CaProductCard.vue
<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:

/components/organisms/CaListPage/CaListPage.vue
<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:

/components/organisms/CaListPage/CaListPage.vue
<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

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
}
}
],
...
],
...

Use MixBrandPage on brands page

Remove all functions and data on brands page and use the MixBrandPage mixin instead.

Example:

/pages/brands/index.vue
...
<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:

/pages/brands/index.vue
<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.

/pages/start/index.vue
<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:

/store/checkout.js
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:

nuxt.config.js
  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.

/components/molecules/CaInfoBlockWidget/CaInfoBlockWidget.vue
...
<script>
export default {
name: 'CaInfoBlockWidget',
props: {
// Widget configuration object
configuration: {
type: Object,
required: true
}
}
...
};
...
</script>
Tip

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.