Pembuatan Miscrosoft Word Document .docx
yang dimana kita tahu bahwa membuat atau menyunting document sangat mudah di lakukan apabila kita menggunakan Microsoft Word.
Namun, bagaimana jika kita hendak mengisi data document tersebut secara otomatis tanpa harus menyunting berkas tersebut di Microsoft Word ?
Contoh kasus ketika kita membuat sebuah form input pada website, kita mungkin ingin menampilkan data tersebut sebagai sebuah dokumen .docx atau menyimpan nya sebagai .json
data oleh pengguna, logika nya data tersebut dapat kita manipulasi berdasarkan kunci dan nilai dari data tersebut dengan kata lain kita butuh sebuah fitur automation pada form tersebut
Tanpa membutuhkan server-side pemanggilan data, kita dapat membuat nya secara langsung pada browser dengan menggunakan javascript karena browser memang support V8 engine ataupun Spider Monkey pada firefox.
Untuk memulai, di sini saya menggunakan library docx.js
Perlu di ketahui bahwa docx.js support seluruh browser terbaru, jadi sangat stabil untuk di terapkan pada project.
Cara penulisan pada ESM script
Untuk menggunakan fungsi import
kita harus menetapkan attribute type='module'
pada tag pembuka <script>
<script type="module">
// code
</script>
Mengimpor library dari CDN
import {
Document,
SectionType,
PageBreak,
NumberFormat,
Header,
Paragraph,
TextRun,
ImageRun,
Tab,
Packer,
AlignmentType,
LevelFormat,
convertInchesToTwip,
convertMillimetersToTwip,
PageOrientation,
PositionalTab,
PositionalTabAlignment,
PositionalTabLeader,
PositionalTabRelativeTo,
TabStopType,
TabStopPosition,
TextWrappingType,
TextWrappingSide
} from 'https://cdn.jsdelivr.net/npm/docx@8.5.0/+esm';
Perlu di perhatikan bahwa TIDAK harus seluruh object method harus di import gunakan seperlu nya untuk mengurangi beban GET request pada browser.
Di sini saya membuat sebuah dokumen pendaftaran PAUD, data ini adalah hasil dari input pendaftaran.
Membuat struktur data
const data = {
anak: {
nama_lengkap: '',
nama_panggilan: '',
jenis_kelamin: '',
tempat: '',
tanggal_lahir: '',
agama: '',
kewarganegaraan: '',
anakke: 'Satu',
jumlah_saudara_kandung: '',
bahasa_sehari_hari: '',
berat_badan: '',
tinggi_badan: '',
golongan_darah: '',
penyakit_yang_pernah_diderita: '',
alamat_tempat_tinggal: '',
telepon: '',
jarak_tempat_tinggal_ke_sekolah: ''
},
orangtua: {
nama_ayah: '',
nama_ibu: '',
pendidikan_tertinggi_ayah: '',
pendidikan_tertinggi_ibu: '',
pekerjaan_ayah: '',
pekerjaan_ibu: ''
}
};
Pada posisi ini kita anggap kita sudah memiliki data untuk kita kumpulkan dari formulir.
Membuat dokumen
Selanjutnya kita buat dokumen baru dengan beberap struktur yang akan kita buat diantaranya:
- creator: Penulis
- title: Nama Dokumen
- styles: Data pengaturan paragraph, font, indent, dll
- numbering: Data konfigurasi daftar berupa Huruf, Nomor, dll
- sections: Data konfigurasi halaman
const doc = new Document({
creator: 'KB Permata Suket Duwur',
title: 'Formulir Pendaftaran Calon Anak Didik',
styles: {
//...
},
numbering: {
//...
},
sections: [
//...
]
})
Dalam tahap ini kita akan melengkapi data styles
untuk dapat mengatur font, warna, indentasi paragraf.
styles: {
paragraphStyles: [
{
id: 'header-bold',
name: 'Header Bold',
basedOn: 'Normal',
//next: "Normal",
quickFormat: true,
run: {
size: 30,
font: 'Arial',
bold: true,
italics: false,
color: '000000',
},
paragraph: {
spacing: {
before: 0,
after: 0
}
}
},
{
id: 'arial-light',
name: 'arial light 11',
basedOn: 'Normal',
//next: "Normal",
quickFormat: true,
run: {
size: 24,
font: 'Arial',
color: '000000',
},
paragraph: {
spacing: {
before: 0,
after: 120
}
}
},
{
id: 'arial-light-no-spacing',
name: 'arial light 11',
basedOn: 'Normal',
//next: "Normal",
quickFormat: true,
run: {
size: 24,
font: 'Arial',
color: '000000',
},
paragraph: {
spacing: {
before: 0,
after: 0
}
}
},
{
id: 'arial-light-spacing-before',
name: 'arial light 11',
basedOn: 'Normal',
//next: "Normal",
quickFormat: true,
run: {
size: 24,
font: 'Arial',
color: '000000',
},
paragraph: {
spacing: {
before: 120,
after: 0
}
}
},
{
id: 'arial-light-spacing-after',
name: 'arial light 11',
basedOn: 'Normal',
//next: "Normal",
quickFormat: true,
run: {
size: 24,
font: 'Arial',
color: '000000',
},
paragraph: {
spacing: {
before: 0,
after: 120
}
}
},
]
},
Kunci id:
akan kita gunakan nanti untuk setiap paragraph yang memerlukan kostumisasi.
Membuat daftar kostum
numbering: {
config: [
{
reference: "letter-A",
levels: [
{
level: 0,
format: LevelFormat.UPPER_LETTER,
text: 'A.',
alignment: AlignmentType.START,
style: {
run: {
font: 'Arial',
size: 24,
bold: true
},
paragraph: {
indent: {
left: convertInchesToTwip(0.25),
hanging: convertInchesToTwip(0.25)
},
spacing: {
before: 120,
after: 120
},
}
}
}
],
},
{
reference: "letter-B",
levels: [
{
level: 0,
format: LevelFormat.UPPER_LETTER,
text: 'B.',
alignment: AlignmentType.START,
style: {
run: {
font: 'Arial',
size: 24,
bold: true
},
paragraph: {
indent: {
left: convertInchesToTwip(0.25),
hanging: convertInchesToTwip(0.25)
},
spacing: {
before: 120,
after: 120
},
}
}
}
],
},
{
reference: "number",
levels: [
{
level: 0,
format: LevelFormat.DECIMAL,
text: '%1.',
alignment: AlignmentType.START,
style: {
run: {
font: 'Arial',
size: 24,
bold: false
},
paragraph: {
indent: {
left: convertInchesToTwip(0.41),
hanging: convertInchesToTwip(0.25)
}
}
}
}
]
},
],
}
Kunci level
adalah untuk membuat daftar turunan di mulai dari 0, 1, 2 dst, level: 0
adalah daftar paling atas.
Kita juga dapat menentukan format seperti Angka LevelFormat.DECIMAL
atau huruf kaptial LevelFormat.UPPER_LETTER
Membuat halaman
sections: [
{
properties: {
titlePage: true,
size: {
orientation: PageOrientation.PORTRAIT,
width: convertMillimetersToTwip(210),
height: convertMillimetersToTwip(297),
},
page: {
pageNumbers: {
start: 1,
formatType: NumberFormat.DECIMAL,
},
},
},
headers: {
first: new Header({
children: [
new Paragraph({
style: 'header-bold',
alignment: AlignmentType.CENTER,
children: [
new TextRun({
text: 'FORMULIR PENDAFTARAN CALON ANAK DIDIK',
bold: true,
})
]
}),
new Paragraph({
style: 'header-bold',
alignment: AlignmentType.CENTER,
children: [
new TextRun({
text: 'PAUD/TK KB PERMATA SUKET DUWUR',
bold: true,
})
]
}),
new Paragraph({
style: 'header-bold',
alignment: AlignmentType.CENTER,
children: [
new TextRun({
text: `TAHUN PELAJARAN: ${tahunsekarang} / ${tahunsekarang + 1} `,
bold: true,
})
]
}),
new Paragraph({
style: 'arial-light',
border: {
bottom: {
color: '#000000',
size: 20,
space: 1,
style: 'single'
}
}
})
],
}),
},
children: [
// A. KETERANGAN ANAK DIDIK
new Paragraph({
style: 'arial-light',
text: 'KETERANGAN ANAK DIDIK',
numbering: {
reference: "letter-A",
level: 0,
},
}),
// data anak
new Paragraph({
style: 'arial-light',
text: 'Nama Lengkap',
numbering: {
reference: "number",
level: 0
},
children: [
new TextRun({
text: '\t\t\t: ',
bold: false
}),
new TextRun({
text: data.anak.nama_lengkap,
bold: false,
})
]
}),
new Paragraph({
style: 'arial-light',
text: 'Nama Panggilan',
numbering: {
reference: "number",
level: 0
},
children: [
new TextRun({
text: '\t\t\t: ',
bold: false
}),
new TextRun({
text: data.anak.nama_panggilan,
bold: false
})
]
}),
new Paragraph({
style: 'arial-light',
text: 'Jenis Kelamin',
numbering: {
reference: "number",
level: 0
},
children: [
new TextRun({
text: '\t\t\t\t: ',
bold: false
}),
new TextRun({
text: 'Laki-laki',
strike: data.anak.jenis_kelamin == 'Laki-laki' ? false : true,
bold: false
}),
new TextRun({
text: '/',
bold: false
}),
new TextRun({
text: 'Perempuan',
strike: data.anak.jenis_kelamin == 'Perempuan' ? false : true,
bold: false
}),
new TextRun({
text: ' *)',
bold: false
})
]
}),
new Paragraph({
style: 'arial-light',
text: 'Tempat, Tanggal Lahir',
numbering: {
reference: "number",
level: 0
},
children: [
new TextRun({
text: '\t\t: ',
bold: false
}),
new TextRun({
text: data.anak.tempat,
bold: false
}),
new TextRun({
text: ', ',
bold: false
}),
new TextRun({
text: data.anak.tanggal_lahir,
bold: false
})
]
}),
new Paragraph({
style: 'arial-light',
text: 'Agama',
numbering: {
reference: "number",
level: 0
},
children: [
new TextRun({
text: '\t\t\t\t\t: ',
bold: false
}),
new TextRun({
text: data.anak.agama,
bold: false
})
]
}),
new Paragraph({
style: 'arial-light',
text: 'Kewarganegaraan',
numbering: {
reference: "number",
level: 0
},
children: [
new TextRun({
text: '\t\t\t: ',
bold: false
}),
new TextRun({
text: 'WNI',
strike: data.anak.kewarganegaraan == 'WNI' ? false : true,
bold: false
}),
new TextRun({
text: '/',
bold: false
}),
new TextRun({
text: 'WNA',
strike: data.anak.kewarganegaraan == 'WNA' ? false : true,
bold: false
}),
new TextRun({
text: ' *)',
bold: false
})
]
}),
new Paragraph({
style: 'arial-light',
text: 'Anak ke',
numbering: {
reference: "number",
level: 0
},
children: [
new TextRun({
text: '\t\t\t\t: ',
bold: false
}),
new TextRun({
text: data.anak.anakke,
bold: false
})
]
}),
new Paragraph({
style: 'arial-light',
text: 'Jumlah Saudara Kandung',
numbering: {
reference: "number",
level: 0
},
children: [
new TextRun({
text: '\t\t: ',
bold: false
}),
new TextRun({
text: data.anak.jumlah_saudara_kandung || '-',
bold: false
}),
]
}),
new Paragraph({
style: 'arial-light',
text: 'Bahasa Sehari-hari',
numbering: {
reference: "number",
level: 0
},
children: [
new TextRun({
text: '\t\t\t: ',
bold: false
}),
new TextRun({
text: data.anak.bahasa_sehari_hari,
bold: false
})
]
}),
new Paragraph({
style: 'arial-light',
text: 'Berat/Tinggi badan',
numbering: {
reference: "number",
level: 0
},
children: [
new TextRun({
text: '\t\t\t: ',
bold: false
}),
new TextRun({
text: data.anak.berat_badan,
bold: false
}),
new TextRun({
text: ' ',
bold: false
}),
new TextRun({
text: 'Kg',
bold: false
}),
new TextRun({
text: '/',
bold: false
}),
new TextRun({
text: ' ',
bold: false
}),
new TextRun({
text: data.anak.tinggi_badan,
bold: false
}),
new TextRun({
text: ' ',
bold: false
}),
new TextRun({
text: 'Cm',
bold: false
}),
]
}),
new Paragraph({
style: 'arial-light',
text: 'Golongan Darah',
numbering: {
reference: "number",
level: 0
},
children: [
new TextRun({
text: '\t\t\t: ',
bold: false
}),
new TextRun({
text: 'A',
strike: data.anak.golongan_darah == 'A' ? false : true,
bold: false
}),
new TextRun({
text: '/',
bold: false
}),
new TextRun({
text: 'B',
strike: data.anak.golongan_darah == 'B' ? false : true,
bold: false
}),
new TextRun({
text: '/',
bold: false
}),
new TextRun({
text: 'O',
strike: data.anak.golongan_darah == 'O' ? false : true,
bold: false
}),
new TextRun({
text: '/',
bold: false
}),
new TextRun({
text: 'AB',
strike: data.anak.golongan_darah == 'AB' ? false : true,
bold: false
}),
new TextRun({
text: ' *)',
bold: false,
})
]
}),
new Paragraph({
style: 'arial-light',
text: 'Penyakit Yang Pernah Diderita',
numbering: {
reference: "number",
level: 0
},
children: [
new TextRun({
text: '\t: ',
bold: false
}),
new TextRun({
text: data.anak.penyakit_yang_pernah_diderita || '-',
bold: false
}),
]
}),
...strchunk(data.anak.alamat_tempat_tinggal, 35),
new Paragraph({
style: 'arial-light',
text: 'Telepon',
alignment: AlignmentType.LEFT,
numbering: false,
indent: {
left: convertInchesToTwip(0.41),
hanging: convertInchesToTwip(0)
},
spacing: {
before: 100,
after: 100
},
children: [
new TextRun({
text: '\t\t\t\t: ',
bold: false,
}),
new TextRun({
text: data.anak.telepon || '-',
bold: false
})
]
}),
new Paragraph({
style: 'arial-light',
text: 'Jarak Tempat Tinggal ke Sekolah',
numbering: {
reference: "number",
level: 0
},
children: [
new TextRun({
text: '\t: ',
bold: false
}),
new TextRun({
text: data.anak.jarak_tempat_tinggal_ke_sekolah || '-',
bold: false
})
]
}),
// B. ORANG TUA/WALI
new Paragraph({
style: 'arial-light',
text: 'ORANG TUA/WALI',
numbering: {
reference: "letter-B",
level: 0
}
}),
// Nama Orang Tua
new Paragraph({
style: 'arial-light',
text: 'Nama Orang Tua',
numbering: {
reference: "number",
level: 0
},
}),
new Paragraph({
style: 'arial-light',
text: '- Ayah Kandung',
alignment: AlignmentType.LEFT,
numbering: false,
indent: {
left: convertInchesToTwip(0.41),
hanging: convertInchesToTwip(0)
},
spacing: {
before: 100,
after: 100
},
children: [
new TextRun({
text: '\t\t\t: ',
bold: false
}),
new TextRun({
text: data.orangtua.nama_ayah,
bold: false
})
]
}),
new Paragraph({
style: 'arial-light',
text: '- Ibu Kandung',
alignment: AlignmentType.LEFT,
numbering: false,
indent: {
left: convertInchesToTwip(0.41),
hanging: convertInchesToTwip(0)
},
spacing: {
before: 100,
after: 100
},
children: [
new TextRun({
text: '\t\t\t\t: ',
bold: false
}),
new TextRun({
text: data.orangtua.nama_ibu,
bold: false
})
]
}),
new Paragraph({
style: 'arial-light',
text: 'Pendidikan Tertinggi',
numbering: {
reference: "number",
level: 0
},
}),
new Paragraph({
style: 'arial-light',
text: '- Ayah Kandung',
alignment: AlignmentType.LEFT,
numbering: false,
indent: {
left: convertInchesToTwip(0.41),
hanging: convertInchesToTwip(0)
},
spacing: {
before: 100,
after: 100
},
children: [
new TextRun({
text: '\t\t\t: ',
bold: false
}),
new TextRun({
text: data.orangtua.pendidikan_tertinggi_ayah,
bold: false
})
]
}),
new Paragraph({
style: 'arial-light',
text: '- Ibu Kandung',
alignment: AlignmentType.LEFT,
numbering: false,
indent: {
left: convertInchesToTwip(0.41),
hanging: convertInchesToTwip(0)
},
spacing: {
before: 100,
after: 100
},
children: [
new TextRun({
text: '\t\t\t\t: ',
bold: false
}),
new TextRun({
text: data.orangtua.pendidikan_tertinggi_ibu,
bold: false
})
]
}),
new Paragraph({
style: 'arial-light',
text: 'Pekerjaan',
numbering: {
reference: "number",
level: 0
},
}),
new Paragraph({
style: 'arial-light',
text: '- Ayah Kandung',
alignment: AlignmentType.LEFT,
numbering: false,
indent: {
left: convertInchesToTwip(0.41),
hanging: convertInchesToTwip(0)
},
spacing: {
before: 100,
after: 100
},
children: [
new TextRun({
text: '\t\t\t: ',
bold: false
}),
new TextRun({
text: data.orangtua.pekerjaan_ayah,
bold: false
})
]
}),
new Paragraph({
style: 'arial-light',
text: '- Ibu Kandung',
alignment: AlignmentType.LEFT,
numbering: false,
indent: {
left: convertInchesToTwip(0.41),
hanging: convertInchesToTwip(0)
},
spacing: {
before: 100,
after: 100
},
children: [
new TextRun({
text: '\t\t\t\t: ',
bold: false,
}),
new TextRun({
text: data.orangtua.pekerjaan_ibu,
bold: false
})
]
})
],
},
]
Di saat kita deklarasikan new Paragraph(...)
maka akan membuat sebuah line baru beserta format teks, style yang tadi kita buat seperti style: 'arial-light'
, kita juga dapat menambahkan lebih banyak teks pada 1 paragraf dengan menggunakan new TextRun(...)
pada chidlren: [...]
sehingga memudahkan kita memasukan data dari hasil masukan formulir.
Ketika kita buat dokumen harap di perhatikan pada titlePage: true
mengindikasikan bahwa ini adalah halaman utama, yang artinya header pada halaman utama tidak akan tampil pada halaman berikutnya.
Dan juga apabila kita ingin membuat halaman landscape maka kita mengganti nya dengan orientation: PageOrientation.LANDSCAPE
.
Bagian ini ...strchunk(data.anak.alamat_tempat_tinggal, 35)
hanya sebagai tambahan ketika kita memiliki data string yang sangat panjang, maka kita dapat memotog setiap kata yang melebihi batas margin pada dokumen dengan fungsi berikut:
function strchunk(name, length) {
let newName = name;
// offsets i to match the current string
let offset = 0;
for (let i = 1; i < name.length; i += 1) {
if (i % length === 0) {
if (newName.charAt(i) === ' ') {
// Your own code with the offset
newName = newName = `${newName.substr(0, i - offset)}\n${newName.substr(i - offset + 1)}`;
} else {
// looking back in the string until there is a space or the string "ends"
while (newName.charAt(i - offset) !== ' ' && offset <= length) {
offset++;
}
// Only set newName if a space was found in the last length
if (i - offset > 0) {
newName = `${newName.substr(0, i - offset)}\n${newName.substr(i - offset + 1)}`;
}
}
}
}
// Fixed indent tabs
const result = newName.split(/\n/g);
if (result.length > 1) {
const firstString = result.shift()
const lastString = result.pop()
const firstArray = [
new Paragraph({
style: 'arial-light-spacing-before',
text: 'Alamat Tempat Tinggal',
numbering: {
reference: "number",
level: 0
},
children: [
new TextRun({
text: '\t\t: ',
bold: false
}),
new TextRun({
text: firstString.trim(),
bold: false
}),
]
})
]
const middleArray = result.length ? result.map(text => {
return new Paragraph({
style: 'arial-light-no-spacing',
numbering: false,
run: {
before: 0,
after: 0,
},
children: [
new TextRun({
text: '\t\t\t\t\t\t ',
bold: false
}),
new TextRun({
text: text.trim(),
bold: false
}),
]
})
}) : []
const lastArray = [
new Paragraph({
style: 'arial-light-spacing-after',
numbering: false,
run: {
before: 0,
after: 120,
},
children: [
new TextRun({
text: '\t\t\t\t\t\t ',
bold: false
}),
new TextRun({
text: lastString.trim(),
bold: false
}),
]
})
]
return firstArray.concat(middleArray, lastArray)
} else {
return result.map(text => {
return new Paragraph({
style: 'arial-light',
text: 'Alamat Tempat Tinggal',
numbering: {
reference: "number",
level: 0
},
children: [
new TextRun({
text: '\t\t: ',
bold: false
}),
new TextRun({
text: text.trim().replace(/\n/g, ''),
bold: false
}),
]
})
})
}
}
Dalam tahap ini kita sudah berhasil membuat sebuah dokumen kita bisa export dokumen dalam bentuk blob.
const docData = await Packer.toBlob(doc)
return docData
Rumit memang, tapi kita sudah membuat sebuah fitur otomatis untuk input data pada sebuah dokumen, daripada kita harus mengisi nya secara manual terlebih apabila data tersebut sangat banyak, sehingga memudahkan pekerjaan kita.