Aplikasi CRUD Sederhana menggunakan PHP, MySQLi, dan Vue.js
Pembuatan aplikasi sederhana ini bertujuan untuk memberikan pengetahuan dan pengalaman bagaimana mengintegrasikan back-end dan front-end menggunakan PHP, MySQLi, dan Vue.js dalam membangun aplikasi Web dinamis. Namun penekanannya sendiri yaitu ke penggunaan framework Vue.js dalam berinteraksi dengan back-end maupun front-end untuk melakukan operasi CRUD (Create, Read, Update, dan Delete) sebagai user interface (UI) dan single-page application (SPA).
Directive dan Binding
Fungsi utama Vue.js terkait dengan rendering serta melakukan composition komponen (membuat fungsi atau variabel) dalam menangai SPA yang akan diterapkan kedalam directive dan binding. Oleh karen itu, jika tidak mengetahui kedua hal ini, maka tidak akan dapat membuat aplikasi Web menggunakan framework Vue.js.
Pada Vue.js, penggunaan istilah directive dapat diartikan sebagai cara menghubungkan atau mengikat (binding) data antara layer model (M)--melalui DOM--dengan layer view (V)--melalui variabel/fungsi--atau mudahnya metode untuk menghubungkan objek Vue.js (JavaScript) dengan dokumen HTML yang diterapkan/dipanggil melalui atribut atau kedalam elemen tag HTML dengan tujuan untuk memanipulasi Web Page itu sendiri.
Pada Vue, terdapat dua jenis binding untuk menghubungkan data model dengan tampilan.
- One-way Binding (satu arah): Data dari model dipetakan ke tampilan/view (MV) menggunakan direktif seperti
{{ }}atauv-bind. - Two-way Binding/ (dua arah): Perubahan di model mempengaruhi tampilan/view dan sebaliknya (MVVM) dengan menggunakan
v-model.
Berikut ini beberapa directive Vue.js yang dapat digunakan beserta fungsinya.
- Directive Data Binding: v-bind, v-model, v-html, v-text, v-once
- Directive Conditional Rendering: v-if, v-else, v-else-if
- Directive Perulangan: v-for
- Directive Event: v-on
Berikut ini source code yang disimpkan kedalam file directive-bind.html, sebagai contoh penerapan directive-binding pada Vue.js secara sederhana.
<!DOCTYPE html>
<html>
<head>
<title>Vue.js: Directive-Bind</title>
<link rel="icon" type="image/x-icon" href="https://idraya.com/favicon.ico">
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@1.1.2/dist/axios.min.js"></script>
</head>
<body>
<div id="app" style="padding: 45px;">
<label>One-way Binding:</label><br>
<input type="text" v-bind:value="message_one">
<span>{{ message_one }}</span>
<br><br>
<label>Two-way Binding:</label><br>
<input type="text" v-model="message_two">
<span>{{ message_two }}</span>
</div>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
message_one: 'Hello World!',
message_two: 'Hello World!'
}
});
</script>
</body>
</html>
Berikut ini luaran dari source code diatas untuk melihat perbedaan antara One-way dengan Two-way binding pada Web Browser.
One-way Vs Two-way Binding
Contoh pada elemen html <input type="text" v-bind:value="message_one"> untuk mengaitkan atribut value (serbagai argumen) dengan nilai dari properti message_one dalam model Vue.js satu arah. Sementara <input type="text" v-bind:value="message_two"> mengaitkan input dengan data model dua arah.
Luaran pada Web Browser terlihat ketika mengubah nilai inputan Two-way, maka nilai inputan One-way akan kembali ke awal. Hal ini karena Vue.js bersifat reaktif, yaitu setiap elemen yang dijadikan objek Vue.js dapat menjadi triger/pemicu, sehingga properti data variable message_one dipanggil kembali. Untuk memastikan bahwa inputan One-way tidak ikut berubah (memastikan hanya satu kali perubahan), dapat menambahkan direktif v-once, contoh <input type="text" v-bind:value="message_one" v-once>.
Aplikasi CRUD
Aplikasi Web sederhana ini memiliki fungsi untuk menambahkan (create) data berupa data Mahasiswa yang terdiri dari data: Nama Lengkap (Full name), Nama Panggilan (Nickname), dan data Program Studi (Program). Selain untuk menambahkan dan menampilkan (read) data, aplikasi Web ini dapat merubah (update) dan menghapus (delete) data Mahasiswa.
Projek ini dapat dikatakan lanjutan dari projek sebelumnya, untuk itu terlebih dahulu membuat folder misal dengan nama crud-student pada sub folder C:\xampp\htdocs\ (Direktori XAMPP pada OS Windows).
Aplikasi Vue.js yang dibangun ini menggunakan library axios untuk interaksi antara Front-end dan Back-end melalui HTTP client berbasis promise (memanfaatkan aync dan await untuk menangai aliran data yang tidak sinkron) sebagai API aplikasi.
1). front-end
Berikut ini source-code front-end yang terdiri dari file index.php, dan style.css. Secara garis besar skrip ini berfungsi untuk mengatur bagaimana suatu data akan ditampilkan pada halaman Web.
Berikut Source code file index.php
<!DOCTYPE html>
<html>
<head>
<title>Student Biodata Information</title>
<link rel="icon" type="image/x-icon" href="https://idraya.com/favicon.ico">
<link rel="stylesheet" type="text/css" href="style.css">
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@1.1.2/dist/axios.min.js"></script>
</head>
<body>
<div id="student" class="container">
<div id="app-header" style="text-align: justify;">
<h2 class="title" style="text-align: center;"><a href="index.php" style="text-decoration: none;">Faculty of Science and Technology (FTS)</a></h2>
<p class="lead">Below is a list of science and technology faculty students consisting of several study programs, namely: Statistics, Mathematics, Biology, Food Technology, Agribusiness, Regional and City Planning, and Information Systems.</p>
</div>
<hr><br>
<div id="student-form">
<form id="studentForm" action="javascript:void(0);" method="post">
<input type="hidden" id="_identity" name="_identity" v-bind:value="student._identity" v-once>
<label for="identity">Identity:</label>
<input type="text" id="identity" name="identity" v-model:value="student.identity" autocomplete="off" placeholder="0123456789" required>
<label for="fullName">Full Name:</label>
<input type="text" id="fullName" name="fullName" v-model:value="student.fullname" autocomplete="off" placeholder="Nauzan Khalid" required>
<label for="nickname">Nickname:</label>
<input type="text" id="nickname" name="nickname" v-model:value="student.nickname" autocomplete="off" placeholder="Nau" required>
<label for="program">Program:</label>
<select name="program" id="program" v-on:change="onChange" v-model:value="student.program" required>
<option value="">Choose...</option>
<option value="statistics">Statistics</option>
<option value="mathematics">Mathematics</option>
<option value="biology">Biology</option>
<option value="food technology">Food Technology</option>
<option value="agribusiness">Agribusiness</option>
<option value="regional and city planning">Regional And City Planning</option>
<option value="information systems">Information Systems</option>
</select>
<div class="btn-group" v-if="edit">
<input type="reset" value="Cancel" v-on:click="edit = false">
<input type="submit" value="Update" v-on:click="updateStudent">
</div>
<div class="btn-group" v-else>
<input type="reset" value="Reset">
<input type="submit" value="Add" v-on:click="addStudent">
</div>
</form>
</div>
<br><hr><br>
<div id="student-data" v-if="payload != null && payload.length > 0">
<h2>Student Data</h2>
<table>
<tr>
<th style="text-align: left;">No.</th>
<th style="text-align: left;">#Identity</th>
<th style="text-align: center;">Action</th>
</tr>
<tr v-for="(student, index) in payload">
<td style="text-align: left;"><b>{{ index + 1 }}</b></td>
<td style="text-align: left;">
<ul>
<li><b>{{ student.identity }}</b>: {{ student.fullname }} ({{ student.nickname }})</li>
<li>{{ ucFirstWord(student.program) }}</li>
</ul>
</td>
<td style="text-align: center;">
<a href="javascript:void(0);" v-on:click="editStudent(student.identity);">Edit</a> | <a href="javascript:void(0);" v-on:click="deleteStudent(student.identity);">Delete</a>
</td>
</tr>
</table>
</div>
<div id="log" v-else>
<label for="log">Log</label>
<textarea style="resize: none; width: 100%; height: 64px;">{{ log }}</textarea>
</div>
<br>
</div>
<script type="text/javascript">
// instance of vue
var app = new Vue({
el: '#student',
data: {
log: 'You loaded this page on ' + new Date().toLocaleString(),
payload: [{}],
student: {
_identity: '',
identity: '',
fullname: '',
nickname: '',
program: ''
},
edit: false
},
mounted: function() {
this.getAllStudents();
},
methods: {
getAllStudents: function() {
axios.get('api.php').then(function (response) {
// handle success
// console.log(response.data);
if(response.data.hasOwnProperty('log')) {
app.log = response.data.log;
}
if(response.data.hasOwnProperty('payload')) {
app.payload = response.data.payload;
}
}).catch(function (error) {
// handle error
console.log(error);
});
},
addStudent: function(e) {
e.preventDefault();
let studentForm = app.toFormData(app.student);
if(app.formValidation()) {
axios.post('api.php?crud=add', studentForm).then(function (response) {
console.log(response);
app.getAllStudents();
app.student._identity = '';
app.student.identity = '';
}).catch(function (error) {
console.log(error);
});
}
},
deleteStudent: function(identity) {
let sure = confirm('Are you sure to delete: ' + identity + "?");
let studentForm = new FormData();
studentForm.append('identity', identity);
if(sure == true) {
axios.post('api.php?crud=delete', studentForm).then(function (response) {
console.log(response);
app.getAllStudents();
}).catch(function (error) {
console.log(error);
});
}
},
editStudent: function(identity) {
let sure = confirm('Are you sure to edit: ' + identity + "?");
let studentForm = new FormData();
studentForm.append('identity', identity);
if(sure == true) {
axios.post('api.php?crud=edit', studentForm).then(function (response) {
console.log(response.data.payload);
app.edit = true;
let obj = response.data.payload;
app.student._identity = obj.identity;
app.student.identity = obj.identity;
app.student.fullname = obj.fullname;
app.student.nickname = obj.nickname;
app.student.program = obj.program;
}).catch(function (error) {
console.log(error);
});
}
},
updateStudent: function(e) {
e.preventDefault();
let sure = confirm('Are you sure to update this data?');
let studentForm = app.toFormData(app.student);
if(sure == true) {
if(app.formValidation()) {
axios.post('api.php?crud=update', studentForm).then(function (response) {
console.log(response);
app.edit = false;
app.getAllStudents();
app.student._identity = '';
app.student.identity = '';
app.student.fullname = '';
app.student.nickname = '';
app.student.program = '';
}).catch(function (error) {
console.log(error);
});
}
}
},
formValidation: function() {
if(!app.student.identity) {
alert('Identity required!');
} else if(!app.student.fullname) {
alert('Fullname required!');
} else if(!app.student.nickname) {
alert('Nickname required!');
} else if(!app.student.program) {
alert('Program required!');
} else {
return true;
}
},
onChange: function(e) {
console.log(event.target.value);
},
toFormData: function(obj) {
let form_data = new FormData();
for(let key in obj) {
form_data.append(key, obj[key]);
}
return form_data;
},
ucFirstWord: function(str) {
if(typeof str === 'string') {
return str.split(" ").map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
}
}
}
});
</script>
</body>
</html>
Berikut Source code file style.css
body {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
margin: 0;
padding: 0;
}
.container {
max-width: 600px;
height: 100%;
margin: 0 auto;
background-color: #ff88;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
h1 {
color: #333;
text-align: center;
margin-top: 0;
padding-bottom: 5px;
border-bottom: 1px solid #888;
}
h3 {
color: #333;
margin-top: 20px;
}
form {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
label {
width: 45%;
margin-bottom: 10px;
font-weight: bold;
}
select {
width: 48.75%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 3px;
margin-bottom: 15px;
}
input[type="text"] {
width: 45%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 3px;
margin-bottom: 15px;
}
.btn-group {
width: 100%;
}
input[type="submit"], input[type="reset"] {
padding: 10px;
border: none;
border-radius: 3px;
cursor: pointer;
color: #f0f0f0;
font-weight: bold;
}
input[type="submit"] {
float: right;
width: 65%;
background-color: #007BFF;
}
input[type="submit"]:hover {
background-color: #0056b3;
}
input[type="reset"] {
width: 25%;
background-color: #ff000c;
}
input[type="reset"]:hover {
background-color: #ff0081;
}
textarea {
margin-top: 10px;
background-color: #ccc;
}
table {
font-family: arial, sans-serif;
border-collapse: collapse;
width: 100%;
}
td, th {
border: 1px solid #000000;
text-align: left;
padding: 8px;
}
tr:nth-child(odd) {
background-color: #ffffff;
}
tr:nth-child(even) {
background-color: #f2f2f2;
}
tr:hover {background-color: #ddd;}
ul {
padding-left: 20px;
}
2). Back-end
Berikut ini source-code back-end yang hanya terdiri dari satu file api.php. Secara garis besar skrip ini ditulis menggunakan bahasa pemrograman PHP berfungsi untuk berinteraksi dengan database MySQLi menggunakan pendekatan OOP (object oriented programming).
<?php
mysqli_report(MYSQLI_REPORT_STRICT);
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "db_fst";
$output = array('log' => null, 'payload' => null);
// to check database connection
try {
$conn = new mysqli($servername, $username, $password);
$sql = 'CREATE DATABASE IF NOT EXISTS ' . $dbname;
$conn->query($sql);
$conn->close();
} catch (\Exception $e) {
$output['log'] = $e->getMessage();
header("Content-type: application/json");
echo json_encode($output);
die();
}
try { // to check table
$conn = new mysqli($servername, $username, $password, $dbname);
$sql = "CREATE TABLE IF NOT EXISTS t_student (
identity INT(12) NOT NULL,
fullname VARCHAR(48) NOT NULL,
nickname VARCHAR(24) NOT NULL,
program VARCHAR(64) NOT NULL,
PRIMARY KEY(identity)
) ENGINE=InnoDB;";
$conn->query($sql);
$conn->close();
} catch (\Exception $e) {
$output['log'] = $e->getMessage();
header("Content-type: application/json");
echo json_encode($output);
die();
}
// to crud operation
$crud = "read";
if(isset($_GET['crud'])) {
$crud = $_GET['crud'];
}
if($crud == "read") {
try {
$conn = new mysqli($servername, $username, $password, $dbname);
$sql = "SELECT identity, fullname, nickname, program
FROM t_student
ORDER BY identity DESC";
$query = $conn->query($sql);
$student = array();
while($row = $query->fetch_array()){
array_push($student, $row);
}
$output['payload'] = $student;
$conn->close();
header("Content-type: application/json");
echo json_encode($output);
die();
} catch (\Exception $e) {
$output['log'] = $e->getMessage();
header("Content-type: application/json");
echo json_encode($output);
die();
}
} else if($crud == "add") {
try {
$identity = htmlspecialchars(trim($_POST['identity']));
$fullname = htmlspecialchars(trim($_POST['fullname']));
$nickname = htmlspecialchars(trim($_POST['nickname']));
$program = htmlspecialchars(trim($_POST['program']));
$conn = new mysqli($servername, $username, $password, $dbname);
$sql = "INSERT INTO t_student(identity, fullname, nickname, program)
VALUES('$identity', '$fullname', '$nickname', '$program')";
$conn->query($sql);
$conn->close();
die();
} catch (\Exception $e) {
$output['log'] = $e->getMessage();
header("Content-type: application/json");
echo json_encode($output);
die();
}
} else if($crud == "delete") {
try {
$identity = htmlspecialchars(trim($_POST['identity']));
$conn = new mysqli($servername, $username, $password, $dbname);
$sql = "DELETE FROM t_student WHERE identity = '$identity'";
$conn->query($sql);
$conn->close();
die();
} catch (\Exception $e) {
$output['log'] = $e->getMessage();
header("Content-type: application/json");
echo json_encode($output);
die();
}
} else if($crud == "edit") {
try {
$identity = htmlspecialchars(trim($_POST['identity']));
$conn = new mysqli($servername, $username, $password, $dbname);
$sql = "SELECT identity, fullname, nickname, program
FROM t_student
WHERE identity = '$identity'";
$student = $conn->query($sql);
$output['payload'] = $student->fetch_assoc();
$conn->close();
header("Content-type: application/json");
echo json_encode($output);
die();
} catch (\Exception $e) {
$output['log'] = $e->getMessage();
header("Content-type: application/json");
echo json_encode($output);
die();
}
} else if($crud == 'update') {
try {
$_identity = htmlspecialchars(trim($_POST['_identity']));
$identity = htmlspecialchars(trim($_POST['identity']));
$fullname = htmlspecialchars(trim($_POST['fullname']));
$nickname = htmlspecialchars(trim($_POST['nickname']));
$program = htmlspecialchars(trim($_POST['program']));
$conn = new mysqli($servername, $username, $password, $dbname);
$sql = "UPDATE t_student
SET identity = '$identity',
fullname = '$fullname',
nickname = '$nickname',
program = '$program'
WHERE identity = '$_identity'";
$conn->query($sql);
$conn->close();
die();
} catch (\Exception $e) {
$output['log'] = $e->getMessage();
header("Content-type: application/json");
echo json_encode($output);
die();
}
}
3). Demo Aplikasi
Berikut demonstrasi aplikasi CRUD sederhana berdasarkan source code diatas (back-end dan front-end). Mudahnya untuk menjalankan aplikasi Web ini, cukup lakukan copy-paste dan simpan kedalam file dengan nama yang sesuai: index.php, style.css, dan api.php. Kemudian jalankan Webserver.
Pada XAMPP untuk mengakses database MySQL melalui alamat http://localhost/phpmyadmin. Untuk database beserta tabel demo aplikasi, akan terbentuk otomatis, karena didefenisikan melalui skrip pada file api.php. Penjelasan mengenai source code akan dipaparkan saat dikelas.
Kesimpulan
Direktif/Directive secara bahasa diartikan sebagai pengarah, petunjuk, atau perintah. Namun pada Vue.js, directive adalah atribut khusus yang didefenisikan pada elemen html sebagai tempat penunjuk untuk menjalakan perintah/ekpresi JavaScript pada DOM.
Pada Vue, perenderan deklaratif merupakan teknik/metode untuk merender (menghasilkan luaran data) ke DOM secara langsung melalui directive sebagai interpolasi data (mendapatkan/menemukan nilai diatara dua data/lebih).
Warning!
We are not responsible for any loss whatsoever due to this site, also if you want to take this article please read terms of use or touch us via contact page.
If there is question, please discuss below. Very welcome and expected to provide corrections, criticisms, and suggestions.
Be the first :D