HTML5-Tutorium: JavaScript: Hello World Vue 04

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
Version vom 18. April 2023, 10:45 Uhr von Kowa (Diskussion | Beiträge) (Die Seite wurde neu angelegt: „{{HTML5-Tutorium:JavaScript:HelloWorld:Menü}} {{InBearbeitung}} '''Musterlösung'''<br/> [{{Git-Server}}/kowa/wk_hello_world_vue Git-Repository], <code>git…“)
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)

Dieser Artikel erfüllt die GlossarWiki-Qualitätsanforderungen nur teilweise:

Korrektheit: 3
(zu größeren Teilen überprüft)
Umfang: 4
(unwichtige Fakten fehlen)
Quellenangaben: 3
(wichtige Quellen vorhanden)
Quellenarten: 5
(ausgezeichnet)
Konformität: 3
(gut)

Vorlesung WebProg

Inhalt | Teil 1 | Teil 2 | Teil 3 | Teil 4 | Teil 5 | Teil 6 | Vue 1 | Vue 2 | Vue 3 | Vue 4 | Vue 5 | Vue 6

Vorlage:InBearbeitung

Musterlösung
Git-Repository, git checkout v04 (JavaScript)
Git-Repository (TypeScript), git checkout v04

Anwendungsfälle (Use Cases)

Gegenüber dem ersten, zweiten und dritten Teil des Vue-Tutoriums ändern sich die die Anwendungsfälle nicht. Die Anwendung leistet also genau dasselbe wie zuvor.

Aufgabe

Aufgabe: Modularisieren Sie HelloWorld.vue Definieren Sie für die Komponente HelloWorld.vue zwei Subkomponenten:

  • components/section/SectionHello.vue
  • components/section/SectionQuestion.vue

Zuvor sollten Sie zwei wiederverwendbare Hilfskomponenten definieren:

  • components/form/Button.vue
  • components/form/Textfield.vue

Erstellen eines neuen Projektzweigs

Erstellen Sie einen neuen Projektzweig (branch) innerhalb von hello_world_vue und fügen Sie das Package uuid hinzu:

git checkout v03     # Wechsle in den Branch v03
git checkout -b v04  # Klone v03 in einen neuen Branch v04
npm i uuid
npm i

Erstellen der Stores

StoreSection

Im Section-Store wird gespeichert, welche Section der Single-Page-Anwendung dem Benutzer aktuell präsentiert wird.

// src/store/StoreSection.js

import { defineStore } from 'pinia'
import { ref }         from 'vue'

const storeSection =
defineStore
( 'section', // must be unique

  () =>
  { const
      section = ref(''),
   
      init =
        p_section =>
        section.value = p_section,

      visability =
        p_section =>
        section.value !== p_section ? 'hidden' : '',

      change =
        p_section =>
        section.value = p_section

    return { init, visability, change }
  }
)

export default storeSection

StoreGreeting

Im Greeting-Store werden die Daten zur Begrüßung des Benutzers gespeichert.

// src/store/StoreGreeting.js

import { defineStore }               from 'pinia'
import { reactive, toRef, computed } from 'vue'

const
  storeGreeting =
  defineStore
  ( 'greeting',

    () =>
    { const
      state =
        reactive({ name: '', stranger: 'Stranger', hello: 'Hello, $1!' }),

      init =
        (p_state = {}) =>
        { Object.assign(state, p_state) },

      name = toRef(state, 'name'),

      sayHello =
        p_name string): string =>
        state.hello.replace('$1', p_name),

      helloStranger =
        computed(() => sayHello(state.stranger)),

      hello =
        computed(() => sayHello(state.name))

      return { init, name, helloStranger, hello }
    }
)

export default storeGreeting

Erstellen der Stores in einer Komponente

// src/components/HelloWorld.vue

<script setup>
  import { onMounted, onUnmounted } from 'vue'
  import ControllerKey from '@/controller/ControllerKey'
  import storeSection  from '@/store/StoreSection'
  import storeGreeting from '@/store/StoreGreeting'

  const
    section  = storeSection(),
    greeting = storeGreeting(),

    sayHello = () => { section.change('hello') },
    controllerKey =
       new ControllerKey('Enter', sayHello, 'input_name')

  section.init('question')
//greeting.init({ hello: 'Hallo, $1!', stranger: 'Fremder'})

  onMounted  (() => controllerKey.add())
  onUnmounted(() => controllerKey.remove())
</script>

Zum Vergleich noch einmal das Skript ohne Store

// src/components/HelloWorld.vue without store

<script setup >
  import { ref, computed, onMounted, onUnmounted } from 'vue'
  import ControllerKey from '@/controller/ControllerKey'

  const
    section       = ref('question'),
    helloStranger = ref('Hello, Stranger'),
    name          = ref(''),

    hello      = computed(() => `Hello, ${name.value}!`),

    visability = (p_section: string) =>
                   p_section !== section.value
                   ? 'hidden'
                   : '',

    sayHello   = () => { section.value = 'hello' },
    controllerKey =
      new ControllerKey('Enter', sayHello, 'input_name')

  onMounted  (() => controllerKey.add())
  onUnmounted(() => controllerKey.remove())
</script>

Anpassung des Templates

Das Template muss angepasst werden. Man muss nun auf die Store-Objekte zugreifen.

// src/components/HelloWorld.vue

<template>
  <section id="section_question" v-bind:class="section.visability('question')">
    <h1>{{greeting.helloStranger}}</h1>
    <form>
      <div>
        <label for="input_name">What's your name?</label>
        <input id="input_name" type="text" autofocus
               v-model="greeting.name"
        />
      </div>
      <div>
        <input id="button_reset"  type="reset"  value="Reset"/>
        <input id="button_submit" type="button" value="Say hello"
               v-on:click="sayHello"
        />
      </div>
    </form>
  </section>
  <section id="section_hello" v-bind:class="section.visability('hello')">
    <h1>{{greeting.hello}}</h1>
    <p>Welcome to Web Programming!</p>
  </section>
</template>

Stores als Klassen

Stores können auch mit Hilfe von Klassen definiert werden. Dazu benötigt man zunächst eine Basisklasse, die von jedem Store wiederverwendet werden kann.

Basisklasse Store

// src/common/Store/Store.js

// cmp. https://medium.com/@mario.brendel1990/vue-3-the-new-store-a7569d4a546f
import { reactive } from 'vue'

class Store
{ #initState
  state

  reset()
  { if (this.state == null)
    { this.state = reactive({...this.initState}) }
    else
    { Object.assign(this.state, this.initState) }
  }

  init(p_state)
  { Object.assign(this.initState, p_state);
    this.reset();
  }

  constructor(p_state)
  { this.initState = p_state;
    this.reset();
  }
}

export default Store

Definition der Store-Klasse StoreSection

Nun kann der Section-Store als Klasse realisiert werden.

// src/store/StoreSection.js

import Store from '@/common/store/Store'

class StoreSection extends Store
{ constructor()
  { super({ name: '' }) }

  visability(p_section)
  { return this.state.name !== p_section ? 'hidden' : '' }

  change(p_section)
  { this.state.name = p_section }
}

const storeSection = new StoreSection();

export default storeSection;

Definition der Store-Klasse StoreGreeting

Den Greeting-Store kann man analog definieren.

// src/store/StoreGreeting.js

import Store        from '@/common/store/Store'
import { computed } from 'vue'

class StoreGreeting extends Store
{ constructor(p_init = { hello: 'Hello, $1!', stranger: 'Stranger' })
  { super({ name: '', ...p_init }); }

  get name()
  { return this.state.name; }

  set name(p_name)
  { this.state.name = p_name; }

  #sayHello(p_name)
  { return this.state.hello.replace('$1', p_name); }

  get helloStranger()
  { return computed(() => this.#sayHello(this.state.stranger)).value }

  get hello()
  { return computed(() => this.#sayHello(this.state.name !== ''
                                         ? this.state.name
                                         : this.state.stranger
                                        )
                   ).value
  }
}

const stateGreeting = new StoreGreeting()

export default stateGreeting

Störend ist hier, dass man für jedes Attribut wie z. B. name eine Getter- und eine Setter-Methode definieren muss. Dieses Problem kann man mit einer weiteren Superklasse abmildern.

StoreExtendible

Man definiert die Klasse StoreExtendible, die es ermöglicht, bei der Initialisierung des Stores dynamisch reaktive Getter- und Setter-Methoden hinzuzufügen.

// src/common/Store/StoreExtendible.js

import Store from './Store'

class StoreExtendible extends Store
{ #add(p_properties, p_getter, p_setter)
  { if (typeof p_properties === 'string')
    { Object.defineProperty
      ( this.constructor.prototype,
        p_properties,
        { ...( p_getter ? { get: () => this.state[p_properties] } : {} ),
          ...( p_setter ? { set: (value) =>
                                { if (this.state[p_properties] !== value)
                                  { this.state[p_properties] = value; }
                                },
                         }
                       : {}
             ),
          configurable: false, // the property cannot be redefined
          enumerable:   true
        },
      );
    }
    else
    { p_properties.forEach(e => this.addGetter(e)) }
  }

  addGetter(p_properties)
  { this.#add(p_properties, true, false) }

  addSetter(p_properties)
  { this.#add(p_properties, false, true) }

  addGetterSetter(p_properties)
  { this.#add(p_properties, true, true) }
}

export default StoreExtendible

Verwendung der alternativen Stores in einer Komponente

// src/components/HelloWorld.vue with store class

<script setup >
  import { onMounted, onUnmounted } from 'vue'
  import ControllerKey from '@/controller/ControllerKey'
  import section  from '@/store/StoreSectionClass'
//import greeting from '@/store/StoreGreetingClass'
  import greeting from '@/store/StoreGreetingClassExtendible'

  const
    sayHello = () => { section.change('hello') },
    controllerKey =
       new ControllerKey('Enter', sayHello, 'input_name')

  section.init({ name: 'question'})
//greeting.init({ hello: 'Hallo, $1!', stranger: 'Fremder'})

  onMounted  (() => controllerKey.add())
  onUnmounted(() => controllerKey.remove())
</script>

Zum Vergleich: Die Verwendung der zuerst definierten Stores.

// src/components/HelloWorld.vue

<script setup>
  import { onMounted, onUnmounted } from 'vue'
  import ControllerKey from '@/controller/ControllerKey'
  import storeSection  from '@/store/StoreSection'
  import storeGreeting from '@/store/StoreGreeting'


  const
    section  = storeSection(),
    greeting = storeGreeting(),

    sayHello = () => { section.change('hello') },
    controllerKey =
       new ControllerKey('Enter', sayHello, 'input_name')

  section.init('question')
//greeting.init({ hello: 'Hallo, $1!', stranger: 'Fremder'})

  onMounted  (() => controllerKey.add())
  onUnmounted(() => controllerKey.remove())
</script>

Fortsetzung des Tutoriums

Sie sollten nun Teil 4 des Vue-Tutoriums bearbeiten. Nun wird die Anwendung modularisiert.

Quellen

  1. Kowarschick (MMProg): Wolfgang Kowarschick; Vorlesung „Multimedia-Programmierung“; Hochschule: Hochschule Augsburg; Adresse: Augsburg; Web-Link; 2018; Quellengüte: 3 (Vorlesung)