# 3. Podział komponentów

W naszych projektach przydzielamy komponenty do jednej z dwóch warstw frontendowych. W zależności od ich zastosowania, złożoności możemy przydzielić komponent do repozytorium odpowiadajacego za wartwę UI lub do repozytorium posiadającym warstwę frontową. Poniżej dowiesz się według jakich kryteriów dzielimy komponenty oraz znajdziesz kilka ich przykładów.

# 3.1 Komponenty UI

Komponenty UI są też często określane “głupimi” (ang. dumb components). Ich główną i w zasadzie jedyną odpowiedzialnością jest, jak sama nazwa wskazuje, prezentacja danych użytkownikowi. Najbardziej typowym i najczęsciej prezentowanych komponentem UI jest button.

<template functional>
  <component
    :is="props.type"
    class="button-icon"
    :class="[data.class, data.staticClass, `button-icon--${props.shape}`]"
    v-bind="data.attrs"
    v-on="listeners"
  >
    <icon
      v-if="props.name"
      class="button-icon__svg"
      :name="props.name"
      :width="props.width"
      :height="props.height"
    />
    <!-- @slot if you need to insert somethig instead of icon -->
    <slot v-if="!props.name" />
  </component>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Komponenty UI mogą wystąpić w aplikacji w różnych miejscach, dlatego ważne jest żeby była możliwość ich modyfikacji za pomocą propsów zamiast tworzenia kilku podobnych komponentów UI różniących się tylko wyglądem. Powyżej zaprezentowany button będzie wyglądał inaczej w zależność od przyjętych wartości.

Każdy komponent UI powinnien mieć przygotowane własne stories, czyli pliki przekazywany to storybooka. W stories przekazujamy przykładowe propsy oraz umieszczamy dokumentacje komponentu.

Stories dla Button.vue:

import { storiesOf } from '@storybook/vue'

import ButtonIcon from './ButtonIcon.vue'

storiesOf('Ui | Button', module)
  .add(
    'ButtonIcon - circle',
    () => ({
      components: { ButtonIcon },

      template: `
        <button-icon name="arrow-up" height="auto" width="45%" />
      `
    }),
    {
      info: {}
    }
  )
  .add(
    'ButtonIcon - poly',
    () => ({
      components: { ButtonIcon },

      template: `
        <button-icon name="arrow-up" height="auto" width="45%" shape="poly" type="SPAN" />
      `
    }),
    {
      info: {}
    }
  )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

Na koniec eksportujemy ten komponent w pliku index.js w następujący sposób:

export { default as SearchField } from './src/components/Ui/SearchField/SearchField'
1

Jednak nie wszystkie komponety UI muszą być tak proste jak button. Poniżej mamy nieco bardziej złożony komponent, jednak nadal mogący być komponentem UI.

<template>
  <div class="search-field">
    <div class="search-field__container">
      <form class="search-field__form" @submit.prevent="searchHandler">
        <input
          v-model="search"
          class="search-field__input"
          type="text"
          :placeholder="labels.searchSite"
          @input="searchHandler"
        />
        <button class="search-field__button" type="submit">
          <icon
            class="search-field__icon flex-shrink-0"
            width="24"
            height="24"
            name="search"
          />
        </button>
      </form>
    </div>
  </div>
</template>

<script>
export default {
  name: 'SearchField',
  props: {
    labels: {
      type: Object,
      required: true,
      default: () => {}
    }
  },
  data() {
    return {
      search: ''
    }
  },
  methods: {
    searchHandler() {
      this.search.length > 1 ? this.$emit('searchingPhrase', this.search) : ''
    }
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

Jest to komponent posiadający forma w którym mamy input typu text oraz button od submita całego forma. Jak jest wyżej wspomniane, w komponentach UI ważne jest unikanie logiki, dzięki czemu komponent będzie bardziej uniwerslany i będzie można go użyć w wielu miejsach.

Komponent komunikuje się z rodzicem za pomocą customowego eventu searchingPhrase, przekuzując stringa odpowiadającego wpisanej frazie. Tak samo wszelkie tłumaczenia i informacje do komponentu przekazywane są od rodzica w formie propsów.

# 3.2 Content elementy

Content elementy są to elementy interfejsu dostępne z panelu typo3, dodawane przez użytkownika. Domyślny wygląd i działanie jest już przygotowane w nuxt-typo3. Ten wygląd i funkcjonalności możemy modyfikować i rozwijać poprzez utowrzenie nowego pliku vue dla tego komponentu w repozytorium nuxtowym i użyciu właściwości extends. Poniżej znajduje sie extendowany CeText.vue:

<template>
  <div class="ce-text rte" :class="`ce-text--${appearance.layout}`">
    <!-- eslint-disable-next-line vue/no-v-html -->
    <p v-if="header" class="ce-text__header" v-html="header" />
    <p v-if="subheader" class="ce-text__subheader">
      {{ subheader }}
    </p>
    <html-parser :content="bodytext" class="ce-text__paragraph" />
  </div>
</template>
<script>
import CeText from '~typo3/components/content/elements/CeText'
export default {
  name: 'CeText',
  funcional: false,
  extends: CeText,
  props: {
    bodytext: {
      type: String,
      default: ''
    },
    appearance: {
      type: Object,
      required: false,
      default: () => {}
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

Taki extendowany component należy również zaimportować w pliku components.js w folderze plugins.

  import CeText from '~/components/Content/Elements/CeText'
1

# 3.3 Komponenty posiadające logikę niebędące content elementami

Ostatnim typem komponentów są komponenty posiadające pewną wartswę logiki niebędące content elementami. Taki komponent nie może być traktowany jako UI komponent. Poniżej mamy przykład takiego typu elementu. Jest to komponent posiadający logikę odpowiadającą za sprawdzanie czy wpisana fraza w SearchField.vue ma wyniki oraz przekazanie wyników do komponentu Results.vue.

<template>
  <div>
    <SearchField :labels="labels" @searchingPhrase="fetchSearchResults" />
    <Results :labels="labels" :results="results" :touched="touched" :search="search" @closeMenu="closeMenu" />
  </div>
</template>

<script>
import { debounce } from 'lodash-es'
import { Results, SearchField } from 'biofarm-ui'

export default {
  name: 'Search',
  components: {
    Results,
    SearchField
  },
  props: {
    currentLang: {
      type: String,
      required: true,
      default: ''
    }
  },
  data () {
    return {
      results: {},
      touched: false,
      search: ''
    }
  },
  computed: {
    labels () {
      return {
        noResults: this.$t('no_results'),
        searchSite: this.$t('search_site'),
        search: this.$t('search'),
        seeAll: this.$t('see_all'),
        suggestions: this.$t('suggestions')
      }
    }
  },
  methods: {
    closeMenu () {
      this.$parent.toggleModalSearch()
    },
    fetchSearchResults: debounce(async function (search) {
      await this.$axios
        .$get(
          this.currentLang === 'en'
            ? `en/search?q=${search}`
            : `/szukaj?q=${search}`
        )
        .then((response) => {
          this.results =
            response.content.colPos0[0].content.data.documents.list
          this.touched = true
          this.search = search
        })
        .catch((error) => {
          console.error(error)
        })
    }, 300)
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

Takie komponenty trzymamy w repo nuxstowym w folderze components. W zależności do struktury i liczby takich komponentów możemy utworzyć dodatkowe foldery.