Logo BrocksiNet

BrocksiNet

Try to clean the web

Create a vue.js composable and call any API within Shopware Frontends

You already heard about the new Shopware Frontends? It uses the headless approach known from 😇 the administration area of Shopware. In general Shopware provides an admin API, the sync API and the store API. So basically the store API is for every action that a website visitor can do in your store. But to be honest Shopware Frontends is not a out-of-the-box solution. You need developers that can put every headless thing together for you. In this blog post I want to show how easy you can add your own Vue.js composable that will get additional data from another source into your Shopware Frontend. Let’s look into some code 🚀

What is our goal?

We will use the API from https://open-meteo.com to get some weather data into our Shopware Frontends. For this we create a vue.js composable that is importing a plain typescript file that is using axios to call the API and then our vue.js component will show the output of the template in our Shopware Frontends. To directly see it we put it next to the search input field. Maybe you ask yourself why we split it up into two files? The reason is simple, it's just to be more flexible in the future because when we split it up then we can later use the same API call also in another component.

How to setup Shopware Frontends?

First make sure you have at least Node.js v16 or v18 installed locally (node -v).

You can use the below setup command for just setup the demo-store or you checkout the project repository and follow the instructions there.

npx tiged shopware/frontends/templates/vue-demo-store demo-store && cd demo-store
npm i && npm run dev

After that you should be able to access Shopware Frontends via http://localhost:3000/ (if you do not use this port for something else) in your browser.

Create the useWeather.ts composable

We will only use this inside the vue-demo-store package so the place to create this composable is this path: templates/vue-demo-store/composables. Let’s create a file called useWeather.ts. This is a typescript file so it would be good if you at least know a bit (like me) typescript. This file we will only use to call the weather API. So the file imports axios, defines the type for the response and provides an async function that we can later use in any vue.js composable.

Have a look at the useWeather.ts file

import axios from "axios";

const weatherAPIBaseUrl =
  "https://api.open-meteo.com/v1/forecast";
const weatherAPIAdditionalParameters =
  "&hourly=temperature_2m,weathercode,windspeed_10m";

export type WeatherResponse = {
  latitude: number;
  longitude: number;
  generationtime_ms: number;
  utc_offset_seconds: number;
  timezone: string;
  timezone_abbreviation: string;
  elevation: number;
  hourly_units: {
    time: string;
    temperature_2m: string;
    weathercode: string;
    windspeed_10m: string;
  };
  hourly: {
    time: [string];
    temperature_2m: [number];
    weathercode: [number];
    windspeed_10m: [number];
  };
};

export async function useWeather(
  latitude: number,
  longitude: number
): Promise<WeatherResponse> {
  const resp = await axios.get(
    weatherAPIBaseUrl +
      "?latitude=" +
      latitude +
      "&longitude=" +
      longitude +
      weatherAPIAdditionalParameters
  );

  return resp.data;
}

After reading the file you have at least a imagination about how the response structure will look like. Thanks to typescript the IDE will give you some auto-completion when working with the response. Sure we can improve the file but at the moment for the demo it is okay that we have the APIBaseUrl hardcoded and also the parameters.

Create the Weather.vue component

The Weather.vue file we will create at this location: templates/vue-demo-store/components/weather/Weather.vue. At the top of this file we have our imports, the default ref values, our cities type definition and our static const holding our cities data (this can also be replace with a dynamic source in the future).

In the middle part of the file we have functions we use to format the date, calling the weather API (see the await useWeather part) and the onMounted path for the initial API call.

At the end of the file you will find the template and will see that we output a HTML select where you can select between the cities you want to see the weather forecast for the current hour. Also we output the data we get back from the API call.

<script setup lang="ts">
import { useWeather, WeatherResponse } from "@/composables/useWeather";
import { ref, onMounted } from "vue";

// Karlsruhe coordinates, it is so beauty that it is the default city ;)
const longitude = ref(49.01);
const latitude = ref(8.4);
const citySelect = ref();

let currentHour = ref(0);
let formatedDate = ref("");
let weatherData = ref({} as Ref<WeatherResponse>);

type cities = {
  [key: string | number]: {
    name: string;
    longitude: number;
    latitude: number;
  };
};

const cities = <cities>{
  karlsruhe: {
    name: "Karlsruhe",
    longitude: 49.01,
    latitude: 8.4,
  },
  berlin: {
    name: "Berlin",
    longitude: 52.52,
    latitude: 13.41,
  },
  "new-york": {
    name: "New York",
    longitude: 40.71,
    latitude: -74.01,
  },
};

function updateWeather($event: Event) {
  const selectedcity = ($event.target as HTMLSelectElement).value;
  executeUpdate(selectedcity);
}

async function executeUpdate(city: string) {
  updateCurrentHour();
  updateLongAndLat(city);

  weatherData.value = await useWeather(unref(longitude), unref(latitude));

  updateFormatedDate(weatherData.value.hourly.time[unref(currentHour)]);
}

function updateFormatedDate(date: string | number | Date) {
  const currentDate = new Date(date);
  const dateFormat = currentDate.toLocaleTimeString("de-DE", {
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
    hour: "2-digit",
    minute: "2-digit",
  });

  formatedDate = ref(dateFormat);
}

function updateLongAndLat(cityName: string) {
  for (const city in cities as cities) {
    if (city === cityName) {
      const city = cities[cityName];
      longitude.value = city.longitude;
      latitude.value = city.latitude;
    }
  }
}

function updateCurrentHour() {
  const currentDate = new Date();
  currentHour.value = currentDate.getHours();
}

onMounted(() => {
  const citySelectValue = (citySelect.value as HTMLSelectElement).value;
  executeUpdate(citySelectValue);
});
</script>

<template>
  <div class="font-bold w-36">Weather forecast</div>
  <select ref="citySelect" @change="updateWeather($event)" class="w-28">
    <option v-for="(city, key) in cities" :value="key">
      {{ city.name }}
    </option>
  </select>
  <template
    v-if="
      weatherData &&
      formatedDate &&
      weatherData.hourly &&
      weatherData.hourly_units
    "
  >
    <div>{{ formatedDate }}</div>
    <div>
      {{ weatherData.hourly.temperature_2m[currentHour] }}
      {{ weatherData.hourly_units.temperature_2m }}
    </div>
  </template>
</template>

Integrate the Weather.vue component into our Shopware Frontends

For demo proposes we just want to show the new Weather.vue component next to the Search Input Field. Open this file: templates/vue-demo-store/components/layout/LayoutHeader.vue and then Search for LayoutStoreSearch after the DIV that is containing the LayoutStoreSearch just put a new DIV with containing our new Weather.vue component.

<div class="weather">
    <Weather/>
</div>

To test if everything is working just start the dev server and have a look at the frontend. Also open the developer tools to see the API calls in the network tab when you change the city for the weather forecase.

# run the dev server for vue-demo-store
pnpm run dev --filter=vue-demo-store

Look at the result

Sure the styling can be improved but this is just an example exercise how you can use any data that is provided via an API inside Shopware Frontends. Also keep in mind that you can adjust also the default API calls to the store-api to your needs and also combine them with other data or custom data structure. You are totally free to push this to the limits. But also be careful that too many requests and too big json files can make your Frontend slow because the browser also needs time to parse it.

You can also play around with the full working code example on StackBlitz.

Thx for reading. 🥳 💙

Released - 16.03.2023

Comments


About the author

Bild Bjoern
Björn MeyerSoftware Engineer
Björn is interested in the details of web technologies. He started with Joomla, Drupal, Typo3, vBulletin, Fireworks and Photoshop. Today his focus is on PHP for example with Magento 2, Shopware 6 and Symfony. He also likes JavaScript with Angular, React, VueJs and TypeScript. In his free time, he seeks constant balance with swimming, running and yoga.