Anteckningar
- Node.js ≥16, npm 8
npm init vue@latest
- Välj No på allt
cd <projektnamn>
npm install
npm run dev
Filändelsen *.vue
:
<script>
(JavaScript)<template>
(HTML-baserad template)<style>
(CSS)
const HelloWorld = {
data() {
return {
msg: 'Hello!',
};
},
template: `<h1>{{ msg }}</h1>`,
};
template
som en vanlig sträng
<script>
export default const {
data() {
return {
msg: 'Hello!',
};
},
}
</script>
<template>
<h1>{{ msg }}</h1>
</template>
- standard i Vue 2
<script>
import { ref } from 'vue';
export default const {
setup() {
const msg = ref('Hello!');
return { msg };
},
};
</script>
<template>
<h1>{{ msg }}</h1>
</template>
- nya Composition API utan socker
<script>
import { ref } from 'vue';
const msg = ref('Hello!');
</script>
<template>
<h1>{{ msg }}</h1>
</template>
- socker för Composition API
- Ta bort alla komponenter utom
App.vue
, och ta bortlogo.svg
- Ersätt hela template-innehållet i
App.vue
medTomt!
- Ta bort all CSS i
App.vue
utom@import ...
och#app {...}
Skapa src/components/Resource.vue
:
<template>
<div class="resource">
<h2>Aftonbladet 1900-talet</h2>
<p>Del av samlingen Kubhist2</p>
</div>
</template>
<style scoped>
.resource {
background: #ded;
border-radius: .5em;
padding: 1em;
margin-bottom: 1em;
}
</style>
App.vue:
import Resource from './components/Resource.vue'
<Resource />
Resource.vue:
defineProps({
name: String,
description: String,
})
<h2>{{ name }}</h2>
<p>{{ description }}</p>
App.vue:
<Resource
name="Aftonbladet 1900-talet"
description="Del av samlingen Kubhist2"
/>
Undersök datastrukturen i API:et: https://ws.spraakbanken.gu.se/ws/metadata/corpora
const resources = [
{
"name_sv": "Aftonbladet 1900-talet",
"description_sv": "Del av samlingen Kubhist2",
},
{
"name_sv": "Alfwar och Skämt 1840-talet",
"description_sv": "Del av samlingen Kubhist2",
},
]
<Resource
v-for="resource in resources"
:key="resource.name_sv"
:name="resource.name_sv"
:description="resource.description_sv" />
- Kolon
:
för att ange JS-uttryck i props - Vue 2:
key
inte längre nödvändigt, men behövs i regel om listan ska uppdateras dynamiskt
App.vue:
async function loadResources() {
const response = await fetch('https://ws.spraakbanken.gu.se/ws/metadata/corpora')
const data = await response.json()
resources = data.resources
}
loadResources()
- async-await
- körs när komponent-instansen skapas
- Vue 2:
created()
- Vue 2:
import { ref } from 'vue'
const resources = ref([])
// ...
resources.value = data.resources
- Wrappa i
ref
: template kan lyssna på förändringar resources.value
för att läsa/skriva en ref-variabel- Vue 2:
data()
Vill att man ska kunna fälla ut/in varje ruta vid klick.
- State: expanded
Resource.vue:
const expanded = false
<p v-if="expanded">
v-if
:<p>
-elementet uteblir annars
const expanded = ref(false)
function toggle() {
expanded.value = !expanded.value
}
- argumentet till
ref()
är det initiala värdet - Vue 2:
methods
<div @click="toggle">
<h2>{{ name }}</h2>
<p v-if="expanded">{{ description }}</p>
</div>
@
lyssna
Vill ha en indikator för utfällt läge
<div @click="toggle" class="resource">
<div class="toggle-marker">
<template v-if="expanded">-</template>
<template v-else>+</template>
</div>
.toggle-marker {
float:right;
font-size: larger;
}
Resource.vue:
<div ... :style="{backgroundColor: expanded ? '#dde' : '#ded'}">
<h2 :style="{fontWeight: expanded ? 'normal' : 'bold'}">
- ett objekt med
camelCase
istfkebab-case
Vill separera CSS och undvika style-attribut
Resource.vue:
.collapsed {
background-color: #dde;
}
.collapsed h2 {
font-weight: bold;
}
<div ... :class="expanded ? '' : 'collapsed'">
- Kan använda
:class
parallellt medclass
<div ... :class="{collapsed: !expanded}">
- objekt med
[klassnamn]: [bool]
Vill filtrera på "19" t ex
const filterResource = a => a.name_sv.includes("19")
const resourcesFiltered = computed(() => resources.value.filter(filterResource))
v-for="resource in resourcesFiltered"
- Vue 2:
computed: { hasDescription: () => ... }
Vill modifiera filtret med formulär
App.vue:
<div class="filter-form">
<h2>Sök</h2>
<input v-model="filter" />
{{ filter }}
</div>
const filter = ref('')
- funkar på formulärelementen:
input
,select
,textarea
const filterResource = resource => resource.name_sv.includes(filter.value)
.filter-form {
background-color: #dee;
padding: 1em;
margin-bottom: 2em;
}
- Extrahera toggle/expand-bitar till egen "composable"
- gärna "stateful" logik
- Mönstret är att exportera en funktion
useX()
som instansierar bitarna- innehåller det
<script setup>
innehåller - returnerar flera saker som objekt
- innehåller det
src/composables/toggle.js:
import { ref } from 'vue'
export default function useToggle() // 1
const expanded = ref(false) // 2
function toggle() { // 3
expanded.value = !expanded.value
}
return {
expanded, // 2
toggle, // 3
}
}
Resource.vue:
import useToggle from '../composables/toggle'
const { expanded, toggle } = useToggle()
App.vue:
import useToggle from './composables/toggle'
const { expanded, toggle } = useToggle()
<div class="filter-form" @click="toggle">
<div class="toggle-marker">
<template v-if="expanded">-</template>
<template v-else>+</template>
</div>
<input v-if="expanded" v-model="filter" />
src/composables/TogglerMarker.vue:
<script setup>
defineProps({
expanded: Boolean,
})
</script>
<template>
<div class="toggle-marker">
<template v-if="expanded">-</template>
<template v-else>+</template>
</div>
</template>
<style>
.toggle-marker {
float:right;
font-weight: bold;
}
</style>
toggle.js:
import ToggleMarker from './ToggleMarker.vue
return {
// ...
ToggleMarker,
}
Resource.vue:
<ToggleMarker v-if="hasDescription" :expanded="expanded" />
App.vue:
<ToggleMarker :expanded="expanded" />
- Nu kan man inte använda textfältet
App.vue:
<input v-if="expanded" v-model="filter" @click.stop />
- Lyssna på
click
men utan någon särskild handler - Däremot med
.stop
som socker förevent.stopPropagation()
- click-eventet når då inte överordnade element
src/composables/resources.js:
import { computed, ref } from 'vue'
export default function useResources(filterRef) {
const resources = ref([])
const filterResource = resource => resource.name_sv.includes(filterRef.value)
const resourcesFiltered = computed(() => resources.value.filter(filterResource))
const compareResources = (a, b) => a.name_sv.localeCompare(b.name_sv, 'sv')
const resourcesSorted = computed(() => resourcesFiltered.value.sort(compareResources))
async function loadResources() {
const response = await fetch('https://ws.spraakbanken.gu.se/ws/metadata/corpora')
const data = await response.json()
resources.value = data.resources
}
loadResources()
return {
resources: resourcesSorted
}
}
App.vue:
import useResources from './composables/resources'
const { resources } = useResources(filter)
v-for="resource in resources"
App.vue:
<select v-if="expanded" v-model="type" @click.stop>
<option>corpora</option>
<option>lexicons</option>
<option>models</option>
</select>
- Sätter textinnehållet som värde, om man inte sätter
value=""
const type = ref('corpora')
const { resources } = useResources(type, filter)
resources.js:
export default function useResources(typeRef, filterRef) {
const response = await fetch('https://ws.spraakbanken.gu.se/ws/metadata/' + typeRef.value)
// - loadResources()
watchEffect(() => loadResources())
watchEffect
märker av vilka reaktiva variabler som används- när variablerna ändras körs funktionen igen
- Vue 2:
watch: { type() {...} }
- Samma mekanism finns i Vue 3:
watch(typeRef, () => {...})
- Samma mekanism finns i Vue 3:
Säg att vi vill välja någon resurs för att se all info till höger.
Columns.vue:
<template>
<div class="columns">
<div class="left">
<slot name="left" />
</div>
<div class="right">
<slot name="right" />
</div>
</div>
</template>
<style>
.columns {
display: flex;
}
.left {
width: 50%;
padding-right: 1em;
}
.right {
width: 50%;
padding-left: 1em;
}
</style>
<slot name="">
Här hamnar innehåll
App.vue:
import Columns from './components/Columns.vue';
<Columns>
<template #left>
<Resource ... />
</template>
<template #right>
...
</template>
</Columns
#left
: lägg innehållet i<slot name="left">
- Vue 2:
<template v-slot:left>
Något händer i en komponent långt ner i trädet, och det ska propageras uppåt.
Resource.vue:
<h2>{{ name }}</h2>
<div v-if="expanded" @click.stop>
<p>{{ description }}</p>
<button @click="select">Visa</button>
</div>
const emit = defineEmits(['select'])
function select() {
emit('select')
}
App.vue:
<Resource ... @select="showResource(resource.id)" />
const activeResource = ref(null)
function showResource(id) {
activeResource.value = resources.value.find(resource => resource.id === id)
}
<template #right>
<pre>{{ activeResource }}</pre>
</template>
- Skriva ut objekt i template: automatiskt
JSON.stringify
- Vue 2:
this.$emit()
, behöver inte deklarera events
För att slippa detaljer i App.vue.
ResourceDetails.vue:
<script setup>
defineProps({
resource: Object,
})
</script>
<template>
<h1>{{ resource.name_sv }}</h1>
<p>{{ resource.description_sv }}</p>
<p>
Språk:
{{ resource.lang.map(lang => lang.name_sv).join(', ')}}
</p>
</template>
App.vue:
import ResourceDetails from './components/ResourceDetails.vue';
<template #right>
<ResourceDetails
v-if="activeResource"
:resource="activeResource" />
</template>
App.vue:
<ResourceDetails ... v-bind="activeResource" />
- Varje element i objektet
activeResource
blir ett attribut - De attribut som är props blir props
ResourceDetails.vue:
const props = defineProps({
id: String,
name_sv: String,
description_sv: String,
lang: Array,
downloads: Array,
})
<h1>{{ name_sv }}</h1>
<p>{{ description_sv }}</p>
<p>
Språk:
{{ lang.map(lang => lang.name_sv).join(', ')}}
</p>
- Mer i
defineProps
, mindre i template
Längre beskrivingstexter finns för vissa resurser om man lägger till ?resource=<id>
i urlen.
ResourceDetails.vue:
const props = defineProps({
id: String,
id
finns där i objektet, bara deklarera prop:en för det
const long_description = ref('')
watchEffect(async () => {
long_description.value = ''
const response = await fetch('https://ws.spraakbanken.gu.se/ws/metadata/?resource=' + props.id)
const data = await response.json()
long_description.value = data.long_description_sv
})
- Tom sträng
''
om ingen lång text finns.
<div>{{ long_description }}</div>
- HTML blir escaped!
<div v-html="long_description" />
- OBS! Säkerhetsrisk. Man måste lita på källan.