SlideShare a Scribd company logo
Dept. of Smart Finance, Korea Polytechnics Seoul Kangseo Campus
ApplicationDevelopmentw/
QuasarFramework
Basic Course
QuasarCLIInstallation
• Prerequisite


Node >=12.22.1 and NPM >=6.14.12


• recommendation node version : v14.17.5


check your node and npm : node -v


npm -v


• npm install -g @quasar/cli
Do not use uneven versions of Node i.e. 13, 15, etc. These versions are not tested with Quasar and often cause issues due to their experimental nature. We highly recommend always using the LTS version of Node.
Ref : https://quasar.dev/quasar-cli/installation
HelloworldⅠ(1/2)
•  we create a project folder with Quasar CLI


• quasar create <folder_name>
Ref : https://quasar.dev/quasar-cli/installation
(base) waynehwang@wayneui-MacBookPro temp % quasar create hello


___


/ _  _ _ __ _ ___ __ _ _ __


| | | | | | |/ _` / __|/ _` | '__|


| |_| | |_| | (_| __  (_| | |


_____,_|__,_|___/__,_|_|




? Project name (internal usage for dev) hello


? Project product name (must start with letter if building mobile apps) hello


? Project description hello world


? Author wayne hwang <wonyong@wonyong.net>


? Pick your CSS preprocessor: SCSS


? Check the features needed for your project: ESLint (recommended), Vuex, Axios


? Pick an ESLint preset: Prettier


? Continue to install project dependencies after the project has been created? (


recommended) NPM


Quasar CLI · Generated "hello".


[*] Installing project dependencies …


[*] Quasar Project initialization finished!


To get started:


cd hello


quasar dev
Helloworld Ⅰ(2/2)
• Look into package.json, App.vue, routes.js, Index.vue, EssentialLink.vue, …


• Run your app with below command:


quasar dev
HelloWorldⅡ
• Repository address :


https://github.com/wonyongHwang/kopoPro
fi
le


• git clone https://github.com/wonyongHwang/kopoPro
fi
le


• npm install


• quasar dev
kopoProfile
HelloWorldⅡ
• In this practice, we don’t have a


drawer and a footer either. So, only


<q-header> is being a
ff
ected by


<q-layout>.


•
layout
<q-layout view="lHh lpr lFf">
reference : https://quasar.dev/layout/layout#understanding-the-view-prop
HelloWorldⅡ
• quasar can detect platform : $q.platfrom.is.~~




eg> if you will deploy app to mobile device(android,


ios) app can detect platform using $q.platform.is.


cordova
$q.platform
<div v-bind:class="{'main': $q.platform.is.desktop, 'mobile_main': $q.platform.is.ipad || $q.platform.is.mobile}"


class="full-height" style="background-color:rgba(0, 0, 0, 0.7);">
reference : https://quasar.dev/options/platform-detection#introduction
HelloWorldⅡ
style
<q-header class="q-py-sm" style="background-color:rgba(0, 0, 0, 0.7);">
reference : https://quasar.dev/style/spacing#table-of-permutations
HelloWorldⅡ
q-tab
reference : https://quasar.dev/vue-components/tabs
<q-tabs v-model="selected_tab" shrink>


<q-tab name="about_me" :style="[selected_tab == 'about_me' ? {backgroundColor: 'green'} : {}]" class="q-mr-sm q-py-xs custom_tab"


@click="scrollToElement('id_about_me')" style="width:120px;min-height:auto !important;color: white"


label="About Me"/>
HelloWorldⅡ
q-card
<q-card class="my-card" flat bordered>


<q-card-section>
reference : https://quasar.dev/vue-components/card#introduction
HelloWorldⅡ
• Get/set scroll position (Quasar Scrolling Utils)


https://quasar.dev/quasar-utils/scrolling-utils#get-set-scroll-position
<script></script>
import {scroll} from 'quasar'


const {getScrollTarget, setScrollPosition} = scroll


export default {


name: 'PageIndex',


data() {


return {


name: "",


email: "",


message: "",


selected_tab: 'about_me',


}


},


methods: {


scrollToElement(id) {


let el = document.getElementById(id)


const target = getScrollTarget(el)


const o
ff
set = el.o
ff
setTop - 65


const duration = 800


setScrollPosition(target, o
ff
set, duration)


}


},


mounted () {


}


}
homework
• customizing web page


• upload source to your github repo.


- README.md should be edited
ex>
homework
• deploy your web pages via netlify


https://app.netlify.com/
homework
• check url


• if you have a own domain, netlify allow you to access your web site


• ‘https(let’s encrypt)’ supports both custom domain and netlify domain
Make Your First Page
Basic Practice
Layout
• quasar create <folder name>


• quasar dev


•
fi
rst page is served by Index.vue


Layout
• Quasar provides basic layouts
reference : https://quasar.dev/layout/gallery
SignIn
• what you need to implement a sign-in page:


2 Input Text
fi
eld


1 Check Box


1 Button


and more …
SignIn
• copy relevant codes and paste to your “Index.vue”


https://quasar.dev/vue-components/input
SignUp
• add Signup.Vue to your project


• add page mapping on routes.js
pagelink
• vue syntax for page link :


<router-link to=“/address”> ~~ </router-link>
<router-link to="/signup" >


<center>New User? Click Here to Register.</center>


</router-link>
Homework
• Place and Organize Input Text
fi
elds (E-mail, Password, etc …)


: center alignment


• Password should be masked while typing


• Place Sign-In / Sign-Up buttons
complete your sign-in / sign-up pages!
f
irebase
• https://
fi
rebase.google.com/?hl=ko
create a project
// For Firebase JS SDK v7.20.0 and later, measurementId is optional


const
fi
rebaseCon
fi
g = {


apiKey: "AIzaSyA~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~",


authDomain: "kopologinchecker.
fi
rebaseapp.com",


projectId: "kopologinchecker",


storageBucket: "kopologinchecker.appspot.com",


messagingSenderId: "17~~~~~~~~~~~~",


appId: "1:17~~~~~~~~:web:d4~~~~~~~~",


measurementId: “G-H0~~~~~~~~~~~~"


};
• npm install --save
fi
rebase


• quasar new boot
fi
rebase


->
fi
rebase.js will be generated in src/boot


-> paste
fi
rebase con
fi
guration to
fi
rebase.js
f
irebase
setting config.
ref : https://quasar.dev/quasar-cli/boot-
fi
les
import firebase from 'firebase/app';


import 'firebase/auth';


import 'firebase/analytics'


const firebaseConfig = {


apiKey: "~",


authDomain: "kopologinchecker.firebaseapp.com",


projectId: "kopologinchecker",


storageBucket: "kopologinchecker.appspot.com",


messagingSenderId: “~",


appId: "~",


measurementId: "~"


};


if (!firebase.apps.length) {


firebase.initializeApp(firebaseConfig);


}


firebase.analytics();


export const auth = firebase.auth();
f
irebase
• enable email/password


• create a test user if needed
sign-in method
f
irebase
• https://
fi
rebase.google.com/docs/auth/web/start?hl=ko
sign-in method
fi
rebase.auth().signInWithEmailAndPassword(email, password
)

  .then((userCredential) =>
{

    // Signed i
n

    var user = userCredential.user
;

    // ..
.

  }
)

  .catch((error) =>
{

    var errorCode = error.code
;

    var errorMessage = error.message
;

  });
• let’s use quasar notify to show success/fail message


https://quasar.dev/quasar-plugins/notify#introduction
f
irebase
sign-in method
auth.signInWithEmailAndPassword(this.text, this.password).then(


(userCredential) => {


console.log(userCredential.user);


this.$router.push({ path: 'home' })


}


).catch(


(err) => {


console.log(err.code);


console.log(err.message);


this.$q.notify({


message: err.message,


color: 'purple'


})


}


)


message, color, postion, textColor, icon, …
import { useQuasar } from 'quasar'


const $q = useQuasar()


$q.notify({


message : "login success",


color : "blue"


})
vue 3
homework
• implement a sign-up function using
fi
rebase api


• validation logic should be implemented


ex> email/password should not be empty


password should be equal to ‘repeat password’


password must be at least 6 characters long


hint> https://levelup.gitconnected.com/
fi
rebase-auth-management-in-vue-js-with-vuex-9c4a5d9cedc


• move page after sign-up


hint> this.$router.push({ path: 'home' })
import { useRouter, useRoute } from 'vue-router'
export default
{

setup()
{

const router = useRouter()
const route = useRoute()
// Now you can access params like
:

console.log(route.params.id)
;

route.push({path: ‘home’}
)

}

};
vue v3
https://next.router.vuejs.org/guide/advanced/composition-api.html#accessing-the-router-and-current-route-inside-setup
• Parent -> child : props


• Child -> Parent : emit
management
f
irebaseauthw/vuex
• state management pattern and its library


• state : data


• mutations : change state (sync)


• actions : commit mutation (async)
what vuex is
Actions
State
View
Mutations
this.$store.commit


emit, props
Unidirectional
fl
ow
management
f
irebaseauthw/vuex
• state =
fi
re auth info


• ‘
fi
re auth info’ can acquire when we call


signInWithEmailAndPassword()


createUserWithEmailAndPassword()


and so on.
state
Actions
State
View
Mutations
this.$store.commit


import {auth} from "src/boot/firebase"


export default store(function (/* { ssrContext } */) {


const Store = createStore({


modules: {


// example


},


state: {


fireUser:null


},


…
State
management
f
irebaseauthw/vuex
• get the current sign-in user is by setting an observer on the Auth object


https://
fi
rebase.google.com/docs/auth/web/manage-users


• state should be null when user signed out


https://
fi
rebase.google.com/docs/auth/web/password-auth?hl=ko
action
actions: {


signOutAction({commit}) {


auth.signOut()


.then(() => {


commit("setFireUser", null);


})


},


authAction({ commit }) {


auth.onAuthStateChanged(user => {


if (user) {


commit("setFireUser", user);


}


});


},


},


mutations: {


setFireUser(state, firebaseUser){


state.fireUser = firebaseUser


}
Actions
Mutations
management
f
irebaseauthw/vuex
• prepare helper functions


https://joshua1988.github.io/web-development/vuejs/vuex-getters-mutations/
getters
getters: {


getFireUser(state) {


return state.fireUser;


},


isUserAuth(state) {


return !!state.fireUser;


}


},
management
f
irebaseauthw/vuex
source code
import { store } from 'quasar/wrappers'


import { createStore } from 'vuex'


import {auth} from "src/boot/firebase"


export default store(function (/* { ssrContext } */) {


const Store = createStore({


state: {


fireUser:null


},


actions: {


signOutAction({commit}) {


auth.signOut()


.then(() => {


commit("setFireUser", null);


})


.catch(error => {


commit("setFireError", error.message);


});


},


authAction({ commit }) {


auth.onAuthStateChanged(user => {


if (user) {


commit("setFireUser", user);


} else {


commit("setFireUser", null);


}


});


},


},


getters: {


getFireUser(state) {


return state.fireUser;


},


isUserAuth(state) {


return !!state.fireUser;


}


},


mutations: {


setFireUser(state, firebaseUser){


state.fireUser = firebaseUser


}


},


})


return Store


})
management
f
irebaseauthw/vuex
• commit state when my application obtain a
fi
rebase auth


• get a
fi
rebase auth to display user info


• check a
fi
rebase auth
usage example
auth.signInWithEmailAndPassword(this.text, this.password).then(


(userCredential) => {


this.$store.commit("setFireUser", userCredential.user);


}


)
<div class="text-h6" align="center">


Hi, {{ this.name }}({{ getFireUser.email }})


<br>


…


computed: {


...mapGetters(["getFireUser", "isUserAuth"])


},


mounted() {


this.authAction();


…


methods: {


...mapActions(["signOutAction","authAction"]),
import { useStore } from "vuex"
;

export default
{

setup()
{

const store = useStore()
;

 

store.commit("setFireUser", userCredential.user)
;

}

};
vue v3
https://kyounghwan01.github.io/blog/Vue/vue3/composition-api-vuex/#vuex-%E1%84%89%E1%85%A6%E1%84%90%E1%85%B5%E1%86%BC-%E1%84%86%E1%85%B5%E1%86%BE-store-module-1%E1%84%80%E1%85%A2%E1%84%85%E1%85%A9-%E1%84%89%E1%85%B5%E1%86%AF%E1%84%92%E1%85%A2%E1%86%BC
homework
• When a user logged in, move Vue Page and display user’s email using vuex


• tip>


<div v-if=“getFireUser">~~</div>


computed: {


...mapGetters(["getFireUser", "isUserAuth"])


},


<div class="text-h6" align="center">


Hi,({{ getFireUser.email }})


</div>


import { mapGetters } from "vuex";
vuelifecycle
• https://cornswrold.tistory.com/342


• beforeCreate, created, beforeMount, mounted,


beforeUpdate, updated, beforeDestroy, destroyed
Firestore
Firestore
Firestore
• Firestore data add: set() or add()


https://
fi
rebase.google.com/docs/
fi
restore/manage-data/add-data?hl=ko
API
db.collection("users").add({


id: this.text,


name: this.name


})


.then((docRef) => {


console.log("Document written with ID: ", docRef.id);


this.$q.notify({


message: "Register Success",


color: 'blue'


})


})


.catch((error) => {


console.error("Error adding document: ", error);


this.$q.notify({


message: error,


color: 'red'


})


});
Firestore
•
fi
rebase.js


import '
fi
rebase/
fi
restore'


export const db =
fi
rebase.
fi
restore();


• import


import { db } from "src/boot/
fi
rebase"
Preparations
Firestore
Data write
db.collection("users").add({


id: this.text,


name: this.name


})


.then((docRef) => {


console.log("Document written with ID: ", docRef.id);


})


.catch((error) => {


console.error("Error adding document: ", error);


});
Firestore
Data read
db.collection("users").where("id", "==", this.getFireUser.email )


.get()


.then((querySnapshot) => {


querySnapshot.forEach((doc) => {


// doc.data() is never undefined for query doc snapshots


console.log(doc.id, " => ", doc.data());


this.name = doc.data().name


});


})


.catch((error) => {


console.log("Error getting documents: ", error);


});
Homework
• When a user logged in, move page(/home) and display user’s name using vuex


- user name was saved at Firestore, so we have to read the data
fi
rst to show it.


• user name should be displayed on page(/home) even after refreshing page.
Homework
Hint
<template>


<div>


HOME


<div class="text-h6" align="center">


<div v-if="getFireUser">


Hi,({{ getFireUser.email }})


:) {{ name }}


</div>


</div>


</div>


</template>
<script>


import { defineComponent } from 'vue';


import { auth, db } from "src/boot/firebase"


import { useQuasar } from 'quasar'


import { mapGetters, mapActions } from "vuex"; //


export default defineComponent({


name: 'PageIndex',


computed: {


...mapGetters(["getFireUser", "isUserAuth"])


},


updated() {




if(this.getFireUser != null && this.name == ''){ // for refresh page //


db.collection("users").where("id", "==", this.getFireUser.email )


.get()


.then((querySnapshot) => {


querySnapshot.forEach((doc) => {


// doc.data() is never undefined for query doc snapshots


console.log(doc.id, " => ", doc.data());


this.name = doc.data().name


});


})


.catch((error) => {


console.log("Error getting documents: ", error);


});


}


},


mounted(){ //


this.authAction() // for refresh page //


if(this.getFireUser != null){


db.collection("users").where("id", "==", this.getFireUser.email )


.get()


.then((querySnapshot) => {


querySnapshot.forEach((doc) => {


// doc.data() is never undefined for query doc snapshots


console.log(doc.id, " => ", doc.data());


this.name = doc.data().name


});


})


.catch((error) => {


console.log("Error getting documents: ", error);


});


}


},


data(){


return{


name : ''


}


},




methods: {


...mapActions(["signOutAction","authAction"])






}


})


</script>
f
irebaseusermgmt.
• https://
fi
rebase.google.com/docs/auth/web/manage-users?hl=ko


• User Info management


- user info update


- user account delete
Some security-sensitive actions—such as deleting an account, setting a primary email address, and changing a password—require that the user has recently signed in. 
Implementationof“rememberme”usinglocalStorage
• UI


<q-checkbox v-model="remember" label="Remember Me" color="teal"/>


• Declaration


let remember = ref(‘false')


• mounted()


• When a user tries to log in: if (remember.value == true) {


localStorage.username = email.value;


localStorage.checkbox = remember.value;


} else {


localStorage.username = "";


localStorage.checkbox = "";


}


mounted(){


if(localStorage.checkbox && localStorage.checkbox !==""){


this.remember = true


this.email = localStorage.username


} else {


this.remember = false


}


}
Implementationof“rememberme”usinglocalStorage
• Test
Homework
• Elaborate your work(home/sign-in/sign-up)


• Conceptualizing your idea
OAuth
• https://
fi
rebase.google.com/docs/auth/web/google-signin?hl=ko


•
google
OAuth
• https://
fi
rebase.google.com/docs/auth/web/facebook-login?hl=ko


• https://developers.facebook.com/apps


• Only https allowed
Facebook
OAuth
GitHub
https://github.com/settings/apps
Local
Firebase
github
f
irebasehosting
f
irebasehosting
• Hosting settings
f
irebasehosting
• Login results
f
irebasehosting
• Select hosting …
f
irebasehosting
• Public directory : dist/spa
f
irebasehosting
•
fi
rebase serve


• quasar build


•
fi
rebase deploy
f
irebasehosting
• Deploy results…
Favicon
• https://favicon.io/favicon-generator/
Menu
• components hierarchy


⎯ MainLayout.vue


EssentialLink.vue
props and emit
icon title


label
link
+
<template>


<q-item clickable tag="a" target="_blank" :href="link">


<q-item-section v-if="icon" avatar>


<q-icon :name="icon" />


</q-item-section>


<q-item-section>


<q-item-label>{{ title }}</q-item-label>


<q-item-label caption> {{ caption }} </q-item-label>


</q-item-section>


</q-item>


</template>


<script>


import { defineComponent } from 'vue'


export default defineComponent({


name: 'EssentialLink',


props: {


title: {


type: String,


required: true


},


caption: {


type: String,


default: ''


},


link: {


type: String,


default: '#'


},


icon: {


type: String,


default: ''


}


}


})


</script>
props : hey, parent component! control me
Menu
• components hierarchy


⎯ MainLayout.vue


EssentialLink.vue
props and emit
<EssentialLink v-for="link in essentialLinks" :key="link.title" v-bind="link" />


…


import EssentialLink from 'components/EssentialLink.vue'


const linksList = [


{


title: 'Docs',


caption: 'quasar.dev',


icon: 'school',


link: 'https://quasar.dev'


},


…


export default defineComponent({


name: 'MainLayout',


components: {


EssentialLink


},




setup () {


return {


essentialLinks: linksList,


}


}


…
parent component(MainLayout) now can set child props
Menu
• <Practice>


- open internal links in a current window


- open external links in a new tab (or window)
props and emit
Menu
• <Practice> child component(EssentialLink) modi
fi
cation


props





template
props and emit
external: {


type: Boolean,


default: false


}
<template>





<section v-if="external">


<q-item clickable tag="a" target="_target" :href="link">


…


</q-item>


</section>


<section v-else>


<router-link :to="link" style="text-decoration: none; color: inherit;”>


…


</router-link>


</section>


</template>
Menu
• <Practice> parent component(MainLayout) modi
fi
cation


props and emit
const linksList = [


{


title: 'Home',


caption: 'Home',


icon: 'home',


link: '/home'


},


{


title: 'QR Scanner',


caption: 'QR Check for Admin',


icon: 'qr_code_scanner',


link: 'https://kopologinchecker.web.app/#/qrscanner',


external: true


},


…
Menu
• <Practice>


- close menu when a external link is clicked
props and emit
emit : hey, parent component! handle this
Menu
• <Practice> child component(EssentialLink)
modi
fi
cation


template





methods
props and emit
<q-item clickable tag="a" target="_target" :href="link" @click="sendEvent()">
methods: {


sendEvent() {


this.$emit('closeDrawer');


console.log("emit emit")


}


},
• <Practice> parent component(MainLayout)
modi
fi
cation


template





methods
<EssentialLink v-on:closeDrawer="closeme()"


v-for="link in essentialLinks" :key="link.title" v-bind="link" />
methods: {


closeme() {


console.log('event received');


this.leftDrawerOpen = false;


}


},
q-table
• https://quasar.dev/vue-components/table
Homework
• save user login timestamps to
fi
restore


• display user login info (w/ timestamp)
build your own table
q-table
• save user data to Firestore


var cdate = Date.now()


// firestore insert => then we can get doc id to generate qr code


db.collection("qrgen").add({


date: cdate,


id: this.getFireUser.email,


status: this.sliderValue //this.group


})


.then((docRef) => {


// console.log("Document written with ID: ", docRef.id);




})


.catch((error) => {


console.error("Error adding document: ", error);


});
q-table
• basic layout
<section v-if="!onloading">


<q-table :title="date" :rows="rows" :columns="columns" row-key="no" v-model:pagination="pagination" :filter="filter">


</q-table>


</section>
‘onloading’ set to be false when data is completely loaded data() {


return {


pagination: {


sortBy: 'time',


descending: false,


rowsPerPage: 15, // current rows per page being displayed




},


date: '2019/02/01',


filter : '',


columns : [


{


name: 'no',


required: true,


label: 'SID',


align: 'left',


field: row => row.no,


format: val => `${val}`,


sortable: true


},


{ name: 'name', align: 'left', label: 'Name', field: 'name', sortable: true },


{ name: 'time', align: 'left', label: 'Check-In Time', field: 'time', sortable: true },


{ name: 'temperature', label: 'Temperature', field: 'temperature', sortable: true }


],


rows : [ ],


cnt : 1,


onloading : true


}


},
q-table
data load
dataLoad: function () {


this.cnt = 1


var now = new Date();


var startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());


var timestamp = startOfDay / 1;


db.collection("checkin").where("time", ">=", timestamp) // 오늘 체크인만 확인


.onSnapshot((snapshot) => {


snapshot.docChanges().forEach((change) => {


if (change.type === "added") {


var tempRow = {name: '', time:'', temperature:''}


tempRow.name = change.doc.data().name


const getTime = (timeStamp, offset) => {


let d = new Date((timeStamp + offset));


var hour = d.getHours()


var min = d.getMinutes()


var sec = d.getSeconds()


if (hour < 10) { hour = "0" + hour; }


if (min < 10) { min = "0" + min; }


if (sec < 10) { sec = "0" + sec; }


let ret = (d.getMonth()+1)+ "/"+(d.getDate())+ " "+hour+ ":"+min+":"+sec


return ret


}


tempRow.time = getTime(change.doc.data().time,0)


tempRow.no = this.cnt


this.cnt++


tempRow.temperature = change.doc.data().temperature


this.rows.push(tempRow)


}


});


});


this.onloading = false


} // end of dataLoad
ref : https://
fi
rebase.google.com/docs/
fi
restore/query-data/listen?hl=ko
q-table
• additional function - search
<section v-if="!onloading">


<q-table :title="date" :rows="rows" :columns="columns" row-key="no" :pagination.sync="pagination" :filter="filter">


<template v-slot:top-right>


<q-input borderless dense debounce="300" v-model="filter" placeholder="Search">


<template v-slot:append>


<q-icon name="search"></q-icon>


</template>


</q-input>


</template>


</q-table>


</section>
q-table
• additional function - date picker
<template v-slot:top-left>


<div class="q-pa-md" style="width: 150px; margin-top: 20px">


<q-input borderless dense debounce="300" v-model="date" mask="date" :rules="['date']">


<template v-slot:append>


<q-icon name="event" class="cursor-pointer">


<q-popup-proxy ref="qDateProxy" transition-show="scale" transition-hide="scale">


<q-date v-model="date">


<div class="row items-center justify-end">


<q-btn v-close-popup label="Close" color="primary" flat @click="datePicked()"></q-btn>


</div>


</q-date>


</q-popup-proxy>


</q-icon>


</template>


</q-input>


</div>


</template>


should be coded between <q-table> and </q-table>
q-table
• additional function


- date picker
datePicked(){


this.cnt = 1


this.rows = []


var yyyyMMdd = String(this.date);


var sYear = yyyyMMdd.substring(0,4);


var sMonth = yyyyMMdd.substring(5,7);


var sDate = yyyyMMdd.substring(8,10);


var startOfDay = new Date(Number(sYear), Number(sMonth)-1, Number(sDate));


var timestamp = startOfDay / 1;


var timestampp1d = timestamp + 86400000


db.collection("users").where("date", ">=", timestamp).where("date", "<", timestampp1d)


.get()


.then((querySnapshot) => {


querySnapshot.forEach((doc) => {


var tempRow = {name: '', time:'', temperature:''}


tempRow.name = doc.data().email


const getTime = (timeStamp, offset) => {


let d = new Date((timeStamp + offset));


console.log(d)


var hour = d.getHours()


var min = d.getMinutes()


var sec = d.getSeconds()


if (hour < 10) { hour = "0" + hour; }


if (min < 10) { min = "0" + min; }


if (sec < 10) { sec = "0" + sec; }


let ret = (d.getMonth()+1)+ "/"+(d.getDate())+" "+hour+ ":"+min+":"+sec


return ret


}


tempRow.time = getTime(doc.data().date,0)


tempRow.no = this.cnt


this.cnt++


tempRow.name = doc.data().name


this.rows.push(tempRow)


});


})


.catch((error) => {


console.log("Error getting documents: ", error);


});


}
q-table
• additional function - csv export
<div align="right">


<q-btn


color="primary"


icon-right="archive"


label="Export to csv"


no-caps


@click="exportTable"


>


</q-btn>


</div>
exportTable () {


var columns = this.columns


var rows = this.rows


// naive encoding to csv format


const content = [columns.map(col => this.wrapCsvValue(col.label))].concat(


rows.map(row => columns.map(col => this.wrapCsvValue(


typeof col.field === 'function'


? col.field(row)


: row[ col.field === void 0 ? col.name : col.field ],


col.format


)).join(','))


).join('rn')


const status = exportFile(


'table-export.csv',


content,


'text/csv'


)


if (status !== true) {


$q.notify({


message: 'Browser denied file download...',


color: 'negative',


icon: 'warning'


})


}


},
q-table
• additional function - csv export
wrapCsvValue (val, formatFn) {


let formatted = formatFn !== void 0


? formatFn(val)


: val


formatted = formatted === void 0 || formatted === null


? ''


: String(formatted)


formatted = formatted.split('"').join('""')


/**


* Excel accepts n and r in strings, but some other CSV parsers do not


* Uncomment the next two lines to escape new lines


*/


// .split('n').join('n')


// .split('r').join('r')


return `"${formatted}"`


},


import { exportFile } from 'quasar'
FirebaseSecurityRules
• https://khreniak.medium.com/cloud-
fi
restore-security-rules-basics-fac6b6bea18e
makeyourprivacypolicy
• https://www.privacy.go.kr/a3sc/per/inf/perInfStep01.do
checklicenses
• https://www.npmjs.com/package/license-checker
OSS(Open Source Software)
npm install -g license-checker


license-checker --csv --out ./licenses.csv
androidstudio
• install


• license package download after installation


https://devvkkid.tistory.com/204


• build


• run (w/ AVD manager)
cordova
• npm install -g cordova


• quasar dev -m android --ide


android studio error might be occurred :


“Installed Build Tools revision 31.0.0 is corrupted. Remove and install again using the SDK Manager.”


if you get above error, then you should modify version settings in your build.grade as below image











then type below command on your terminal (https://stackover
fl
ow.com/questions/68387270/android-studio-error-installed-build-tools-revision-31-0-0-is-corrupted)


cd ~/Library/Android/sdk/build-tools/31.0.0 && mv d8 dx && cd lib && mv d8.jar dx.jar


then close android studio and re-run “quasar dev -m android —ide"
cordova
• result
cordova
• quasar build -m cordova -T android


• brew install gradle
if you get a net error on simulator, then check you port in quasar.conf.js
cordova
• https://bottlecok.tistory.com/177
aab build
cordova
• quasar dev -m cordova -T ios


• quasar build -m cordova -T ios


• https://www.youtube.com/watch?v=P_Ox5s4anCI
ios
Vue3 tutorial
extra
BasicStructure
• setup is called between “before created” and “created”


• you can set a style whatever you want
step 1
<template>


<div class="test">


Hello World, {{ name }}


</div>


</template>


<script>


import { defineComponent } from 'vue';


export default defineComponent({


name: 'PageIndex',


setup() {


const name = "KOPO"


return {


name


}


}


})


</script>


<style lang="scss">


.test {


color: red;


background-color: yellow;


text-align: center;


font-size: 30px;




}


</style>
BasicStructure
• add function “printName”
add a function
<template>


<div class="test">


Hello World, {{ name }}


<p></p>


{{ printName() }}


</div>


</template>


<script>


import { defineComponent } from 'vue';


export default defineComponent({


name: 'PageIndex',


setup() {


const name = "KOPO"


const printName = () => {


return name + "!!"


}


return {


name,


printName


}


}


})


</script>
BasicStructure
• add a parameter : param


• call the function with parameter
add a parameter
<template>


<div class="test">


Hello World, {{ name }}


<p></p>


{{ printName(name) }}


</div>


</template>


<script>


import { defineComponent } from 'vue';


export default defineComponent({


name: 'PageIndex',


setup() {


const name = "KOPO"


const printName = (param) => {


return 'hello '+ param + "!!"


}


return {


name,


printName


}


}


})


</script>
BasicStructure
• we want to change name string when a button is clicked.


but, the result is …


• tip> button width dynamically can be set.


use $ref !
add a button
<template>


<div class="test" ref="topitem">


Hello World, {{ name }}


<p></p>


{{ printName(name) }}


</div>


<q-btn


@click="changeName"


class="button is-primary"


color="primary"

label="BUTTON"


:style="{width: itemWidth+'px'}" />


</template>


<script>


import { defineComponent, ref } from 'vue';


export default defineComponent({


name: 'PageIndex',


mounted() {


this.itemWidth = this.$refs.topitem.clientWidth


console.log(this.itemWidth)


},


setup() {


let name = "KOPO"


let itemWidth = ref(0)


const printName = (param) => {


return 'hello '+ param + "!!"


}


const changeName = () => {


name = "Button Clicked"


console.log("name chaged? ", name)


}


return {


name,


itemWidth,


printName,


changeName


}


}


})


</script>
<style lang="scss">


.test {


color: red;


background-color: yellow;


text-align: center;


font-size: 30px;


width: 300px;


}


</style>
BasicStructure
• “KOPO” —> ref(“KOPO”)


• name —> name.value
use ref to make the variable to be reactive
setup() {


let name = ref("KOPO")


let itemWidth = ref(0)


const printName = (param) => {


return 'hello '+ param + "!!"


}


const changeName = () => {


name.value = "Button Clicked"


console.log("name chaged? ", name)


}


if you use object or array data, use reactive(“~”) instead ref(“~”)

More Related Content

App development with quasar (pdf)

  • 1. Dept. of Smart Finance, Korea Polytechnics Seoul Kangseo Campus ApplicationDevelopmentw/ QuasarFramework Basic Course
  • 2. QuasarCLIInstallation • Prerequisite 
 Node >=12.22.1 and NPM >=6.14.12 • recommendation node version : v14.17.5 
 check your node and npm : node -v 
 npm -v • npm install -g @quasar/cli Do not use uneven versions of Node i.e. 13, 15, etc. These versions are not tested with Quasar and often cause issues due to their experimental nature. We highly recommend always using the LTS version of Node. Ref : https://quasar.dev/quasar-cli/installation
  • 3. HelloworldⅠ(1/2) •  we create a project folder with Quasar CLI • quasar create <folder_name> Ref : https://quasar.dev/quasar-cli/installation (base) waynehwang@wayneui-MacBookPro temp % quasar create hello ___ / _ _ _ __ _ ___ __ _ _ __ | | | | | | |/ _` / __|/ _` | '__| | |_| | |_| | (_| __ (_| | | _____,_|__,_|___/__,_|_| ? Project name (internal usage for dev) hello ? Project product name (must start with letter if building mobile apps) hello ? Project description hello world ? Author wayne hwang <wonyong@wonyong.net> ? Pick your CSS preprocessor: SCSS ? Check the features needed for your project: ESLint (recommended), Vuex, Axios ? Pick an ESLint preset: Prettier ? Continue to install project dependencies after the project has been created? ( recommended) NPM Quasar CLI · Generated "hello". [*] Installing project dependencies … [*] Quasar Project initialization finished! To get started: cd hello quasar dev
  • 4. Helloworld Ⅰ(2/2) • Look into package.json, App.vue, routes.js, Index.vue, EssentialLink.vue, … • Run your app with below command: 
 quasar dev
  • 5. HelloWorldⅡ • Repository address : 
 https://github.com/wonyongHwang/kopoPro fi le • git clone https://github.com/wonyongHwang/kopoPro fi le • npm install • quasar dev kopoProfile
  • 6. HelloWorldⅡ • In this practice, we don’t have a 
 drawer and a footer either. So, only 
 <q-header> is being a ff ected by 
 <q-layout>. • layout <q-layout view="lHh lpr lFf"> reference : https://quasar.dev/layout/layout#understanding-the-view-prop
  • 7. HelloWorldⅡ • quasar can detect platform : $q.platfrom.is.~~ 
 
 eg> if you will deploy app to mobile device(android, 
 ios) app can detect platform using $q.platform.is. 
 cordova $q.platform <div v-bind:class="{'main': $q.platform.is.desktop, 'mobile_main': $q.platform.is.ipad || $q.platform.is.mobile}" class="full-height" style="background-color:rgba(0, 0, 0, 0.7);"> reference : https://quasar.dev/options/platform-detection#introduction
  • 8. HelloWorldⅡ style <q-header class="q-py-sm" style="background-color:rgba(0, 0, 0, 0.7);"> reference : https://quasar.dev/style/spacing#table-of-permutations
  • 9. HelloWorldⅡ q-tab reference : https://quasar.dev/vue-components/tabs <q-tabs v-model="selected_tab" shrink> <q-tab name="about_me" :style="[selected_tab == 'about_me' ? {backgroundColor: 'green'} : {}]" class="q-mr-sm q-py-xs custom_tab" @click="scrollToElement('id_about_me')" style="width:120px;min-height:auto !important;color: white" label="About Me"/>
  • 10. HelloWorldⅡ q-card <q-card class="my-card" flat bordered> <q-card-section> reference : https://quasar.dev/vue-components/card#introduction
  • 11. HelloWorldⅡ • Get/set scroll position (Quasar Scrolling Utils) 
 https://quasar.dev/quasar-utils/scrolling-utils#get-set-scroll-position <script></script> import {scroll} from 'quasar' const {getScrollTarget, setScrollPosition} = scroll export default { name: 'PageIndex', data() { return { name: "", email: "", message: "", selected_tab: 'about_me', } }, methods: { scrollToElement(id) { let el = document.getElementById(id) const target = getScrollTarget(el) const o ff set = el.o ff setTop - 65 const duration = 800 setScrollPosition(target, o ff set, duration) } }, mounted () { } }
  • 12. homework • customizing web page • upload source to your github repo. 
 - README.md should be edited ex>
  • 13. homework • deploy your web pages via netlify 
 https://app.netlify.com/
  • 14. homework • check url • if you have a own domain, netlify allow you to access your web site • ‘https(let’s encrypt)’ supports both custom domain and netlify domain
  • 15. Make Your First Page Basic Practice
  • 16. Layout • quasar create <folder name> • quasar dev • fi rst page is served by Index.vue 

  • 17. Layout • Quasar provides basic layouts reference : https://quasar.dev/layout/gallery
  • 18. SignIn • what you need to implement a sign-in page: 
 2 Input Text fi eld 
 1 Check Box 
 1 Button 
 and more …
  • 19. SignIn • copy relevant codes and paste to your “Index.vue” 
 https://quasar.dev/vue-components/input
  • 20. SignUp • add Signup.Vue to your project • add page mapping on routes.js
  • 21. pagelink • vue syntax for page link : 
 <router-link to=“/address”> ~~ </router-link> <router-link to="/signup" > <center>New User? Click Here to Register.</center> </router-link>
  • 22. Homework • Place and Organize Input Text fi elds (E-mail, Password, etc …) 
 : center alignment • Password should be masked while typing • Place Sign-In / Sign-Up buttons complete your sign-in / sign-up pages!
  • 23. f irebase • https:// fi rebase.google.com/?hl=ko create a project // For Firebase JS SDK v7.20.0 and later, measurementId is optional const fi rebaseCon fi g = { apiKey: "AIzaSyA~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~", authDomain: "kopologinchecker. fi rebaseapp.com", projectId: "kopologinchecker", storageBucket: "kopologinchecker.appspot.com", messagingSenderId: "17~~~~~~~~~~~~", appId: "1:17~~~~~~~~:web:d4~~~~~~~~", measurementId: “G-H0~~~~~~~~~~~~" };
  • 24. • npm install --save fi rebase • quasar new boot fi rebase 
 -> fi rebase.js will be generated in src/boot 
 -> paste fi rebase con fi guration to fi rebase.js f irebase setting config. ref : https://quasar.dev/quasar-cli/boot- fi les import firebase from 'firebase/app'; import 'firebase/auth'; import 'firebase/analytics' const firebaseConfig = { apiKey: "~", authDomain: "kopologinchecker.firebaseapp.com", projectId: "kopologinchecker", storageBucket: "kopologinchecker.appspot.com", messagingSenderId: “~", appId: "~", measurementId: "~" }; if (!firebase.apps.length) { firebase.initializeApp(firebaseConfig); } firebase.analytics(); export const auth = firebase.auth();
  • 25. f irebase • enable email/password • create a test user if needed sign-in method
  • 26. f irebase • https:// fi rebase.google.com/docs/auth/web/start?hl=ko sign-in method fi rebase.auth().signInWithEmailAndPassword(email, password )   .then((userCredential) => {     // Signed i n     var user = userCredential.user ;     // .. .   } )   .catch((error) => {     var errorCode = error.code ;     var errorMessage = error.message ;   });
  • 27. • let’s use quasar notify to show success/fail message 
 https://quasar.dev/quasar-plugins/notify#introduction f irebase sign-in method auth.signInWithEmailAndPassword(this.text, this.password).then( (userCredential) => { console.log(userCredential.user); this.$router.push({ path: 'home' }) } ).catch( (err) => { console.log(err.code); console.log(err.message); this.$q.notify({ message: err.message, color: 'purple' }) } ) message, color, postion, textColor, icon, … import { useQuasar } from 'quasar' const $q = useQuasar() $q.notify({ message : "login success", color : "blue" }) vue 3
  • 28. homework • implement a sign-up function using fi rebase api • validation logic should be implemented 
 ex> email/password should not be empty 
 password should be equal to ‘repeat password’ 
 password must be at least 6 characters long 
 hint> https://levelup.gitconnected.com/ fi rebase-auth-management-in-vue-js-with-vuex-9c4a5d9cedc • move page after sign-up 
 hint> this.$router.push({ path: 'home' }) import { useRouter, useRoute } from 'vue-router' export default { setup() { const router = useRouter() const route = useRoute() // Now you can access params like : console.log(route.params.id) ; route.push({path: ‘home’} ) } }; vue v3 https://next.router.vuejs.org/guide/advanced/composition-api.html#accessing-the-router-and-current-route-inside-setup
  • 29. • Parent -> child : props • Child -> Parent : emit
  • 30. management f irebaseauthw/vuex • state management pattern and its library • state : data • mutations : change state (sync) • actions : commit mutation (async) what vuex is Actions State View Mutations this.$store.commit emit, props Unidirectional fl ow
  • 31. management f irebaseauthw/vuex • state = fi re auth info • ‘ fi re auth info’ can acquire when we call 
 signInWithEmailAndPassword() 
 createUserWithEmailAndPassword() 
 and so on. state Actions State View Mutations this.$store.commit import {auth} from "src/boot/firebase" export default store(function (/* { ssrContext } */) { const Store = createStore({ modules: { // example }, state: { fireUser:null }, … State
  • 32. management f irebaseauthw/vuex • get the current sign-in user is by setting an observer on the Auth object 
 https:// fi rebase.google.com/docs/auth/web/manage-users • state should be null when user signed out 
 https:// fi rebase.google.com/docs/auth/web/password-auth?hl=ko action actions: { signOutAction({commit}) { auth.signOut() .then(() => { commit("setFireUser", null); }) }, authAction({ commit }) { auth.onAuthStateChanged(user => { if (user) { commit("setFireUser", user); } }); }, }, mutations: { setFireUser(state, firebaseUser){ state.fireUser = firebaseUser } Actions Mutations
  • 33. management f irebaseauthw/vuex • prepare helper functions 
 https://joshua1988.github.io/web-development/vuejs/vuex-getters-mutations/ getters getters: { getFireUser(state) { return state.fireUser; }, isUserAuth(state) { return !!state.fireUser; } },
  • 34. management f irebaseauthw/vuex source code import { store } from 'quasar/wrappers' import { createStore } from 'vuex' import {auth} from "src/boot/firebase" export default store(function (/* { ssrContext } */) { const Store = createStore({ state: { fireUser:null }, actions: { signOutAction({commit}) { auth.signOut() .then(() => { commit("setFireUser", null); }) .catch(error => { commit("setFireError", error.message); }); }, authAction({ commit }) { auth.onAuthStateChanged(user => { if (user) { commit("setFireUser", user); } else { commit("setFireUser", null); } }); }, }, getters: { getFireUser(state) { return state.fireUser; }, isUserAuth(state) { return !!state.fireUser; } }, mutations: { setFireUser(state, firebaseUser){ state.fireUser = firebaseUser } }, }) return Store })
  • 35. management f irebaseauthw/vuex • commit state when my application obtain a fi rebase auth • get a fi rebase auth to display user info • check a fi rebase auth usage example auth.signInWithEmailAndPassword(this.text, this.password).then( (userCredential) => { this.$store.commit("setFireUser", userCredential.user); } ) <div class="text-h6" align="center"> Hi, {{ this.name }}({{ getFireUser.email }}) <br> … computed: { ...mapGetters(["getFireUser", "isUserAuth"]) }, mounted() { this.authAction(); … methods: { ...mapActions(["signOutAction","authAction"]), import { useStore } from "vuex" ; export default { setup() { const store = useStore() ; store.commit("setFireUser", userCredential.user) ; } }; vue v3 https://kyounghwan01.github.io/blog/Vue/vue3/composition-api-vuex/#vuex-%E1%84%89%E1%85%A6%E1%84%90%E1%85%B5%E1%86%BC-%E1%84%86%E1%85%B5%E1%86%BE-store-module-1%E1%84%80%E1%85%A2%E1%84%85%E1%85%A9-%E1%84%89%E1%85%B5%E1%86%AF%E1%84%92%E1%85%A2%E1%86%BC
  • 36. homework • When a user logged in, move Vue Page and display user’s email using vuex • tip> 
 <div v-if=“getFireUser">~~</div> 
 computed: { ...mapGetters(["getFireUser", "isUserAuth"]) }, <div class="text-h6" align="center"> Hi,({{ getFireUser.email }}) </div> import { mapGetters } from "vuex";
  • 37. vuelifecycle • https://cornswrold.tistory.com/342 • beforeCreate, created, beforeMount, mounted, 
 beforeUpdate, updated, beforeDestroy, destroyed
  • 40. Firestore • Firestore data add: set() or add() 
 https:// fi rebase.google.com/docs/ fi restore/manage-data/add-data?hl=ko API db.collection("users").add({ id: this.text, name: this.name }) .then((docRef) => { console.log("Document written with ID: ", docRef.id); this.$q.notify({ message: "Register Success", color: 'blue' }) }) .catch((error) => { console.error("Error adding document: ", error); this.$q.notify({ message: error, color: 'red' }) });
  • 41. Firestore • fi rebase.js 
 import ' fi rebase/ fi restore' 
 export const db = fi rebase. fi restore(); • import 
 import { db } from "src/boot/ fi rebase" Preparations
  • 42. Firestore Data write db.collection("users").add({ id: this.text, name: this.name }) .then((docRef) => { console.log("Document written with ID: ", docRef.id); }) .catch((error) => { console.error("Error adding document: ", error); });
  • 43. Firestore Data read db.collection("users").where("id", "==", this.getFireUser.email ) .get() .then((querySnapshot) => { querySnapshot.forEach((doc) => { // doc.data() is never undefined for query doc snapshots console.log(doc.id, " => ", doc.data()); this.name = doc.data().name }); }) .catch((error) => { console.log("Error getting documents: ", error); });
  • 44. Homework • When a user logged in, move page(/home) and display user’s name using vuex 
 - user name was saved at Firestore, so we have to read the data fi rst to show it. • user name should be displayed on page(/home) even after refreshing page.
  • 45. Homework Hint <template> <div> HOME <div class="text-h6" align="center"> <div v-if="getFireUser"> Hi,({{ getFireUser.email }}) :) {{ name }} </div> </div> </div> </template> <script> import { defineComponent } from 'vue'; import { auth, db } from "src/boot/firebase" import { useQuasar } from 'quasar' import { mapGetters, mapActions } from "vuex"; // export default defineComponent({ name: 'PageIndex', computed: { ...mapGetters(["getFireUser", "isUserAuth"]) }, updated() { if(this.getFireUser != null && this.name == ''){ // for refresh page // db.collection("users").where("id", "==", this.getFireUser.email ) .get() .then((querySnapshot) => { querySnapshot.forEach((doc) => { // doc.data() is never undefined for query doc snapshots console.log(doc.id, " => ", doc.data()); this.name = doc.data().name }); }) .catch((error) => { console.log("Error getting documents: ", error); }); } }, mounted(){ // this.authAction() // for refresh page // if(this.getFireUser != null){ db.collection("users").where("id", "==", this.getFireUser.email ) .get() .then((querySnapshot) => { querySnapshot.forEach((doc) => { // doc.data() is never undefined for query doc snapshots console.log(doc.id, " => ", doc.data()); this.name = doc.data().name }); }) .catch((error) => { console.log("Error getting documents: ", error); }); } }, data(){ return{ name : '' } }, methods: { ...mapActions(["signOutAction","authAction"]) } }) </script>
  • 46. f irebaseusermgmt. • https:// fi rebase.google.com/docs/auth/web/manage-users?hl=ko • User Info management 
 - user info update 
 - user account delete Some security-sensitive actions—such as deleting an account, setting a primary email address, and changing a password—require that the user has recently signed in. 
  • 47. Implementationof“rememberme”usinglocalStorage • UI 
 <q-checkbox v-model="remember" label="Remember Me" color="teal"/> • Declaration 
 let remember = ref(‘false') • mounted() • When a user tries to log in: if (remember.value == true) { localStorage.username = email.value; localStorage.checkbox = remember.value; } else { localStorage.username = ""; localStorage.checkbox = ""; } mounted(){ if(localStorage.checkbox && localStorage.checkbox !==""){ this.remember = true this.email = localStorage.username } else { this.remember = false } }
  • 49. Homework • Elaborate your work(home/sign-in/sign-up) • Conceptualizing your idea
  • 61. Menu • components hierarchy 
 ⎯ MainLayout.vue 
 EssentialLink.vue props and emit icon title label link + <template> <q-item clickable tag="a" target="_blank" :href="link"> <q-item-section v-if="icon" avatar> <q-icon :name="icon" /> </q-item-section> <q-item-section> <q-item-label>{{ title }}</q-item-label> <q-item-label caption> {{ caption }} </q-item-label> </q-item-section> </q-item> </template> <script> import { defineComponent } from 'vue' export default defineComponent({ name: 'EssentialLink', props: { title: { type: String, required: true }, caption: { type: String, default: '' }, link: { type: String, default: '#' }, icon: { type: String, default: '' } } }) </script> props : hey, parent component! control me
  • 62. Menu • components hierarchy 
 ⎯ MainLayout.vue 
 EssentialLink.vue props and emit <EssentialLink v-for="link in essentialLinks" :key="link.title" v-bind="link" /> … import EssentialLink from 'components/EssentialLink.vue' const linksList = [ { title: 'Docs', caption: 'quasar.dev', icon: 'school', link: 'https://quasar.dev' }, … export default defineComponent({ name: 'MainLayout', components: { EssentialLink }, 
 
 setup () { return { essentialLinks: linksList, } } … parent component(MainLayout) now can set child props
  • 63. Menu • <Practice> 
 - open internal links in a current window 
 - open external links in a new tab (or window) props and emit
  • 64. Menu • <Practice> child component(EssentialLink) modi fi cation 
 props 
 

 template props and emit external: { type: Boolean, default: false } <template> <!-- <router-link :to="link" style="text-decoration: none"> --> <section v-if="external"> <q-item clickable tag="a" target="_target" :href="link"> … </q-item> </section> <section v-else> <router-link :to="link" style="text-decoration: none; color: inherit;”> … </router-link> </section> </template>
  • 65. Menu • <Practice> parent component(MainLayout) modi fi cation 
 props and emit const linksList = [ { title: 'Home', caption: 'Home', icon: 'home', link: '/home' }, { title: 'QR Scanner', caption: 'QR Check for Admin', icon: 'qr_code_scanner', link: 'https://kopologinchecker.web.app/#/qrscanner', 
 external: true }, …
  • 66. Menu • <Practice> 
 - close menu when a external link is clicked props and emit emit : hey, parent component! handle this
  • 67. Menu • <Practice> child component(EssentialLink) modi fi cation 
 template 
 

 methods props and emit <q-item clickable tag="a" target="_target" :href="link" @click="sendEvent()"> methods: { sendEvent() { this.$emit('closeDrawer'); console.log("emit emit") } }, • <Practice> parent component(MainLayout) modi fi cation 
 template 
 

 methods <EssentialLink v-on:closeDrawer="closeme()" 
 v-for="link in essentialLinks" :key="link.title" v-bind="link" /> methods: { closeme() { console.log('event received'); this.leftDrawerOpen = false; } },
  • 69. Homework • save user login timestamps to fi restore • display user login info (w/ timestamp) build your own table
  • 70. q-table • save user data to Firestore 
 var cdate = Date.now() // firestore insert => then we can get doc id to generate qr code db.collection("qrgen").add({ date: cdate, id: this.getFireUser.email, status: this.sliderValue //this.group }) .then((docRef) => { // console.log("Document written with ID: ", docRef.id); }) .catch((error) => { console.error("Error adding document: ", error); });
  • 71. q-table • basic layout <section v-if="!onloading"> <q-table :title="date" :rows="rows" :columns="columns" row-key="no" v-model:pagination="pagination" :filter="filter"> </q-table> </section> ‘onloading’ set to be false when data is completely loaded data() { return { pagination: { sortBy: 'time', descending: false, rowsPerPage: 15, // current rows per page being displayed }, date: '2019/02/01', filter : '', columns : [ { name: 'no', required: true, label: 'SID', align: 'left', field: row => row.no, format: val => `${val}`, sortable: true }, { name: 'name', align: 'left', label: 'Name', field: 'name', sortable: true }, { name: 'time', align: 'left', label: 'Check-In Time', field: 'time', sortable: true }, { name: 'temperature', label: 'Temperature', field: 'temperature', sortable: true } ], rows : [ ], cnt : 1, onloading : true } },
  • 72. q-table data load dataLoad: function () { this.cnt = 1 var now = new Date(); var startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate()); var timestamp = startOfDay / 1; db.collection("checkin").where("time", ">=", timestamp) // 오늘 체크인만 확인 .onSnapshot((snapshot) => { snapshot.docChanges().forEach((change) => { if (change.type === "added") { var tempRow = {name: '', time:'', temperature:''} tempRow.name = change.doc.data().name const getTime = (timeStamp, offset) => { let d = new Date((timeStamp + offset)); var hour = d.getHours() var min = d.getMinutes() var sec = d.getSeconds() if (hour < 10) { hour = "0" + hour; } if (min < 10) { min = "0" + min; } if (sec < 10) { sec = "0" + sec; } let ret = (d.getMonth()+1)+ "/"+(d.getDate())+ " "+hour+ ":"+min+":"+sec return ret } tempRow.time = getTime(change.doc.data().time,0) tempRow.no = this.cnt this.cnt++ tempRow.temperature = change.doc.data().temperature this.rows.push(tempRow) } }); }); this.onloading = false } // end of dataLoad ref : https:// fi rebase.google.com/docs/ fi restore/query-data/listen?hl=ko
  • 73. q-table • additional function - search <section v-if="!onloading"> <q-table :title="date" :rows="rows" :columns="columns" row-key="no" :pagination.sync="pagination" :filter="filter"> <template v-slot:top-right> <q-input borderless dense debounce="300" v-model="filter" placeholder="Search"> <template v-slot:append> <q-icon name="search"></q-icon> </template> </q-input> </template> </q-table> </section>
  • 74. q-table • additional function - date picker <template v-slot:top-left> <div class="q-pa-md" style="width: 150px; margin-top: 20px"> <q-input borderless dense debounce="300" v-model="date" mask="date" :rules="['date']"> <template v-slot:append> <q-icon name="event" class="cursor-pointer"> <q-popup-proxy ref="qDateProxy" transition-show="scale" transition-hide="scale"> <q-date v-model="date"> <div class="row items-center justify-end"> <q-btn v-close-popup label="Close" color="primary" flat @click="datePicked()"></q-btn> </div> </q-date> </q-popup-proxy> </q-icon> </template> </q-input> </div> </template> should be coded between <q-table> and </q-table>
  • 75. q-table • additional function 
 - date picker datePicked(){ this.cnt = 1 this.rows = [] var yyyyMMdd = String(this.date); var sYear = yyyyMMdd.substring(0,4); var sMonth = yyyyMMdd.substring(5,7); var sDate = yyyyMMdd.substring(8,10); var startOfDay = new Date(Number(sYear), Number(sMonth)-1, Number(sDate)); var timestamp = startOfDay / 1; var timestampp1d = timestamp + 86400000 db.collection("users").where("date", ">=", timestamp).where("date", "<", timestampp1d) .get() .then((querySnapshot) => { querySnapshot.forEach((doc) => { var tempRow = {name: '', time:'', temperature:''} tempRow.name = doc.data().email const getTime = (timeStamp, offset) => { let d = new Date((timeStamp + offset)); console.log(d) var hour = d.getHours() var min = d.getMinutes() var sec = d.getSeconds() if (hour < 10) { hour = "0" + hour; } if (min < 10) { min = "0" + min; } if (sec < 10) { sec = "0" + sec; } let ret = (d.getMonth()+1)+ "/"+(d.getDate())+" "+hour+ ":"+min+":"+sec return ret } tempRow.time = getTime(doc.data().date,0) tempRow.no = this.cnt this.cnt++ tempRow.name = doc.data().name this.rows.push(tempRow) }); }) .catch((error) => { console.log("Error getting documents: ", error); }); }
  • 76. q-table • additional function - csv export <div align="right"> <q-btn color="primary" icon-right="archive" label="Export to csv" no-caps @click="exportTable" > 
 </q-btn> </div> exportTable () { var columns = this.columns var rows = this.rows // naive encoding to csv format const content = [columns.map(col => this.wrapCsvValue(col.label))].concat( rows.map(row => columns.map(col => this.wrapCsvValue( typeof col.field === 'function' ? col.field(row) : row[ col.field === void 0 ? col.name : col.field ], col.format )).join(',')) ).join('rn') const status = exportFile( 'table-export.csv', content, 'text/csv' ) if (status !== true) { $q.notify({ message: 'Browser denied file download...', color: 'negative', icon: 'warning' }) } },
  • 77. q-table • additional function - csv export wrapCsvValue (val, formatFn) { let formatted = formatFn !== void 0 ? formatFn(val) : val formatted = formatted === void 0 || formatted === null ? '' : String(formatted) formatted = formatted.split('"').join('""') /** * Excel accepts n and r in strings, but some other CSV parsers do not * Uncomment the next two lines to escape new lines */ // .split('n').join('n') // .split('r').join('r') return `"${formatted}"` }, import { exportFile } from 'quasar'
  • 80. checklicenses • https://www.npmjs.com/package/license-checker OSS(Open Source Software) npm install -g license-checker license-checker --csv --out ./licenses.csv
  • 81. androidstudio • install • license package download after installation 
 https://devvkkid.tistory.com/204 • build • run (w/ AVD manager)
  • 82. cordova • npm install -g cordova • quasar dev -m android --ide 
 android studio error might be occurred : 
 “Installed Build Tools revision 31.0.0 is corrupted. Remove and install again using the SDK Manager.” 
 if you get above error, then you should modify version settings in your build.grade as below image 
 







 then type below command on your terminal (https://stackover fl ow.com/questions/68387270/android-studio-error-installed-build-tools-revision-31-0-0-is-corrupted) 
 cd ~/Library/Android/sdk/build-tools/31.0.0 && mv d8 dx && cd lib && mv d8.jar dx.jar 
 then close android studio and re-run “quasar dev -m android —ide"
  • 84. cordova • quasar build -m cordova -T android • brew install gradle if you get a net error on simulator, then check you port in quasar.conf.js
  • 86. cordova • quasar dev -m cordova -T ios • quasar build -m cordova -T ios • https://www.youtube.com/watch?v=P_Ox5s4anCI ios
  • 88. BasicStructure • setup is called between “before created” and “created” • you can set a style whatever you want step 1 <template> <div class="test"> Hello World, {{ name }} </div> </template> <script> import { defineComponent } from 'vue'; export default defineComponent({ name: 'PageIndex', setup() { const name = "KOPO" return { name } } }) </script> <style lang="scss"> .test { color: red; background-color: yellow; text-align: center; font-size: 30px; } </style>
  • 89. BasicStructure • add function “printName” add a function <template> <div class="test"> Hello World, {{ name }} <p></p> {{ printName() }} </div> </template> <script> import { defineComponent } from 'vue'; export default defineComponent({ name: 'PageIndex', setup() { const name = "KOPO" const printName = () => { return name + "!!" } return { name, printName } } }) </script>
  • 90. BasicStructure • add a parameter : param • call the function with parameter add a parameter <template> <div class="test"> Hello World, {{ name }} <p></p> {{ printName(name) }} </div> </template> <script> import { defineComponent } from 'vue'; export default defineComponent({ name: 'PageIndex', setup() { const name = "KOPO" const printName = (param) => { return 'hello '+ param + "!!" } return { name, printName } } }) </script>
  • 91. BasicStructure • we want to change name string when a button is clicked. 
 but, the result is … • tip> button width dynamically can be set. 
 use $ref ! add a button <template> <div class="test" ref="topitem"> Hello World, {{ name }} <p></p> {{ printName(name) }} </div> <q-btn @click="changeName" class="button is-primary" color="primary" label="BUTTON" :style="{width: itemWidth+'px'}" /> </template> <script> import { defineComponent, ref } from 'vue'; export default defineComponent({ name: 'PageIndex', mounted() { this.itemWidth = this.$refs.topitem.clientWidth console.log(this.itemWidth) }, setup() { let name = "KOPO" let itemWidth = ref(0) const printName = (param) => { return 'hello '+ param + "!!" } const changeName = () => { name = "Button Clicked" console.log("name chaged? ", name) } return { name, itemWidth, printName, changeName } } }) </script> <style lang="scss"> .test { color: red; background-color: yellow; text-align: center; font-size: 30px; width: 300px; } </style>
  • 92. BasicStructure • “KOPO” —> ref(“KOPO”) • name —> name.value use ref to make the variable to be reactive setup() { let name = ref("KOPO") let itemWidth = ref(0) const printName = (param) => { return 'hello '+ param + "!!" } const changeName = () => { name.value = "Button Clicked" console.log("name chaged? ", name) } if you use object or array data, use reactive(“~”) instead ref(“~”)