HTML5-Tutorium: JavaScript: Hello World Vue 01: Unterschied zwischen den Versionen

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
(43 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 2: Zeile 2:


'''Musterlösung'''<br/>
'''Musterlösung'''<br/>
[{{Git-Server}}/kowa/wk_hello_world_vue Git-Repository], <code>git checkout v01</code> (JavaScript)<br>
[{{Git-Server}}/kowa/wk_hello_world_vue Git-Repository], <code>git checkout v01</code> (JavaScript)<!--<br>
[{{Git-Server}}/kowa/wk_hello_world_vue_ts Git-Repository (TypeScript)], <code>git checkout v01</code>
[{{Git-Server}}/kowa/wk_hello_world_vue_ts Git-Repository (TypeScript, )], <code>git checkout v01</code>-->


== Anwendungsfälle (Use Cases) ==
== Anwendungsfälle (Use Cases) ==
Zeile 34: Zeile 34:
{ "files.eol": "\n" }
{ "files.eol": "\n" }
EOF
EOF
# Tragen Sie
#    !.vscode/settings.json
# in die Datei .gitignore ein.
# VLC (neu) starten, damit settings.json gelesen wird
# VLC (neu) starten, damit settings.json gelesen wird
# und Terminal für das Projekt öffnen
# und dann ein Terminal für das Projekt öffnen


# initialize git
# initialize git
Zeile 58: Zeile 61:
Webpack gilt allerdings schon wieder als Dinosaurier (existiert seit 2012)
Webpack gilt allerdings schon wieder als Dinosaurier (existiert seit 2012)


Seit Version 3.2. verwendet Vue  Vite (französisch „schnell“).
Seit Version 3.2 verwendet Vue  Vite (französisch „schnell“).
Dies hat zur Konsequenz, dass das Initialisieren und Testen einer
Dies hat zur Konsequenz, dass das Initialisieren und Testen einer
Vue-Anwendung anders erfolgt als früher.
Vue-Anwendung anders erfolgt als früher.


Vue 3 unterstützt im Gegensatz zu Vue 2 TypeScript.
Vue 3 unterstützt im Gegensatz zu Vue 2 TypeScript.
Daher verwenden wir auch TypeScript.
Allerdings wir im Tutorium Typescript nicht eingesetzt.


Vue-Anwendungen sind aus Komponenten aufgebaut.
Vue-Anwendungen sind aus Komponenten aufgebaut.
Zeile 79: Zeile 82:
vite.config.js        # mit vite.config.js von hello_world_06 mergen
vite.config.js        # mit vite.config.js von hello_world_06 mergen
                       # Ausnahmen: root, publicDir, und outDir
                       # Ausnahmen: root, publicDir, und outDir
index.html            # gemäß hello_world_06 definieren
index.html            # gemäß hello_world_06 definieren; Import von 'main.js' anpassen (src="src/main.js")
                      # Der Inhalt des Body-Elements wird ersetzt: <main id="app"></main>
src/css              # Ordner von hello_world_06 übernehmen
src/css              # Ordner von hello_world_06 übernehmen
public                # favicon ersetzen
public                # favicon ersetzen
Zeile 88: Zeile 92:


'''Tipp'''<br/>
'''Tipp'''<br/>
Wenn Sie Code von mir verwenden, sollten Sie
Wenn Sie Code von mir verwenden, sollten Sie in die Datei
  "no-unexpected-multiline": false
<code>eslintrc.cjs</code> nach Zeile 10 folgenden Code einfügen:
in das "env"-Objekt in der Datei exlintrc.cjs nach Zeile 10 einfügen:
<source lang="javascript">
<source lang="javascript">
"env":
  ignorePatterns:
{ "vue/setup-compiler-macros": true,
  [ 'dist', 'public'],
   "no-unexpected-multiline":   false,
  rules:  
}
   { 'no-unexpected-multiline': 0 }
</source>
</source>


Zeile 106: Zeile 109:
</source>
</source>


'''Neuer Pfad-Alias'''<br/>
Passen Sie auch noch die Datei <code>jsconfig.json</code> an, damit in Visual Studio Code die Pfadverfolgung mittels Point and Click ermöglicht wird.  
Um die SCSS-Konfigurationsdatei <code>_config.scss</code> von jedem Ordner aus mittels
<code>@import 'config';</code> importieren zu können, benötigt man einen weiteren Pfad-Alias-Eintrag in der Datei
<code>vite.config.js</code>. Ergänzen Sie auch gleich einen Pfad für den Import von anderen CSS-Dateien.
 
<source lang="javascript">
resolve:
{ alias:
  { '@':      fileURLToPath(new URL('./src',                  import.meta.url)),
    '/css':  fileURLToPath(new URL('./src/css',              import.meta.url)),
    'config': fileURLToPath(new URL('./src/css/_config.scss', import.meta.url)),
  }
},
</source>
 
Definieren Sie auch noch eine Datei <code>jsconfig.json</code>, mit der die Pfadverfolgung mittel Point an Click in Visual Studio Code ermöglicht wird.  
Leider funktioniert dies nicht für den Alias <code>config</code>.
Leider funktioniert dies nicht für den Alias <code>config</code>.


Zeile 128: Zeile 116:
   {  "baseUrl": ".",
   {  "baseUrl": ".",
     "paths":  
     "paths":  
       {  "@/*":       ["./src/*"],
       {  "@/*":   ["./src/*"],
         "/css/*":   ["./src/css/*.scss"]
         "/css/*": ["./src/css/*.scss"]
       }
       }
   },
   },
Zeile 135: Zeile 123:
   "extensions": [".js", ".vue", ".json"]
   "extensions": [".js", ".vue", ".json"]
}
}
</source>
=== <code>src/main.js</code> ===
Da wir in diesem Tutorium die JavaScript-Dateien asynchron laden, müssen
sie das Main-Skript entsprechenden anpassen. Die App darf erst erstellt
und gemounted werden, wenn alle Dateien der App vollständig geladen wurden.
<source lang="ecmascript">
import { createApp } from 'vue'
import App from './App.vue'
window.addEventListener
( 'load',
  () => createApp(App).mount('#app')
)
</source>
</source>


Zeile 146: Zeile 150:
<!DOCTYPE html>
<!DOCTYPE html>
<html lang="en">
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name    = "viewport"
          content = "width=device-width, initial-scale=1.0, user-scalable=yes"
    >
    <link rel="icon" href="/favicon.ico">


    <title>WK Hello World Vue 01</title>
<head>
  <meta charset = "UTF-8">
  <meta name    = "viewport"
        content = "width=device-width, initial-scale=1.0, user-scalable=yes"
  >
 
  <title>Hello World Vue (v01)</title>
 
  <style>
    @import url("/css/head.scss");
  </style>
  <script src="src/main.js" type="module" async></script>
</head>
 
<body>
  <main id="app"></main>
</body>


    <style>
      @import url("src/css/head.css");
    </style>
    <script src="src/main.js" async="async" type="module"></script>
  </head>
  <body>
    <main id="app"></main>
  </body>
</html>
</html>
</source>
</source>
Zeile 169: Zeile 176:
* Die Hello-World-Komponente und das Style-Element laden.
* Die Hello-World-Komponente und das Style-Element laden.
* Nur die Hello-World-Komponente ins Template einfügen.
* Nur die Hello-World-Komponente ins Template einfügen.
* <code>body.scss</code> importieren.
* <code>body.scss</code> importieren (ohne das Attribut <code>scoped</code>, damit der Inhalt der CSS-Datei allen Komponenten zur Verfügung steht).


<source lang="html">
<source lang="html">
Zeile 185: Zeile 192:
</source>
</source>


Der Ordner <code>src/assets</code> wird nun nicht mehr benötigt und kann gelöscht werden.
Der Ordner <code>src/assets</code> wird nun nicht mehr benötigt und kann gelöscht werden ebenso wie die anderen beiden Komponenten.


'''<code>src/components/HelloWorld.vue</code>'''
=== <code>src/components/HelloWorld.vue</code> ===
* Sections aus index.html in Template-Element einfügen.
* Sections aus index.html in Template-Element einfügen.
* Sectionspezifische SCSS-Anweisungen in <code>src/css/HelloWorld.scss</code> verschieben und diese Datei in <code>src/components/HelloWorld.vue</code> importieren (<code>import '/css/HelloWorld';</code> ).
* Sectionspezifische SCSS-Anweisungen in <code>src/css/HelloWorld.scss</code> verschieben und diese Datei in <code>src/components/HelloWorld.vue</code> importieren (<code>@import '@/css/components/HelloWorld';</code> ).
* Skript-Element befüllen.
* Skript-Element befüllen.


'''SCSS für die Hello-World-Komponente'''<br/>
<source lang="css">
<source lang="css">
/* src/css/HelloWorld.scss */
/* src/css/components/HelloWorld.scss */
 
/* Hier wird alles aus der Datei body.scss übernommen,
* bis auf html {...} und body {...}.
*/
</source>
 
'''Hello-World-Komponente'''<br/>
<source lang="javascript">
<!-- src/components/HelloWorld.vue -->
 
<script setup>
  import { ref, computed } from 'vue'
 
  const
    section      = ref('from'),
    helloStranger = ref('Hello, Stranger'),
    name          = ref(''),
 
    hello      = computed(() => `Hello, ${name.value}!`),
    visibility = p_section =>
                p_section !== section.value ? 'hidden' : '',
    sayHello  = () => section.value = 'hello'
</script>
 
...
 
<style scoped lang="scss">
  @import '@/css/components/HelloWorld';
</style>
 
</source>
 
Im Template von HelloWorld.vue sollten Sie alle Attribute <code>"id"</code> mit Ausnahme von <code>"section_from"</code>, <code>"section_hello"</code> und <code>"input_name"</code> löschen.
 
Der Grund ist, dass ID-Attribute im HTML-Dokument eindeutig sein müssen. Wenn in einer Komponente ein ID-Attribut definiert wird, kann man diese nicht mehrfach in eine andere Komponente einfügen.
 
Ein Ausnahme bildet das Attribut <code>"input_name"</code>, da dieser Identifikator von zugehörigen laben referenziert wird: <code><label for="input_name"></code>. Bei der Erstellung einer eigenständigen Komponente Textfield muss man daher darauf achten, dass bei jeder Verwendung dieser Komponente ein neuer eindeutiger Identifikator (z. B. mittels <code>uuid</code>) erzeugt wird.  (Siehe [[Version HTML5-Tutorium: JavaScript: Hello World Vue 04|Version v04]].)
 
Außerdem benötig man diese ID in der [[Version HTML5-Tutorium: JavaScript: Hello World Vue 02|Version v02]] für den Enter-Key-Controller.
 
Darüber hinaus benötigen wir die Section-IDs für CSS. Diese werden erst später gelöscht.
<source lang="html">
<template>
  <section id="section_form" v-bind:class="visibility('form')">
    <h1>{{helloStranger}}</h1>
    <form>
      <div>
        <label for="input_name">What's your name?</label>
        <input id="input_name" type="text" v-model="name" autofocus>
      </div>
      <div>
        <input type="reset"  value="Reset">
        <input type="button" value="Say hello" v-on:click="sayHello">
      </div>
    </form>
  </section>
  <section id="section_hello" v-bind:class="visibility('hello')">
    <h1>{{hello}}</h1>
    <p>Welcome to Web Programming!</p>
  </section>
</template>
 
...
</source>
 
=== <code>src/components/HelloWorld.vue</code> ===
* Sections aus index.html in Template-Element einfügen.
* Sectionspezifische SCSS-Anweisungen in <code>src/css/HelloWorld.scss</code> verschieben und diese Datei in <code>src/components/HelloWorld.vue</code> importieren (<code>@import '@/css/components/HelloWorld';</code> ).
* Skript-Element befüllen.
 
'''SCSS für die Hello-World-Komponente'''<br/>
<source lang="css">
/* src/css/components/HelloWorld.scss */


#section_form
#section_form
Zeile 201: Zeile 282:


   h1
   h1
   { margin-bottom: 0.5ex; }
   { margin-bottom: 0.5ex;  
    margin-top:    auto;
  }
}
}


Zeile 210: Zeile 293:
</source>
</source>


'''Hello-World-Komponente'''<br/>
<source lang="javascript">
<source lang="javascript">
<!-- src/components/HelloWorld.vue -->
<!-- src/components/HelloWorld.vue -->
Zeile 217: Zeile 301:


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


     hello      = computed(() => `Hello, ${name.value}!`),
     hello      = computed(() => `Hello, ${name.value}!`),
     visability = p_section =>
     visibility = p_section =>
                 p_section !== section.value ? 'hidden' : '',
                 p_section !== section.value ? 'hidden' : '',
     sayHello  = () => { section.value = 'hello' }
     sayHello  = () => section.value = 'hello'
</script>
</script>


...
...
<style scoped lang="scss">
  @import '@/css/components/HelloWorld';
</style>


</source>
</source>


'''Hello-World-Komponente'''<br/>
Im Template von HelloWorld.vue sollten Sie alle Attribute <code>"id"</code> mit Ausnahme von <code>"section_form"</code>, <code>"section_hello"</code> und <code>"input_name"</code> löschen.
Im Template von HelloWorld.vue sollten Sie alle Attribute <code>"id"</code> mit Ausnahme von <code>"input_name"</code> löschen.


Der Grund ist, dass ID-Attribute im HTML-Dokument eindeutig sein müssen. Wenn in einer Komponente ein ID-Attribut definiert wird, kann man diese nicht mehrfach in eine andere Komponente einfügen.
Der Grund ist, dass ID-Attribute im HTML-Dokument eindeutig sein müssen. Wenn in einer Komponente ein ID-Attribut definiert wird, kann man diese nicht mehrfach in eine andere Komponente einfügen.


Ein Ausnahme bildet das Attribut <code>"input_name"</code>, da dieser Identifikator von zugehörigen laben referenziert wird: <code><label for="input_name"></code>
Ein Ausnahme bildet das Attribut <code>"input_name"</code>, da dieser Identifikator von zugehörigen laben referenziert wird: <code><label for="input_name"></code>. Bei der Erstellung einer eigenständigen Komponente Textfield muss man daher darauf achten, dass bei jeder Verwendung dieser Komponente ein neuer eindeutiger Identifikator (z. B. mittels <code>uuid</code>) erzeugt wird.  (Siehe [[Version HTML5-Tutorium: JavaScript: Hello World Vue 04|Version v04]].)
 
Bei der Erstellung einer eigenständigen Komponente Textfield muss man daher darauf achten, dass bei jeder Verwendung dieser Komponente ein neuer eindeutiger Identifikator (z. B. mittels <code>uuid</code>) erzeugt wird.  (Siehe [[Version HTML5-Tutorium: JavaScript: Hello World Vue 04|Version v04]].)


Außerdem benötig man diese ID in der [[Version HTML5-Tutorium: JavaScript: Hello World Vue 02|Version v02]] für den Enter-Key-Controller.
Außerdem benötig man diese ID in der [[Version HTML5-Tutorium: JavaScript: Hello World Vue 02|Version v02]] für den Enter-Key-Controller.


Darüber hinaus benötigen wir die Section-IDs für CSS. Diese werden erst später gelöscht.
<source lang="html">
<source lang="html">
<template>
<template>
   <section v-bind:class="visability('question')">
   <section id="section_form" v-bind:class="visibility('form')">
     <h1>{{helloStranger}}</h1>
     <h1>{{helloStranger}}</h1>
     <form>
     <form>
       <div>
       <div>
         <label for="input_name">What's your name?</label>
         <label for="input_name">What's your name?</label>
         <input id="input_name" type="text" autofocus v-model="name" />
         <input id="input_name" type="text" v-model="name" />
       </div>
       </div>
       <div>
       <div>
Zeile 257: Zeile 343:
     </form>
     </form>
   </section>
   </section>
   <section v-bind:class="visability('hello')">
   <section id="section_hello" v-bind:class="visibility('hello')">
     <h1>{{hello}}</h1>
     <h1>{{hello}}</h1>
     <p>Welcome to Web Programming!</p>
     <p>Welcome to Web Programming!</p>
Zeile 264: Zeile 350:


...
...
</source>
'''Autofokus-Aktivierung per JavaScript'''<br/>
Wir können auch noch den Autofokus-Mechanismus von Hello-World-06 nachbilden.
Dazu verwenden wird zwei Vue-Observerfunktionen <code>onMounted</code> und <code>onUnmounted</code>.
Diese werden jeweils aufgerufen, wenn das Ergeignis „Komponente wurde in den DOM-Baum eingefügt“ bzw.
„Komponente wurde aus dem DOM-Baum entfernt“ eintritt. Wenn die Kompontente eingefügt wird, werden
die bekannten Autofokus-Eventlistener registriert und der Autofokus aktiviert.
Wenn die Komponenten entfernt wird, werden die Autofokus-Eventlistener leöscht, da evtl.
andere Komponenten einen anderen Autofokus aktivieren möchten.
Da der Autofokus auch bei Beträtigung des Reset-Buttons aktiviert werden soll, müssen Sie seine ID
<code>"button_reset"</code> wieder ins Template einfügen.
<source lang="javascript">
<script setup>
  import { ref, computed, onMounted, onUnmounted } from 'vue'
  const
    section      = ref('form'),
    helloStranger = ref('Hello, Stranger'),
    name          = ref(''),
    hello      = computed(() => `Hello, ${name.value}!`),
    visibility = p_section =>
                p_section !== section.value ? 'hidden' : '',
    sayHello  = () => section.value = 'hello',
    autofocus  = () => document.getElementById('input_name').focus()
  onMounted
  ( () =>
    { window.addEventListener('focus', autofocus);
      document.getElementById('button_reset').addEventListener('click', autofocus);
      autofocus();
    }
  )
  onUnmounted
  ( () =>
    { window.removeEventListener('visibilitychange', autofocus);
      document.getElementById('button_reset').removeEventListener('click', autofocus); 
    }
  )
</script>
</source>
</source>


Zeile 272: Zeile 403:
== Alternative Skript-Varianten ==
== Alternative Skript-Varianten ==


=== <code><script setup></code> (Vue 3.2)</code> ===
Im Folgenden sehen Sie drei Skript-Varianten (ohne Behandlung von Autofocus). Bitte benutzen Sie in Ihrem Prhekte die erste Variante (<code><script setup</code>).
 
=== <code><script setup></code> (Vue 3.2, Vue 3.4)</code> ===
<source lang="javascript">
<source lang="javascript">
<script setup>
<script setup>
Zeile 278: Zeile 411:


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


     hello      = computed(() => `Hello, ${name.value}!`),
     hello      = computed(() => `Hello, ${name.value}!`),
     visability = p_section =>
     visibility = p_section =>
                 p_section !== section.value ? 'hidden' : '',
                 p_section !== section.value ? 'hidden' : '',
     sayHello  = () => { section.value = 'hello' }
     sayHello  = () => section.value = 'hello'
</script>
</script>
</source>
</source>
Zeile 297: Zeile 430:
   { setup()
   { setup()
     { const
     { const
         section      = ref('question'),
         section      = ref('form'),
         helloStranger = ref('Hello, Stranger'),
         helloStranger = ref('Hello, Stranger'),
         name          = ref(''),
         name          = ref(''),


         hello      = computed(() => `Hello, ${name.value}!`),
         hello      = computed(() => `Hello, ${name.value}!`),
         visability = p_section =>
         visibility = p_section =>
                     p_section !== section.value ? 'hidden' : '',
                     p_section !== section.value ? 'hidden' : '',
         sayHello  = () => { section.value = 'hello' }
         sayHello  = () => section.value = 'hello'


       return {
       return {
         section, helloStranger, name,
         section, helloStranger, name,
         hello, visability, sayHello
         hello, visibility, sayHello
       }
       }
     }
     }
Zeile 329: Zeile 462:
     function()
     function()
     { return {
     { return {
         section:      'question',
         section:      'form',
         helloStranger: 'Hello, Stranger!',
         helloStranger: 'Hello, Stranger!',
         name:          ''
         name:          ''
Zeile 344: Zeile 477:
       { this.section = 'hello' },
       { this.section = 'hello' },


       visability(p_section)
       visibility(p_section)
       { return this.section !== p_section ? 'hidden' : '' }
       { return this.section !== p_section ? 'hidden' : '' }
     }
     }
Zeile 364: Zeile 497:
<!--references/-->
<!--references/-->
<ol>
<ol>
<li value="1"> {{Quelle|Kowarschick, W.: Multimedia-Programmierung}}</li>
<li value="1"> {{Quelle|Kowarschick, W.: Web-Programmierung}}</li>
</ol>
</ol>
<noinclude>[[Kategorie: HTML5-Tutorium: JavaScript: Hello World]][[Kategorie: HTML5-Beispiel]][[Kategorie:Kapitel:Multimedia-Programmierung:Beispiele]]</noinclude>
<noinclude>[[Kategorie: HTML5-Tutorium: JavaScript: Hello World]][[Kategorie: HTML5-Beispiel]][[Kategorie:Kapitel:Multimedia-Programmierung:Beispiele]]</noinclude>

Version vom 30. April 2024, 14:55 Uhr

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

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

Anwendungsfälle (Use Cases)

Gegenüber dem dritten, vierten, fünften und sechsten Teil des Tutoriums ändern sich die die Anwendungsfälle nicht. Die Anwendung leistet also genau dasselbe wie zuvor.

Aufgabe

In diesem Teil geht es zunächst darum, die Lösung aus Teil 6 des Tutoriums mit vue zu realisieren.

Erstellen eines neuen Projekts

Erstellen Sie ein neues Projekt hello_world_vue:

cd <somewhere>

npm init vue@latest
  Ok to proceed? (y)
  Project name: » hello_world_vue
  Add TypeScript? » No (Yes mittels Tab-Taste)
  Add ESLint for code quality? » Yes

cd hello_world_vue
npm install
npm run dev  # Im Browser: http://localhost:5173/

# Linefeed ändern (unter Windows => Unix-Zeilenende)
cat <<EOF > .vscode/settings.json
{ "files.eol": "\n" }
EOF
# Tragen Sie
#     !.vscode/settings.json
# in die Datei .gitignore ein.
# VLC (neu) starten, damit settings.json gelesen wird
# und dann ein Terminal für das Projekt öffnen

# initialize git
git init
git add -A
git commit -m "initial commit"

git remote add origin https://gitlab.multimedia.hs-augsburg.de/ACCOUNT/hello_world_vue
git remote -v
git push --set-upstream origin master

# create a new git branch
git checkout -b v00
git push --set-upstream origin v00

Anmerkungen

Vue ist ein Framework zum Erstellen von Single-Page-Web-Anwendungen. Allerdings gibt es eine Router-Erweiterung: Multi-Page-Web-Anwendungen.

Vue basierte bis Version 3.0 auf Webpack. Webpack gilt allerdings schon wieder als Dinosaurier (existiert seit 2012)

Seit Version 3.2 verwendet Vue Vite (französisch „schnell“). Dies hat zur Konsequenz, dass das Initialisieren und Testen einer Vue-Anwendung anders erfolgt als früher.

Vue 3 unterstützt im Gegensatz zu Vue 2 TypeScript. Allerdings wir im Tutorium Typescript nicht eingesetzt.

Vue-Anwendungen sind aus Komponenten aufgebaut. Das passt sehr gut Atomic-Design-Prinzip.

Überführung von hello-world-06 nach vue

git checkout -b v01   # neuen Git-Zweig (branch) anlegen
npm i -D sass         # sass aktivieren

.gitignore            # ansehen
LICENSE               # wird aus einem anderen Projekt kopiert
package.json          # mit package.json von hello_world_06 mergen
vite.config.js        # mit vite.config.js von hello_world_06 mergen
                      # Ausnahmen: root, publicDir, und outDir
index.html            # gemäß hello_world_06 definieren; Import von 'main.js' anpassen (src="src/main.js")
                      # Der Inhalt des Body-Elements wird ersetzt: <main id="app"></main>
src/css               # Ordner von hello_world_06 übernehmen
public                # favicon ersetzen
README.md             # ansehen und verbessern

npm run dev           # Vite-Server neu starten

Tipp
Wenn Sie Code von mir verwenden, sollten Sie in die Datei eslintrc.cjs nach Zeile 10 folgenden Code einfügen:

  ignorePatterns:
  [ 'dist', 'public'], 
  rules: 
  { 'no-unexpected-multiline': 0 }

Relative Pfade
Um relative Pfade beim Building zu erzwingen, reicht es nicht, die Option base: in vite.config.js einzufügen. Sie müssen dies zusätzlich in vue.config.js festlegen:

import { defineConfig } from '@vue/cli-service'

export default defineConfig ({ publicPath: '' })

Passen Sie auch noch die Datei jsconfig.json an, damit in Visual Studio Code die Pfadverfolgung mittels Point and Click ermöglicht wird. Leider funktioniert dies nicht für den Alias config.

{ "compilerOptions": 
  {  "baseUrl": ".",
     "paths": 
      {  "@/*":    ["./src/*"],
         "/css/*": ["./src/css/*.scss"]
      }
  },
  "exclude":    [ "node_modules", "dist" ],
  "extensions": [".js", ".vue", ".json"]
}

src/main.js

Da wir in diesem Tutorium die JavaScript-Dateien asynchron laden, müssen sie das Main-Skript entsprechenden anpassen. Die App darf erst erstellt und gemounted werden, wenn alle Dateien der App vollständig geladen wurden.

import { createApp } from 'vue'
import App from './App.vue'

window.addEventListener
( 'load', 
  () => createApp(App).mount('#app')
)

index.html

  • Die Single-Page-Datei index-html wird gemäß Hello World 06 definiert.

Allerdings wird der Inhalt des Bodies in die Komponenten-Datei App.vue verlagert. Wenn Sie eine Datei favicon.ico zur Hand haben, können Sie die Vite-Version dieser Datei ersetzen.

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset = "UTF-8">
  <meta name    = "viewport"
        content = "width=device-width, initial-scale=1.0, user-scalable=yes"
  >

  <title>Hello World Vue (v01)</title>
  
  <style>
    @import url("/css/head.scss");
  </style>
 
  <script src="src/main.js" type="module" async></script>
</head>

<body>
  <main id="app"></main>
</body>

</html>

src/App.vue

  • Die Hello-World-Komponente und das Style-Element laden.
  • Nur die Hello-World-Komponente ins Template einfügen.
  • body.scss importieren (ohne das Attribut scoped, damit der Inhalt der CSS-Datei allen Komponenten zur Verfügung steht).
<script setup>
  import HelloWorld from './components/HelloWorld.vue'
</script>

<template>
  <HelloWorld />
</template>

<style lang="scss">
  @import '/css/body.scss';
</style>

Der Ordner src/assets wird nun nicht mehr benötigt und kann gelöscht werden ebenso wie die anderen beiden Komponenten.

src/components/HelloWorld.vue

  • Sections aus index.html in Template-Element einfügen.
  • Sectionspezifische SCSS-Anweisungen in src/css/HelloWorld.scss verschieben und diese Datei in src/components/HelloWorld.vue importieren (@import '@/css/components/HelloWorld'; ).
  • Skript-Element befüllen.

SCSS für die Hello-World-Komponente

/* src/css/components/HelloWorld.scss */

/* Hier wird alles aus der Datei body.scss übernommen,
 * bis auf html {...} und body {...}.
 */

Hello-World-Komponente

<!-- src/components/HelloWorld.vue -->

<script setup>
  import { ref, computed } from 'vue'

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

    hello      = computed(() => `Hello, ${name.value}!`),
    visibility = p_section =>
                 p_section !== section.value ? 'hidden' : '',
    sayHello   = () => section.value = 'hello'
</script>

...

<style scoped lang="scss">
  @import '@/css/components/HelloWorld';
</style>

Im Template von HelloWorld.vue sollten Sie alle Attribute "id" mit Ausnahme von "section_from", "section_hello" und "input_name" löschen.

Der Grund ist, dass ID-Attribute im HTML-Dokument eindeutig sein müssen. Wenn in einer Komponente ein ID-Attribut definiert wird, kann man diese nicht mehrfach in eine andere Komponente einfügen.

Ein Ausnahme bildet das Attribut "input_name", da dieser Identifikator von zugehörigen laben referenziert wird: <label for="input_name">. Bei der Erstellung einer eigenständigen Komponente Textfield muss man daher darauf achten, dass bei jeder Verwendung dieser Komponente ein neuer eindeutiger Identifikator (z. B. mittels uuid) erzeugt wird. (Siehe Version v04.)

Außerdem benötig man diese ID in der Version v02 für den Enter-Key-Controller.

Darüber hinaus benötigen wir die Section-IDs für CSS. Diese werden erst später gelöscht.

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

...

src/components/HelloWorld.vue

  • Sections aus index.html in Template-Element einfügen.
  • Sectionspezifische SCSS-Anweisungen in src/css/HelloWorld.scss verschieben und diese Datei in src/components/HelloWorld.vue importieren (@import '@/css/components/HelloWorld'; ).
  • Skript-Element befüllen.

SCSS für die Hello-World-Komponente

/* src/css/components/HelloWorld.scss */

#section_form
{ text-align:   center;
  margin-left:  auto;
  margin-right: auto;

  h1
  { margin-bottom: 0.5ex; 
    margin-top:    auto;
  }
}

#section_hello
{ p
  { font-size: $font-size-p-large !important; }
}

Hello-World-Komponente

<!-- src/components/HelloWorld.vue -->

<script setup>
  import { ref, computed } from 'vue'

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

    hello      = computed(() => `Hello, ${name.value}!`),
    visibility = p_section =>
                 p_section !== section.value ? 'hidden' : '',
    sayHello   = () => section.value = 'hello'
</script>

...

<style scoped lang="scss">
  @import '@/css/components/HelloWorld';
</style>

Im Template von HelloWorld.vue sollten Sie alle Attribute "id" mit Ausnahme von "section_form", "section_hello" und "input_name" löschen.

Der Grund ist, dass ID-Attribute im HTML-Dokument eindeutig sein müssen. Wenn in einer Komponente ein ID-Attribut definiert wird, kann man diese nicht mehrfach in eine andere Komponente einfügen.

Ein Ausnahme bildet das Attribut "input_name", da dieser Identifikator von zugehörigen laben referenziert wird: <label for="input_name">. Bei der Erstellung einer eigenständigen Komponente Textfield muss man daher darauf achten, dass bei jeder Verwendung dieser Komponente ein neuer eindeutiger Identifikator (z. B. mittels uuid) erzeugt wird. (Siehe Version v04.)

Außerdem benötig man diese ID in der Version v02 für den Enter-Key-Controller.

Darüber hinaus benötigen wir die Section-IDs für CSS. Diese werden erst später gelöscht.

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

...

Autofokus-Aktivierung per JavaScript

Wir können auch noch den Autofokus-Mechanismus von Hello-World-06 nachbilden. Dazu verwenden wird zwei Vue-Observerfunktionen onMounted und onUnmounted. Diese werden jeweils aufgerufen, wenn das Ergeignis „Komponente wurde in den DOM-Baum eingefügt“ bzw. „Komponente wurde aus dem DOM-Baum entfernt“ eintritt. Wenn die Kompontente eingefügt wird, werden die bekannten Autofokus-Eventlistener registriert und der Autofokus aktiviert. Wenn die Komponenten entfernt wird, werden die Autofokus-Eventlistener leöscht, da evtl. andere Komponenten einen anderen Autofokus aktivieren möchten.

Da der Autofokus auch bei Beträtigung des Reset-Buttons aktiviert werden soll, müssen Sie seine ID "button_reset" wieder ins Template einfügen.

<script setup>
  import { ref, computed, onMounted, onUnmounted } from 'vue'

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

    hello      = computed(() => `Hello, ${name.value}!`),
    visibility = p_section =>
                 p_section !== section.value ? 'hidden' : '',
    sayHello   = () => section.value = 'hello',
    autofocus  = () => document.getElementById('input_name').focus()

  onMounted
  ( () => 
    { window.addEventListener('focus', autofocus);
      document.getElementById('button_reset').addEventListener('click', autofocus);
      autofocus();
    }
  )

  onUnmounted
  ( () => 
    { window.removeEventListener('visibilitychange', autofocus);
      document.getElementById('button_reset').removeEventListener('click', autofocus);  
    }
  )
</script>

Testen

Rufen Sie nun npm run dev oder npm run build sowie npm run prebuild auf.

Alternative Skript-Varianten

Im Folgenden sehen Sie drei Skript-Varianten (ohne Behandlung von Autofocus). Bitte benutzen Sie in Ihrem Prhekte die erste Variante (<script setup).

<script setup> (Vue 3.2, Vue 3.4)

<script setup>
  import { ref, computed } from 'vue'

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

    hello      = computed(() => `Hello, ${name.value}!`),
    visibility = p_section =>
                 p_section !== section.value ? 'hidden' : '',
    sayHello   = () => section.value = 'hello'
</script>

Composition API (Vue 3.0)

<script>
  import { ref, computed } from 'vue'

  export default
  { setup()
    { const
        section       = ref('form'),
        helloStranger = ref('Hello, Stranger'),
        name          = ref(''),

        hello      = computed(() => `Hello, ${name.value}!`),
        visibility = p_section =>
                     p_section !== section.value ? 'hidden' : '',
        sayHello   = () => section.value = 'hello'

      return {
        section, helloStranger, name,
        hello, visibility, sayHello
      }
    }
  }
</script>

Diese Variante kann ohne weitere Anpassungen an Stelle der Script-Setup-Variante verwendet werden. Allerdings ist die Script-Setup-Variante leichter zu schreiben und zu lesen.

Musterlösung wk_hello_world_vue, git checkout v01b

Options API (Vue 2.0)

<script>
  export default
  { name: 'HelloWorld',

    data:
    function()
    { return {
        section:       'form',
        helloStranger: 'Hello, Stranger!',
        name:          ''
      }
    },

    computed:
    { hello()
      { return `Hello, ${this.name}!` }
    },

    methods:
    { sayHello()
      { this.section = 'hello' },

      visibility(p_section)
      { return this.section !== p_section ? 'hidden' : '' }
    }
  }
</script>

Diese Variante kann ohne weitere Anpassungen an Stelle der Script-Setup-Variante verwendet werden. Allerdings ist die Script-Setup-Variante leichter zu schreiben und zu lesen. Die Options-API gilt als veraltet.

Musterlösung wk_hello_world_vue, git checkout v01c

Fortsetzung des Tutoriums

Sie sollten nun Teil 2 des Vue-Tutoriums bearbeiten. Es wird ein Keyup-Event-Handler ergänzt, zu Wahrung der Barrierefreiheit.

Quellen

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