This is a continuation of a video series on using AWS Amplify Datastore with Vue JS and Ionic Framework for the user interface. In the first two parts of the video we did setup, user authentication/account creation and querying data.
In the third video we cover the remaining CRUD actions of creating, updating and deleting data from AWS Amplify Datastore.
This blog post is to provide the source code from the project.
Please see the entire tutorial videos by using the link below
ENTRYFORM.VUE
<template>
<ion-header :translucent="true">
<ion-toolbar>
<ion-title>Create New Entry</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-item>
<ion-label>Title</ion-label>
<ion-input type="" v-model="formData.title"></ion-input>
</ion-item>
<ion-item>
<ion-label>Description</ion-label>
<ion-textarea rows="3" v-model="formData.description"></ion-textarea>
</ion-item>
<ion-item>
<ion-label position="fixed">Status</ion-label>
<ion-checkbox
slot="end"
:modelValue="formData.status"
@update:modelValue="formData.status = $event"
></ion-checkbox>
</ion-item>
<ion-item>
<ion-label>Start Date</ion-label>
<ion-datetime disabled v-model="formData.start_date"></ion-datetime>
</ion-item>
<ion-item>
<ion-label>Completion Date</ion-label>
<ion-datetime disabled v-model="formData.end_date"></ion-datetime>
</ion-item>
</ion-content>
<ion-footer :translucent="true">
<ion-toolbar>
<ion-button @click="saveData()">SAVE</ion-button>
<ion-button @click="$emit('closeModal', null)">CANCEL</ion-button>
</ion-toolbar>
</ion-footer>
</template>
<script lang="ts">
import {
IonContent,
IonItem,
IonLabel,
IonButton,
IonInput,
IonToolbar,
IonTitle,
IonHeader,
IonFooter,
IonTextarea,
IonCheckbox,
IonDatetime
} from "@ionic/vue";
import { ref } from "vue";
export default {
props: ["initialData"],
emits: ["closeModal"],
setup(props, ctx) {
const formData = ref<any>({
status: false,
["start_date"]: new Date().toLocaleString(),
...props.initialData
});
/**
*
*/
const saveData = () => {
if ( formData.value.status === true && props.initialData.status !== true) {
// set the completion date
formData.value['end_date'] = new Date().toLocaleString();
}
ctx.emit("closeModal", { ...formData.value });
};
return {
formData,
saveData
};
},
components: {
IonContent,
IonItem,
IonLabel,
IonButton,
IonInput,
IonToolbar,
IonTitle,
IonHeader,
IonFooter,
IonTextarea,
IonCheckbox,
IonDatetime
}
};
</script>
<style lang="css" scoped>
</style>
HOME.VUE
<template>
<!-- amplify-ui -->
<amplify-authenticator username-alias="email">
<ion-page>
<ion-header :translucent="true">
<ion-toolbar>
<ion-title>HOME</ion-title>
<ion-buttons slot="end">
<ion-button @click="showInputModal(null)">NEW</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content :fullscreen="true" class="ion-padding">
<p>TASKS: {{ user?.attributes?.email }}</p>
<div>
<ion-list>
<ion-item v-for="t in tasks" :key="t.id">
<ion-label class="ion-text-wrap">
<p>{{ t.title }}</p>
<p>{{ t.description }}</p>
<p id="id">{{ t.id }}</p>
</ion-label>
</ion-item>
</ion-list>
</div>
<ion-modal
:is-open="showModal?.isOpen"
@onDidDismiss="showModal.isOpen = false"
>
<entry-form
:initialData="showModal?.data"
@closeModal="handleCloseModal"
></entry-form
></ion-modal>
</ion-content>
<ion-footer class="ion-padding">
<amplify-sign-out></amplify-sign-out>
</ion-footer>
</ion-page>
<!-- [end] amplify-ui -->
</amplify-authenticator>
</template>
<script lang="ts">
import {
IonContent,
IonPage,
IonTitle,
IonToolbar,
IonHeader,
IonFooter,
IonList,
IonItem,
IonLabel,
IonButton,
IonButtons,
IonModal
} from "@ionic/vue";
import { defineComponent, onMounted, onUnmounted, ref } from "vue";
import { onAuthUIStateChange } from "@aws-amplify/ui-components";
import { DataStore } from "@aws-amplify/datastore";
import { Task } from "../models";
import EntryForm from "@/components/EntryForm.vue";
export default defineComponent({
name: "Home",
setup() {
let unsubscribeAuth: any = null;
let unsubscribeData: any = null;
const curAuthState = ref<any>(null);
const user = ref<any>(null);
const showModal = ref<{
isOpen: boolean;
data: any;
}>({ isOpen: false, data: null });
// results from query
const tasks = ref<any>([]);
onUnmounted(() => {
try {
unsubscribeAuth();
if (unsubscribeData) unsubscribeData();
} catch (e) {
console.log(e);
}
});
const getData = async () => {
const models = await DataStore.query(Task);
console.log(models);
tasks.value = models;
};
/**
*
*/
const createData = async (data: any) => {
console.log(data);
return await DataStore.save(new Task({ ...data }));
};
/**
*
*/
const deleteData = async (data: any) => {
return await DataStore.delete(Task, t => t.id("eq", data.id));
};
/**
*
*/
const saveData = async (data: any) => {
const original = await DataStore.query(Task, data.id);
if (original === undefined) return;
return await DataStore.save(
Task.copyOf(original, (updated: any) => {
updated.title = data.title;
updated.description = data.description;
updated.status = data.status;
updated["end_date"] = data.end_date;
})
);
};
const handleCloseModal = async (payload: any) => {
showModal.value = {
isOpen: false,
data: null
};
// save data
console.log(payload);
if (payload) {
try {
await (payload?.id !== undefined
? saveData(payload)
: createData(payload));
} catch (e) {
debugger;
console.log(e);
}
}
};
const showInputModal = (value: any) => {
if (value === null) {
showModal.value = {
isOpen: true,
data: null
};
} else {
showModal.value = {
isOpen: true,
data: value
};
}
};
onMounted(() => {
unsubscribeAuth = onAuthUIStateChange((authState, authData) => {
curAuthState.value = authState;
user.value = authData;
if (user.value) {
getData();
const { unsubscribe } = DataStore.observe(Task).subscribe(msg => {
console.log(msg.model, msg.opType, msg.element);
getData();
});
unsubscribeData = unsubscribe;
}
});
});
return {
user,
tasks,
showModal,
//functions
showInputModal,
handleCloseModal,
deleteData
};
},
components: {
IonContent,
IonPage,
IonTitle,
IonToolbar,
IonHeader,
IonFooter,
IonList,
IonItem,
IonLabel,
IonButton,
IonModal,
IonButtons,
EntryForm
}
});
</script>
<style scoped>
#id {
font-size: x-small;
}
</style>
MAIN.TS
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import { IonicVue } from "@ionic/vue";
/* Core CSS required for Ionic components to work properly */
import "@ionic/vue/css/core.css";
/* Basic CSS for apps built with Ionic */
import "@ionic/vue/css/normalize.css";
import "@ionic/vue/css/structure.css";
import "@ionic/vue/css/typography.css";
/* Optional CSS utils that can be commented out */
import "@ionic/vue/css/padding.css";
import "@ionic/vue/css/float-elements.css";
import "@ionic/vue/css/text-alignment.css";
import "@ionic/vue/css/text-transformation.css";
import "@ionic/vue/css/flex-utils.css";
import "@ionic/vue/css/display.css";
/* Theme variables */
import "./theme/variables.css";
/* AMPLIFY */
import {
applyPolyfills,
defineCustomElements,
} from "@aws-amplify/ui-components/loader";
import Amplify from "aws-amplify";
import awsconfig from "./aws-exports";
Amplify.configure(awsconfig);
applyPolyfills().then(() => {
defineCustomElements(window);
});
const app = createApp(App)
.use(IonicVue)
.use(router);
app.config.isCustomElement = (tag) => tag.startsWith("amplify-");
router.isReady().then(() => {
app.mount("#app");
});