Membuat Tanda Tangan Pada Form Input Dengan Apps Script (Signature Unlimited)
*Untuk Mendapatkan Full Script tanpa password silahkan Klik Disini*
1. Silahkan copy Spreadsheet (Klik Disini)
2. Pada file Spreadsheet yang sudah di copy, terdapat 1 sheet yang bernama Responses, dan ada beberapa tabel/kolom yang akan otomatis terisi apabila kita berhasil menginputkan data dari form.
3. Buatlah lembar kerja Apps Script, dengan cara klik menu Ekstensi/Extensions lalu pilih Apps Script.
4. Pada lembar kerja Apps Script terdapat 3 file yaitu
- backend.gs
- index.html
- vuejs.html
5. Copy dan pastekan script di bawah ini ke beckend,gs
Masukkan Password Untuk Melihat Script (Password ada di dalam video)
const SETTINGS = {
APP_NAME: "JP34 Input Data Dengan Tanda Tangan Dengan Apps Script",
SHEET_NAME: {
RESPONSES: "Responses"
},
HEADERS: [
{ key: "timestamp", value: "Timestamp" },
{ key: "id", value: "ID" },
{ key: "name", value: "Nama" },
{ key: "email", value: "Email" },
{ key: "phone", value: "No Telp" },
{ key: "gender", value: "Jenis Kelamin" },
{ key: "city", value: "Alamat" },
{ key: "date", value: "Tanggal" },
{ key: "signature", value: "Tanda Tangan" },
]
}
function link(filename) {
return HtmlService.createTemplateFromFile(filename).evaluate().getContent()
}
function doGet() {
return HtmlService.createTemplateFromFile("index.html")
.evaluate()
.setTitle(SETTINGS.APP_NAME)
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
}
function submit(data) {
data = JSON.parse(data)
const headers = SETTINGS.HEADERS.map(({value}) => value)
const id = Utilities.getUuid()
const signatures = []
const values = SETTINGS.HEADERS.map(({key}, index) => {
if (key === "id") return id
if (key === "timestamp") return new Date()
if (!key in data) return null
if (Array.isArray(data[key])) return data[key].join(",")
if (data[key].startsWith("data:image")) {
signatures.push(index)
return SpreadsheetApp.newCellImage().setSourceUrl(data[key]).build().toBuilder()
}
return data[key]
})
const ws = SpreadsheetApp.getActive().getSheetByName(SETTINGS.SHEET_NAME.RESPONSES) || SpreadsheetApp.getActive().insertSheet(SETTINGS.SHEET_NAME.RESPONSES)
ws.getRange(1,1, 1, headers.length).setValues([headers])
const lastRow = ws.getLastRow()
ws.getRange(lastRow + 1, 1, 1, values.length).setValues([values])
signatures.forEach(index => {
ws.getRange(lastRow + 1, index + 1).setValue(values[index])
})
return JSON.stringify({success: true, message: `Terima Kasih, Data berhasil di input dengan ! ID: ${id}`})
}
const SETTINGS = { APP_NAME: "JP34 Input Data Dengan Tanda Tangan Dengan Apps Script", SHEET_NAME: { RESPONSES: "Responses" }, HEADERS: [ { key: "timestamp", value: "Timestamp" }, { key: "id", value: "ID" }, { key: "name", value: "Nama" }, { key: "email", value: "Email" }, { key: "phone", value: "No Telp" }, { key: "gender", value: "Jenis Kelamin" }, { key: "city", value: "Alamat" }, { key: "date", value: "Tanggal" }, { key: "signature", value: "Tanda Tangan" }, ] } function link(filename) { return HtmlService.createTemplateFromFile(filename).evaluate().getContent() } function doGet() { return HtmlService.createTemplateFromFile("index.html") .evaluate() .setTitle(SETTINGS.APP_NAME) .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL) } function submit(data) { data = JSON.parse(data) const headers = SETTINGS.HEADERS.map(({value}) => value) const id = Utilities.getUuid() const signatures = [] const values = SETTINGS.HEADERS.map(({key}, index) => { if (key === "id") return id if (key === "timestamp") return new Date() if (!key in data) return null if (Array.isArray(data[key])) return data[key].join(",") if (data[key].startsWith("data:image")) { signatures.push(index) return SpreadsheetApp.newCellImage().setSourceUrl(data[key]).build().toBuilder() } return data[key] }) const ws = SpreadsheetApp.getActive().getSheetByName(SETTINGS.SHEET_NAME.RESPONSES) || SpreadsheetApp.getActive().insertSheet(SETTINGS.SHEET_NAME.RESPONSES) ws.getRange(1,1, 1, headers.length).setValues([headers]) const lastRow = ws.getLastRow() ws.getRange(lastRow + 1, 1, 1, values.length).setValues([values]) signatures.forEach(index => { ws.getRange(lastRow + 1, index + 1).setValue(values[index]) }) return JSON.stringify({success: true, message: `Terima Kasih, Data berhasil di input dengan ! ID: ${id}`}) }
6. Copy dan pastekan script di bawah ini ke index.html
<!DOCTYPE html> <html> <head> <base target="_top"> <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/@mdi/font@6.x/css/materialdesignicons.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui"> </head> <body> <div id="app"> <v-app> <v-main> <v-container> <v-form ref="form" @submit.prevent="submit" :disabled="loading"> <v-card :loading="loading" outlined> <v-img class="white--text align-end" height="200px" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8n4NyX5mHYCdP4VSahYIo_3Bq9YSsmn4oO_ZoezeY4VYWYkMMIz0ynW0bEe4Hdc_wexBznUzDSvfM6fLTXjU__UrLXzLcLkfs9BDYQKs5Ivv5WlKytTxEmJbMWKIGf1-pf5waejuaXpZltwcUc4Ml3TmJA0GHSUlyiz1-z96g7t1FK4IPS5bR0o63/s16000/asasa.jpg"> <br> </br> <marquee><v-card-subtitle v-if="subtitle" style="color:black;">{{ subtitle }}</v-card-subtitle></marquee> </v-img> <v-card-text> <v-row> <v-col cols="12" sm="12" md="6" lg="4" xl="3"> <my-input :item="form.name"></my-input> </v-col> <v-col cols="12" sm="12" md="6" lg="4" xl="3"> <my-input :item="form.email"></my-input> </v-col> <v-col cols="12" sm="12" md="6" lg="4" xl="3"> <my-input :item="form.phone"></my-input> </v-col> <v-col cols="12" sm="12" md="6" lg="4" xl="3"> <my-input :item="form.gender"></my-input> </v-col> <v-col cols="12" sm="12" md="6" lg="4" xl="3"> <my-input :item="form.city"></my-input> </v-col> <v-col cols="12" sm="12" md="6" lg="4" xl="3"> <my-input :item="form.date"></my-input> </v-col> <v-col cols="12" sm="12" md="6" lg="4" xl="3"> <my-input :item="form.signature"></my-input> </v-col> <v-col cols="12"> <v-btn type="submit" color="primary" :disabled="loading" large depressed>Submit</v-btn> </v-col> </v-row> </v-card-text> </v-form> <v-snackbar v-model="snackbar.show" :color="snackbar.color" bottom> {{ snackbar.message }} <template v-slot:action="{ attrs }"> <v-btn color="white" text v-bind="attrs" :timeout="snackbar.timeout" @click="snackbar.show = false"> <v-icon>mdi-close</v-icon> </v-btn> </template> </v-snackbar> </v-container> </v-main> </v-app> </div> <?!= link("vuejs.html"); ?> </body> <style>.footer,.generic-footer{margin-bottom:98px}@media (min-width:52px){.footer,.generic-footer{margin-bottom:78px}}@media (min-width:52px){.footer,.generic-footer{margin-bottom:56px}}@media (min-width:52px){.footer,.generic-footer{margin-bottom:0}}.disclaimer{position:fixed;z-index:9999999;bottom:0;right:0;border-top:2px solid #ff5c62;text-align:center;font-size:14px;font-weight:400;background-color:#fff;padding:5px 10px 5px 10px}.disclaimer a:hover{text-decoration:underline}@media (min-width:52px){.disclaimer{text-align:right;border-left:2px solid red;border-top-left-radius:10px}}@media (min-width:1920px){.disclaimer{width:20%}}</style><div class="disclaimer">Version.01.05.22 @Copyright <a title="https://www.javabitpro.com/" target="_blank" href="https://www.javabitpro.com/" style="color: black;"><b>www.javabitpro.com</b></a></div> </html>
7. Copy dan pastekan script di bawah ini ke vuejs.html
<script src="https://cdn.jsdelivr.net/npm/signature_pad@2.3.2/dist/signature_pad.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script> <script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script> <script> const apiCall = function (functionName, params = {}) { params = JSON.stringify(params); return new Promise((resolve, reject) => { google.script.run .withSuccessHandler((response) => resolve(JSON.parse(response))) .withFailureHandler((error) => reject(error)) [functionName](params); }); }; const getFormData = (form) => { const data = {}; Object.entries(form).forEach(([key, item]) => { data[key] = item.value; }); return data }; const form = { name: { label: "Nama", type: "text", value: "", disabled: false, placeholder: "", rules: [(v) => !!v || "Wajib di isi!"], }, email: { label: "Email", type: "email", value: "", disabled: false, placeholder: "", rules: [(v) => !!v || "Wajib di isi!"], }, phone: { label: "No Telp", type: "tel", value: "", disabled: false, placeholder: "", rules: [(v) => !!v || "Wajib di isi!"], }, gender: { label: "Jenis Kelamin", type: "select", value: "", items: ["Laki-Laki", "Perempuan"], disabled: false, placeholder: "", rules: [(v) => !!v || "Wajib di isi!"], }, city: { label: "Alamat", type: "select", value: "", items: ["Jember", "Banyuwangi", "Lumajang", "Surabaya", "Jakarta"], disabled: false, placeholder: "", rules: [(v) => !!v || "Wajib di isi!"], }, date: { label: "Tanggal", type: "date", value: "", disabled: false, placeholder: "", rules: [(v) => !!v || "Wajib di isi!"], }, signature: { label: "Tanda Tangan", type: "signature", value: "", disabled: false, placeholder: "Klik untuk membuka lembar tanda tangan", items: [], rules: [(v) => !!v || "Wajib di isi!"], }, }; const MySnackbar = Vue.component("my-snackbar", { template: ` `, props: { show: true, message: "", color: "", }, data: () => ({ snackbar: this.show, timeout: 5000, }) }) const MySignature = Vue.component("my-signature", { template: ` <div> <v-select v-model="item.value" :label="item.label" :placeholder="item.placeholder" :rules="item.rules" :type="item.type" :items="item.items" @click="openPad" small-chips filled ></v-select> <v-dialog v-model="dialog" width="400" eager > <v-card> <v-card-title class="text-h5 primary white--text"> Lembar {{item.label}} </v-card-title> <v-card-text class="pa-0"> <canvas :ref="item.label" width="400" height="140"/> </v-card-text> <v-divider></v-divider> <v-card-actions> <v-spacer></v-spacer> <v-btn color="primary" text @click="savePad" > Selesai </v-btn> <v-btn color="error" text @click="clearPad" > Hapus </v-btn> <v-btn color="grey" text @click="closePad" > Tutup </v-btn> </v-card-actions> </v-card> </v-dialog> </div> `, props: { item: Object, }, data: () => ({ dialog: false, show: false, signaturePad: null, }), methods: { openPad: function(){ this.dialog = true const label = this.item.label this.signaturePad = new SignaturePad(this.$refs[label]) if (this.item.value) this.signaturePad.fromDataURL(this.item.value) }, closePad: function(){ this.dialog = false }, clearPad(){ this.signaturePad.clear() }, savePad(){ if (this.signaturePad.isEmpty()) { this.item.value = null this.item.items = [] } else { this.item.value = this.signaturePad.toDataURL() this.item.items = [{ text: `Signed at ${new Date().toLocaleString()}`, value: this.item.value }] } this.signaturePad.clear() this.dialog = false } } }) const MyInput = Vue.component("my-input", { components: {MySignature}, template: ` <my-signature v-if="item.type === 'signature' ":item="item"></my-signature> <v-autocomplete v-else-if="item.type === 'select'" v-model="item.value" :label="item.label" :placeholder="item.placeholder" :rules="item.rules" :type="item.type" :items="item.items" :multiple="item.multiple" small-chips filled ></v-autocomplete> <v-text-field v-else v-model="item.value" :label="item.label" :placeholder="item.placeholder" :rules="item.rules" :type="item.type" filled ></v-text-field> `, props: { item: Object, }, }); new Vue({ el: "#app", vuetify: new Vuetify(), data: () => ({ loading: false, title: "JP34 Input Data Dengan Tanda Tangan Dengan Apps Script", subtitle: "Source : www.javabitpro.com", form, snackbar: { show: false, message: "", color: "red", timeout: 5000, }, }), methods: { showSnackbar: function({message, color}){ this.snackbar.message = message this.snackbar.color = color this.snackbar.show = true }, submit: async function () { if (!this.$refs.form.validate()) { return this.showSnackbar({message: "Form Tidak Valid", color: "warning"}) } this.loading = true; const data = getFormData(this.form); try { const result = await apiCall("submit", data); this.loading = false; this.$refs.form.reset() this.showSnackbar({message: result.message, color: "success"}) } catch (error) { this.loading = false; this.showSnackbar({message: error.message, color: "error"}) } }, }, }); </script>
8. Klik ikon Save