React.js - Hướng dẫn dùng react.js với rails
1. Giới thiệu React.js là một Javascript Framework mới khá là phổ biến được tạo ra bởi Facebook. Trong bài viết chúng ta sẽ thử sử dụng nó để viết một ứng dụng giả lập theo dõi chi phí trong rails như hình Mỗi bản ghi sẽ có tiêu đề, thời gian, số tiền Debit: Tổng các giá trị nhỏ hơn ...
1. Giới thiệu
React.js là một Javascript Framework mới khá là phổ biến được tạo ra bởi Facebook. Trong bài viết chúng ta sẽ thử sử dụng nó để viết một ứng dụng giả lập theo dõi chi phí trong rails như hình
-
Mỗi bản ghi sẽ có tiêu đề, thời gian, số tiền
-
Debit: Tổng các giá trị nhỏ hơn 0
-
Credit: tổng các giá trị lớn hơn 0
-
Balance: Tổng các giá trị của tất cả bản ghi
-
Khi người dùng tạo một bản ghi, nó sẽ được thêm vào bảng
-
Người dùng có thể sửa các bản ghi, và xóa nó bằng các nut edit, delete ứng với mỗi bản ghi
2. Hướng dẫn cài đặt để tạo ứng dụng
Bước đầu tiên, ta sẽ tạo một project trong rails
rails new sample_app
Đối với giao diện người dùng trong project này, ta sử dụng bootstrap gem làm theo hướng dẫn như sau Trong Gemfile: cài đặt 2 gem
gem "bootstrap-sass" gem "sass-rails", "~> 5.0"
Trong app/assets/stylesheets/custom.css.scss
@import "bootstrap-sprockets"; @import "bootstrap";
Cài đặt gem "react-rails" trong Gemfile để sử dụng thư viện react.js
gem 'react-rails', '~> 1.0'
Sau đó chạy lệnh
rails g react:install
Nó sẽ tạo cho bạn 1 file components.js ở trong thư mục app/assets/javascripts/components/. Sau khi chạy xong lệnh trên, thì trong file application.js xuất hiện 3 dòng
//= require react //= require react_ujs //= require components
3. Tạo Resource
Chúng ta sẽ tạo một record resource gồm có date,title và amount
rails g resource Record title date:date amount:float
Chạy cơ sở dữ liệu:
rake db:create db:migrate
Tạo 2 record trong rails console, và không quên lệnh rails s
Record.create title: 'Record 1', date: Date.today, amount: 500 Record.create title: 'Record 2', date: Date.today, amount: -100
4. Tạo danh sách các records
Trong app/controllers/records_controller.rb ta có
class RecordsController < ApplicationController def index @records = Record.all end end
Trong file apps/views/records/index.html.erb
<%= react_component "Records", { data: @records } %>
file này sẽ là cầu nối giữa Rails app và React Components
Ta có một list các record được viết như sau, trong app/assets/javascripts/components/records.js.coffee
@Records = React.createClass getInitialState: -> records: @props.data getDefaultProps: -> records: [] render: -> React.DOM.div className: 'records' React.DOM.h2 className: 'title' 'Records' React.DOM.table className: 'table table-bordered' React.DOM.thead null, React.DOM.tr null, React.DOM.th null, 'Date' React.DOM.th null, 'Title' React.DOM.th null, 'Amount' React.DOM.tbody null, for record in @state.records React.createElement Record, key: record.id, record: record
Chúng ta cần tạo mới Record component để hiện thị cho mỗi Record, trong app/assets/javascripts/components/record.js.coffee
@Record = React.createClass render: -> React.DOM.tr null, React.DOM.td null, @props.record.date React.DOM.td null, @props.record.title React.DOM.td null, amountFormat(@props.record.amount)
Thêm "$$ vào trước giá trị amount, app/assets/javascripts/utils.js.coffee
@amountFormat = (amount) -> '$ ' + Number(amount).toLocaleString()
Kết quả:
tham khảo tại https://github.com/vietanhbkaaa/React.js-with-rails/pull/2/files
5. Create Records
Ta cần tạo một method Create trong controller, trong app/controllers/records_controller.rb ta viết
class RecordsController < ApplicationController ... def create @record = Record.new(record_params) if @record.save render json: @record else render json: @record.errors, status: :unprocessable_entity end end private def record_params params.require(:record).permit(:title, :amount, :date) end end
Sau đó, trong file app/assets/javascripts/components/record_form.js.coffee ta sẽ viết như sau
@RecordForm = React.createClass getInitialState: -> title: ' date: ' amount: ' valid: -> @state.title && @state.date && @state.amount handleChange: (e) -> name = e.target.name @setState "#{ name }": e.target.value handleSubmit: (e) -> e.preventDefault() $.post ', { record: @state }, (data) => @props.handleNewRecord data @setState @getInitialState() , 'JSON' render: -> React.DOM.form className: 'form-inline' onSubmit: @handleSubmit React.DOM.div className: 'form-group' React.DOM.input type: 'text' className: 'form-control' placeholder: 'Date' name: 'date' value: @state.date onChange: @handleChange React.DOM.div className: 'form-group' React.DOM.input type: 'text' className: 'form-control' placeholder: 'Title' name: 'title' value: @state.title onChange: @handleChange React.DOM.div className: 'form-group' React.DOM.input type: 'number' className: 'form-control' placeholder: 'Amount' name: 'amount' value: @state.amount onChange: @handleChange React.DOM.button type: 'submit' className: 'btn btn-primary' disabled: !@valid() 'Create record'
Ta có Khi điền đầy đủ thông tin, button Create Record sẽ được enable
Khi tạo ra một record, để hiển thị record đó vào danh sách các record, ta viết thêm một phương thức addRecord trong app/assets/javascripts/components/records.js.coffee
@Records = React.createClass ... addRecord: (record) -> records = @state.records.slice() records.push record @setState records: records render: -> React.DOM.div className: 'records' React.DOM.h2 className: 'title' 'Records' React.createElement RecordForm, handleNewRecord: @addRecord React.DOM.hr null ...
thao khảo tại: https://github.com/vietanhbkaaa/React.js-with-rails/pull/3/files
6. Xử lý đối với các chỉ số lượng (Amount)
Để tạo các box như hình với những nhiệm vụ đã được đề cập ở đầu bài viết.
Ta làm như sau:
Trong file # app/assets/javascripts/components/amount_box.js.coffee
@AmountBox = React.createClass render: -> React.DOM.div className: 'col-md-4' React.DOM.div className: "panel panel-#{ @props.type }" React.DOM.div className: 'panel-heading' @props.text React.DOM.div className: 'panel-body' amountFormat(@props.amount)
Trong file # app/assets/javascripts/components/records.js.coffee
@Records = React.createClass ... credits: -> credits = @state.records.filter (val) -> val.amount >= 0 credits.reduce ((prev, curr) -> prev + parseFloat(curr.amount) ), 0 debits: -> debits = @state.records.filter (val) -> val.amount < 0 debits.reduce ((prev, curr) -> prev + parseFloat(curr.amount) ), 0 balance: -> @debits() + @credits() ...
phần render của records.js.coffee để hiển thị 3 box trên ta viết
@Records = React.createClass ... render: -> React.DOM.div className: 'records' React.DOM.h2 className: 'title' 'Records' React.DOM.div className: 'row' React.createElement AmountBox, type: 'success', amount: @credits(), text: 'Credit' React.createElement AmountBox, type: 'danger', amount: @debits(), text: 'Debit' React.createElement AmountBox, type: 'info', amount: @balance(), text: 'Balance' React.createElement RecordForm, handleNewRecord: @addRecord ...
Kết quả tham khảo tại: https://github.com/vietanhbkaaa/React.js-with-rails/pull/4
7. Deleting Records
Trước hết ta tạo một phương thức delete trong controller app/controllers/records_controller.rb
class RecordsController < ApplicationController ... def destroy @record = Record.find(params[:id]) @record.destroy head :no_content end ... end
trong mỗi record, ta tạo một button Delete, và method handleDelete
# app/assets/javascripts/components/record.js.coffee @Record = React.createClass handleDelete: (e) -> e.preventDefault() $.ajax method: 'DELETE' url: "/records/#{ @props.record.id }" dataType: 'JSON' success: () => @props.handleDeleteRecord @props.record render: -> React.DOM.tr null, React.DOM.td null, @props.record.date React.DOM.td null, @props.record.title React.DOM.td null, amountFormat(@props.record.amount) React.DOM.td null, React.DOM.a className: 'btn btn-danger' onClick: @handleDelete 'Delete'
Trong app/assets/javascripts/components/records.js.coffee
@Records = React.createClass ... deleteRecord: (record) -> records = @state.records.slice() index = records.indexOf record records.splice index, 1 @replaceState records: records render: -> ... # almost at the bottom of the render method React.DOM.table React.DOM.thead null, React.DOM.tr null, React.DOM.th null, 'Date' React.DOM.th null, 'Title' React.DOM.th null, 'Amount' React.DOM.th null, 'Actions' React.DOM.tbody null, for record in @state.records React.createElement Record, key: record.id, record: record, handleDeleteRecord: @deleteRecord
kết quả tham khảo tại https://github.com/vietanhbkaaa/React.js-with-rails/pull/5
8. Editing Records
Trong phần này ta sẽ tối ưu một số đoạn code bằng cách sử dụng đoạn lệnh sau trong config/application.rb
... module Accounts class Application < Rails::Application ... config.react.addons = true end end
restart sever, sau đó tối ưu một số đoạn code như sau:
# app/assets/javascripts/components/records.js.coffee @Records = React.createClass ... addRecord: (record) -> records = React.addons.update(@state.records, { $push: [record] }) @setState records: records deleteRecord: (record) -> index = @state.records.indexOf record records = React.addons.update(@state.records, { $splice: [[index, 1]] }) @replaceState records: records
Thêm method update trong controller # app/controllers/records_controller.rb
class RecordsController < ApplicationController ... def update @record = Record.find(params[:id]) if @record.update(record_params) render json: @record else render json: @record.errors, status: :unprocessable_entity end end ... end
Trong # app/assets/javascripts/components/record.js.coffee
@Record = React.createClass ... handleEdit: (e) -> e.preventDefault() data = title: React.findDOMNode(@refs.title).value date: React.findDOMNode(@refs.date).value amount: React.findDOMNode(@refs.amount).value # jQuery doesn't have a $.put shortcut method either $.ajax method: 'PUT' url: "/records/#{ @props.record.id }" dataType: 'JSON' data: record: data success: (data) => @setState edit: false @props.handleEditRecord @props.record, data ...
Trong # app/assets/javascripts/components/records.js.coffee viết
@Records = React.createClass ... updateRecord: (record, data) -> index = @state.records.indexOf record records = React.addons.update(@state.records, { $splice: [[index, 1, data]] }) @replaceState records: records ... render: -> ... # almost at the bottom of the render method React.DOM.table React.DOM.thead null, React.DOM.tr null, React.DOM.th null, 'Date' React.DOM.th null, 'Title' React.DOM.th null, 'Amount' React.DOM.th null, 'Actions' React.DOM.tbody null, for record in @state.records React.createElement Record, key: record.id, record: record, handleDeleteRecord: @deleteRecord, handleEditRecord: @updateRecord
Kết quả Tham khảo tại: https://github.com/vietanhbkaaa/React.js-with-rails/pull/6
9. Kết luận
Trong bài viết này, chúng ta nắm được những bước cơ bản của sử dụng react.js trong rails. Cách sử dụng các phương thức create, update, destroy của react.js. Đây là một công nghệ đáng để thử.