Memulai Pembuatan Aplikasi Web dengan Express.js (7): Pagination dan Upload Foto
Muhammad Arslan 5 November 2016
Setelah mengikuti episode sebelumnya. Sekarang saatnya kita membuat CRUD untuk mencatat anggota yang bergabung dengan organisasi ktia. Sebelum mengikuti tutorial ini, Anda diharuskan untuk mengikuti tutorial sebelumnya yaitu:
- Memulai Pembuatan Aplikasi Web dengan Express.js (2): Menggunakan Jade dan Memasang Template Web AdminLTE
- Memulai Pembuatan Aplikasi Web dengan Express.js (3): Form dan Autentikasi
- Memulai Pembuatan Aplikasi Web dengan Express.js (4): CRUD User
- Memulai Pembuatan Aplikasi Web dengan Express.js (5): CRUD Anggota
Persiapan
Sebagai persiapan, silahkan buat sebuah folder dengan nama uploads di dalam folder public. Folder tersebut akan digunakan untuk menyimpan foto yang di-upload oleh user untuk mengganti foto anggota. Lalu, karena kita akan beberapa modul Node.js berikut:$ npm install formidable
$ npm install pagination
Dan ada perubahan sedikit untuk model anggota.js dengan menambahkan field propic_path seperti pada kode berikut:
var mongoose = require('mongoose'); var Schema = mongoose.Schema;
var anggotaSchema = new Schema({ nama: { type: String, required: true, unique: true }, tempat_lahir: String, tanggal_lahir: Date, email: String, telepon: String, gender: String, keterangan: String, propic_path: String,
alamat: [{alamat: String, types: String}], // multirecord kontak: [{kontak: String, types: String}], // multirecord
}, { timestamps: true });
var Anggota = mongoose.model('Anggota', anggotaSchema);
module.exports = Anggota;
Pagination di Halaman Index Anggota
Ubah bagian import modul pada file routes/anggota.js menjadi seperti berikut:Pada kode diatas, kita menggunakan modul formidable, fs, dan pagination. Sekarang mari kita rombak route "/" untuk menghasilkan dan menampilkan pagination:var express = require('express'); var Anggota = require('../models/anggota'); var Auth_mdw = require('../middlewares/auth'); var moment = require('moment'); var formidable = require('formidable'); var fs = require('fs'); var pagination = require('pagination');
var router = express.Router(); var session_store;
router.get('/', function(req, res, next) {
session_store = req.session;
// pagination
page = 1;
offset = 0;
if (req.query.hasOwnProperty('page')){
page = req.query.page;
offset = (req.query.page - 1)*3;
}
Anggota.find({}, function(err, rows){
Anggota.count({}, function(err, count){
var bootstrapPaginator = new pagination.TemplatePaginator({
prelink:'/anggota', current: page, rowsPerPage: 3,
totalResult: count,
template: function(result) {
var i, len, prelink;
var html = '<div><ul class="pagination">';
if(result.pageCount < 2) {
html += '</ul></div>';
return html;
}
prelink = this.preparePreLink(result.prelink);
if(result.previous) {
html += '<li><a href="' + prelink + result.previous + '">Sebelumnya</a></li>';
}
if(result.range.length) {
for( i = 0, len = result.range.length; i < len; i++) {
if(result.range[i] === result.current) {
html += '<li class="active"><a href="' + prelink + result.range[i] + '">' + result.range[i] + '</a></li>';
} else {
html += '<li><a href="' + prelink + result.range[i] + '">' + result.range[i] + '</a></li>';
}
}
}
if(result.next) {
html += '<li><a href="' + prelink + result.next + '" class="paginator-next">Berikutnya</a></li>';
}
html += '</ul></div>';
return html;
}
});
res.render('anggota/index', { session_store:session_store, anggota: rows, paginator: bootstrapPaginator.render(), offset: offset });
});
}).skip(offset).limit(3);
});
Sebelum masuk ke query, kita konfigurasi terlebih dahulu untuk menghitung pagination yang akan ditampilkan. Mulai dari offset dan banyaknya item yang akan ditampilkan. Setelah query mengambil dokumen, kita eksekusi di dalamnya query untuk menghitung semua dokumen yang ada di collection anggota. Dan kita racik elemen pagination yang akan ditampilkan dengan cukup rumit. Kode tersebut merupakan adaptasi dari contoh kode yang ada di dokumentasi resmi NPM pagination. Anda dapat melihatnya untuk melihat contoh lainnya.
Barulah kita tampilkan paginator di halaman index anggota. Lalu untuk view anggota/index.jade sekarang berubah menjadi seperti ini:
extends ../layout/base
block content
section.content-header
h1
| Daftar Anggota
small Kelola anggota organisasi Anda disini
// Main content
section.content
.row
.col-xs-12
- if (messages.msg_error)
.alert.alert-danger.alert-dismissable
button.close(type='button', data-dismiss='alert', aria-hidden='true') ×
| #{messages.msg_error}
- if (messages.msg_info)
.alert.alert-success.alert-dismissable
button.close(type='button', data-dismiss='alert', aria-hidden='true') ×
| #{messages.msg_info}
.box
.box-header
h3.box-title Anggota
.box-tools
a(href="/anggota/add").btn.btn-primary.btn-sm
i.glyphicon.glyphicon-plus
| Tambah Anggota
// /.box-header
.box-body.table-responsive.no-padding
table.table.table-striped
thead
th No.
th Nama
th Info
th Action
tbody
for row, index in anggota
tr
td #{index + offset + 1}
td #{row.nama}
td #{row.keterangan}
td(style="width:100px;")
div(style="display:inline-block;margin-right:5px;")
a(href="/anggota/#{row._id}").btn.btn-primary.btn-xs
i.glyphicon.glyphicon-eye-open
div(style="display:inline-block")
form(method="POST", action="/anggota/delete/#{row._id}")
input(type="hidden", name="_method", value="DELETE")
button(type="submit").btn.btn-danger.btn-xs
i.glyphicon.glyphicon-trash
// /.box-body
.box-footer.clearfix
!{paginator}
// /.box
// /.content
Berikut adalah tampilan baru halaman index anggota setelah diberi pagination:
Upload Foto di Halaman Detail Anggota
Masuk kembali ke halaman detail anggota, kita akan mengubah sedikit kode views/anggota/detail.jade untuk menampilkan shortcut dan panel ganti foto. Silahkan ubah kode view tersebut menjadi seperti berikut:Pada kode diatas, kita hanya menambahkan panel ganti foto di sebelah kanan panel detail anggota. Sekarang tampilannya dapat dilihat seperti berikut:extends ../layout/base block content section.content-header h1 | Detail Anggota small Anda dapat melihat detail anggota disini // Main content section.content .row .col-xs-8 - if (messages.msg_error) .alert.alert-danger.alert-dismissable button.close(type='button', data-dismiss='alert', aria-hidden='true') × | #{messages.msg_error} - if (messages.msg_info) .alert.alert-success.alert-dismissable button.close(type='button', data-dismiss='alert', aria-hidden='true') × | #{messages.msg_info} .box .box-header h3.box-title #{anggota.nama} .box-tools div(style="display:inline-block; margin-right:5px;") a(href="/anggota/edit/#{anggota.id}").btn.btn-success.btn-sm i.glyphicon.glyphicon-pencil | Edit div(style="display:inline-block") form(method="POST", action="/anggota/delete/#{anggota._id}") input(type="hidden", name="_method", value="DELETE") button(type="submit").btn.btn-danger.btn-sm i.glyphicon.glyphicon-trash Hapus // /.box-header .box-body
ul li tempat / tanggal lahir: #{anggota.tempat_lahir} / #{anggota.tanggal_lahir} li gender: #{anggota.gender} li e-mail: #{anggota.email} li telepon: #{anggota.telepon} p #{anggota.keterangan} // /.box-body // /.box .col-xs-4 .box .box-header h3.box-title Foto .box-tools a(href="/anggota/change_profile_picture/#{anggota.id}").btn.btn-success.btn-sm i.glyphicon.glyphicon-picture | &nbsp; Change Photo | &nbsp; // /.box-header .box-body div(style="text-align:center;") img(src="/uploads/#{anggota.propic_path}", style="height:125px;") // /.box-body // /.box .row .col-xs-6 .box .box-header h3.box-title Kontak .box-tools a(href="/anggota/kontak/#{anggota.id}").btn.btn-success.btn-sm i.glyphicon.glyphicon-pencil | &nbsp; Edit | &nbsp; .box-body.table-responsive.no-padding table.table.table-striped thead tr td No. td Kontak td Tipe tbody for row, index in anggota.kontak tr td #{index+1} td #{row.kontak} td #{row.types} .col-xs-6 .box .box-header h3.box-title Alamat .box-tools a(href="/anggota/alamat/#{anggota.id}").btn.btn-success.btn-sm i.glyphicon.glyphicon-pencil | &nbsp; Edit | &nbsp; // /.box-header .box-body.table-responsive.no-padding table.table.table-striped thead tr td No. td Alamat td Tipe tbody for row, index in anggota.alamat tr td #{index+1} td #{row.alamat} td #{row.types} // /.box // /.content
Membuat Form Upload Foto
Setelah menambahkan panel ganti foto, sekarang kita buat form untuk ganti foto. Silahkan buat kode jade berikut di dalam file views/anggota/change_photo.jade:Karena yang baru saja kita buat masih cangkangnya saja, kita buat juga route "/change_profile_picture/(:id)" untuk menampilkan form ganti foto tersebut. Silahkan tambahkan route berikut tepat diatas route detail anggota:extends ../layout/base block content section.content-header h1 | Ganti Foto Anggota small Ganti foto anggota organisasi Anda disini // Main content section.content // Your Page Content Here // /.row .row .col-xs-12 .box .box-header h3.box-title Upload Foto // /.box-header .box-body - if (messages.msg_error) .alert.alert-danger.alert-dismissable button.close(type='button', data-dismiss='alert', aria-hidden='true') × | !{messages.msg_error} - if (messages.msg_info) .alert.alert-success.alert-dismissable button.close(type='button', data-dismiss='alert', aria-hidden='true') × | #{messages.msg_info} form(action="/anggota/change_profile_picture/#{anggota_id}", method="POST", role="form", enctype="multipart/form-data").form-horizontal .form-group label(for="nama").col-md-3.control-label Pilih Foto Baru .col-md-6 input(type="file", name="nama", id="nama", value="").form-control .form-group .col-sm-offset-3.col-sm-6 button(type="submit").btn.btn-primary i.fa.fa-save | Simpan | a(href="/anggota").btn.btn-danger Batal
// /.box-body // /.box // /.content
..............................................
router.get('/change_profile_picture/(:id)', function(req, res, next){
session_store = req.session;
res.render('anggota/change_photo', {session_store: session_store, anggota_id: req.params.id})
});
router.get('/(:id)', function(req, res, next){
session_store = req.session;
Anggota.findById(req.params.id, function(err, row){
if (err)
{
console.log(err);
req.flash('msg_error', 'Punten, sepertinya anggota yang dimaksud sudah tidak ada. Dan kebetulan lagi ada masalah sama sistem kami :D');
res.redirect('/anggota');
}
else
{
res.render('anggota/detail', {session_store: session_store, anggota: row})
}
});
});
module.exports = router;
Bila kita jalankan maka akan tampil seperti pada gambar berikut:
Proses Upload Foto
Untuk menangani proses upload file, kita harus membuat lagi route dengan metode POST. Di dalamnya akan ada penggunaan formidable untuk membaca file yang dikirimkan dari form dan akan disimpan ke folder public/uploads dengan bantuan modul fs. Path yang didapatkan dari pembacaan oleh formidable, akan disimpan di dalam field propic_path yang ada di dalam model models/anggota.js.Silahkan tambahkan route ganti foto dengan metode POST berikut diatas route detail anggota dan dibawah route ganti foto bermetode GET:
..............................................
router.post('/change_profile_picture/(:id)', function(req, res, next){
session_store = req.session;
var form = new formidable.IncomingForm();
form.parse(req, function(err, fields, files) {
if (err)
{
console.log(err);
}
else
{
console.log(files.nama);
var tmp_path = files.nama.path;
var upload_path = './public/uploads/'+files.nama.name;
fs.rename(tmp_path, upload_path, function(err){
if (err)
{
console.log(err);
}
Anggota.findById(req.params.id, function(err, row){
row.propic_path = files.nama.name;
row.save();
});
res.redirect('/anggota/'+req.params.id);
fs.unlink(tmp_path, function(){
if (err)
{
console.log(err);
}
});
})
}
});
});
router.get('/(:id)', function(req, res, next){
session_store = req.session;
Anggota.findById(req.params.id, function(err, row){
if (err)
{
console.log(err);
req.flash('msg_error', 'Punten, sepertinya anggota yang dimaksud sudah tidak ada. Dan kebetulan lagi ada masalah sama sistem kami :D');
res.redirect('/anggota');
}
else
{
res.render('anggota/detail', {session_store: session_store, anggota: row})
}
});
});
module.exports = router;
Sekarang mari kita coba dengan mengganti foto beberapa kali:
Penutup
Di tutorial yang keenam ini kita masih memantapkan diri untuk membuat CRUD yang lebih kompleks daripada CRUD sebelumnya. Di tutorial berikutnya, kita akan mencoba untuk membuat fitur upload foto anggota dan menambahkan pagination di halaman daftar anggota. Tentunya fitur tersebut akan semakin menarik bila kita pelajari.Tetep pantengin Codepolitan yah :D, disini ajaa....
(codepolitan/nodejs/expressjs)