Ví dụ nhỏ về sự kết hợp giữa Vue.js and Rails
Bài đăng này đã không được cập nhật trong 8 năm
Mở đầu
Vue.js đã được áp dụng rộng rãi bởi cộng đồng Laravel, nhưng ở Rails thì rất hiếm. Mình đã sử dụng VueJS được một ít nên xin phép được chia sẻ cách kết hợp Rails và VueJS lại với nhau.
Chúng ta sẽ làm gì
Chúng ta sẽ xây dựng một ứng dụng quản lý nhân viên để có thể thấy được tất cả các action CRUD đơn giản. Mục đích là để sử dụng Vue thao tác với từng actions trong 1 single page. Chúng ta sẽ có thể hiển thị tất cả các nhân viên, tạo/mời nhân viên mới, sửa thông tin của nhân viên, thăng cấp và hạ cấp các nhân viên, hoặc là sa thải. Bài này đòi hỏi bạn phải có kiến thức về Rails.
1. Thêm VueJS vào Rails
Các bạn có thể dùng Bower để lấy VueJS về và cài đặt thêm một ít để dùng, hoặc đơn giản hơn là dùng Gem vuejs-rails
.
Nếu dùng Gem thì hãy nhớ làm theo Readme trên trang GitHub của nó nhé.
2. Khởi tạo dự án
Chúng ta sẽ tạo ra model Employee kèm theo controller, cùng với đó là file JavaScript và view index của Employee. Như vậy, chúng ta sẽ chỉ cần những files sau:
app/models/employee.rb
app/controllers/employees_controller.rb
app/assets/javascripts/employees.js
app/views/employees/index.html
Và chúng ta sẽ không cần dùng tới 2 actions new
và edit
nên hãy cấu hình cho resource employees
trong file config/routes.rb
như sau:
resources :employees, except: [:new, :edit]
2 actions trên chúng ta sẽ thực hiện ngay trong trong index
. Và tất cả các actions sẽ trả về theo định dạng json
.
Chúng ta cũng sẽ tạo file migration cho employee, với các trường name
, email
,manager
như sau:
# migration
class CreateEmployees < ActiveRecord::Migration
def change
create_table :employees do |t|
t.string :name
t.string :email
t.boolean :manager
t.timestamps null: false
end
end
end
Tạo danh sách nhân viên
Bước đầu tiên, chúng ta sẽ tạo ra một table để hiển thị danh sách nhân viên. Ta sẽ tạo action index
và trả về tất cả nhân viên theo định dạng json
:
# app/controllers/employees_controller.rb
class EmployeesController < ApplicationController
def index
@employees = Employee.all
respond_to do |format|
format.html
format.json {render json: @employees}
end
end
end
Tiếp theo ta sẽ cài đặt view và javascript để load list người dùng từ server:
// app/assets/javascripts/employees.js
var employees = new Vue({
el: '#employees',
data: {
employees: []
},
ready: function() {
var that;
that = this;
$.ajax({
url: '/employees.json',
success: function(res) {
that.employees = res;
}
});
}
});
<!-- app/views/employees/index.html -->
<h1>Employees</h1>
<div id="employees">
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Manager</th>
</tr>
</thead>
<tbody>
<tr v-for="employee in employees">
<td>{{ employee.name }}</td>
<td>{{ employee.email }}</td>
<td>{{ employee.manager }}</td>
</tr>
</tbody>
</table>
</div>
Đoạn này chúng ta sẽ còn dùng nhiều nên tốt nhất là tách ra 1 component riêng:
<tr>
<td>{{ employee.name }}</td>
<td>{{ employee.email }}</td>
<td>{{ employee.manager }}</td>
</tr>
Tạo thêm component mới
// app/assets/javascripts/employees.js
Vue.component('employee-row', {
template: '#employee-row',
props: {
employee: Object
}
})
// ...
Thêm vào view index
template:
<!-- app/views/employees/index.html -->
<script type="text/x-template" id="employee-row">
<tr>
<td>{{ employee.name }}</td>
<td>{{ employee.email }}</td>
<td>{{ employee.manager }}</td>
</tr>
</script>
<!-- ... -->
Chúng ta sẽ sửa lại đoạn này trong view index
<tbody>
<tr v-for="employee in employees">
<td>{{ employee.name }}</td>
<td>{{ employee.email }}</td>
<td>{{ employee.manager }}</td>
</tr>
</tbody>
thành
<tbody>
<tr
is="employee-row"
v-for="employee in employees"
:employee="employee">
</tr>
</tbody>
<!-- ... -->
Mời nhân viên mới
Điều thú vị đã bắt đầu. Để có thể mời mọt nhân viên, chúng ta cần một thêm một row vào table, và nó sẽ bao gồm Name, Email, và Manager status. Component employees
sẽ cần một object mới khi nhân viên được tạo, đồng thời một hash errors
để handle errors.
// app/assets/javascripts/employees.js
var employees = new Vue({
el: '#employees',
data: {
employees: [],
employee: {
name: '',
email: '',
manager: false
},
errors: {}
},
// ...
Tiếp theo, tạo action create
trả về object employee
nếu như tạo được và errors
nếu ngược lại.
# app/controllers/employees_controller.rb
# ...
def create
@employee = Employee.new(employee_params)
respond_to do |format|
format.json do
if @employee.save
render :json => @employee
else
render :json => { :errors => @employee.errors.messages }, :status => 422
end
end
end
end
private
def employee_params
params.require(:employee).permit(:name, :email, :manager)
end
end
Ta cũng đồng thời thêm method hireEmployee
vào Vue instance, và nó sẽ được gọi khi Hire button được clicked. Hàm nãy sẽ tạo một ajax để thêm employee vào hệ thống.
// app/assets/javascripts/employees.js
// ...
methods: {
hireEmployee: function () {
var that = this;
$.ajax({
method: 'POST',
data: {
employee: that.employee,
},
url: '/employees.json',
success: function(res) {
that.errors = {}
that.employees.push(res);
},
error: function(res) {
that.errors = res.responseJSON.errors
}
})
}
}
<!-- app/views/employees/index.html -->
<!-- ... -->
<tr>
<td>
<!-- Input -->
<input type="text" v-model="employee.name"><br>
<!-- Validation errors -->
<span style="color:red">{{ errors.name }}</span>
</td>
<td>
<!-- Input -->
<input type="text" v-model="employee.email"><br>
<!-- Validation errors -->
<span style="color:red">{{ errors.email }}</span>
</td>
<td><input type="checkbox" v-model="employee.manager"></td>
<!-- button click calls hireEmployee -->
<td><button @click="hireEmployee">Hire</button></td>
</tr>
</tbody>
<!-- ... -->
Chỉnh sửa nhân viên
Ta sẽ thêm button "Edit" vào từng dòng một, và khi click vào, nó sẽ cho chúng ta chỉnh sửa thông tin của nhân viên. Thực hiện update
action thì khá giống với create
action, ngoại trừ việc chúng ta phải tìm ra employee
để sửa chứ không phải là thêm mới vào.
update
action sẽ được gọi khi click vào "Save" button ở trạng thái chỉnh sửa. Ta cũng sẽ thêm chức năng "Thăng chức/Giáng chức" bằng cách tạo 1 toggle "Promote/Demote".
# app/controllers/employees_controller.rb
# ...
def update
@employee = Employee.find(params[:id])
respond_to do |format|
format.json do
if @employee.update(employee_params)
render :json => @employee
else
render :json => { :errors => @employee.errors.messages }, :status => 422
end
end
end
end
# ...
// app/assets
Vue.component('employee-row', {
//...
data: function () {
return {
editMode: false,
errors: {}
}
},
methods: {
// toggle the manager status which also updates the employee in the database
toggleManagerStatus: function () {
this.employee.manager = !this.employee.manager
this.updateEmployee()
},
// ajax call for updating an employee
updateEmployee: function () {
var that = this;
$.ajax({
method: 'PUT',
data: {
employee: that.employee,
},
url: '/employees/' + that.employee.id + '.json',
success: function(res) {
that.errors = {}
that.employee = res
that.editMode = false
},
error: function(res) {
that.errors = res.responseJSON.errors
}
})
}
}
<!-- app/views/employees/index.html -->
<script type="text/x-template" id="employee-row">
<tr>
<td>
<!-- Show input when in edit mode -->
<div v-if="editMode">
<input type="text" v-model="employee.name"><br>
<span style="color:red">{{ errors.name }}</span>
</div>
<div v-else>{{ employee.name }}</div>
</td>
<td>
<div v-if="editMode">
<input type="text" v-model="employee.email"><br>
<span style="color:red">{{ errors.email }}</span>
</div>
<div v-else>{{ employee.email }}</div>
</td>
<td>
<div v-if="editMode">
<input type="checkbox" v-model="employee.manager">
</div>
<div v-else>{{ employee.manager ? '✔' : '' }}</div>
</td>
<td>
<!-- Save button calls updateEmployee -->
<button v-if="editMode" @click="updateEmployee">Save</button>
<!-- Edit button puts row into edit mode -->
<button v-else @click="editMode = true">Edit</button>
<!-- Promote / Demote based on current status -->
<button v-if="!editMode" @click="toggleManagerStatus">{{ employee.manager ? 'Demote' : 'Promote' }}</button>
</td>
</tr>
</script>
Sa thải nhân viên
Một phần nữa là chức năng sa thải nhân viên. Ta sẽ thêm "Fire" button và nó sẽ thực thi method fireEmployee
, method này sẽ gọi action destroy
# app/controllers/employees_controller.rb
# ...
def destroy
Employee.find(params[:id]).destroy
respond_to do |format|
format.json { render :json => {}, :status => :no_content }
end
end
# ...
// app/assets/javascripts/employees.js
// Inside the employee component
fireEmployee: function () {
var that = this;
$.ajax({
method: 'DELETE',
url: '/employees/' + that.employee.id + '.json',
success: function(res) {
that.$remove()
}
})
}
// ...
<!-- the Fire button inside the component template-->
<button v-if="!editMode" @click="fireEmployee" style="color:red">Fire</button>
Và tất cả đã xong. Nếu bạn có thắc mắc gì, hãy để lại comment bên dưới nhé.
All rights reserved