over 3 years ago

承襲前一篇的教學,我們來實踐另一個實作吧。

單頁式的 CRUD 應用,也就是在同一頁面上,實現完整 CRUD 的功能,聽起來酷吧!

透過 AngularJS data-binding 的優勢,我們可以不必像使用 JQuery 時,在程式碼內大量的去操作 DOM。

使用僅僅不過 50 行的 AngularJS code,完整接上 Rails Restful API,與你的網頁做互動。

我們承襲上一篇設定好的 Project,繼續往下做吧!

安裝表單處理套件

在 Rails 中,我本身習慣使用 simple-form (Github Repo) 作為 Rails 中產生表單的套件,先來安裝吧!在 Gemfile 中先加上:

gem 'simple_form'

而在 AngularJS 中,我習慣使用 ngUpload (Github Repo) 這個套件來處理表單的上傳,

我們可以透過 Rails-Assets 來安裝,在 Gemfile 中加上:

gem 'rails-assets-ngUpload', '0.5.11'

透過 Bundler 安裝這些套件:

$ bundler install

安裝 simple_form 到 Rails 中:

$ rails g simple_form:install

增加 ngUpload 到 assets pipeline 中,在 application.js 中新增:

//= require ngUpload

使用 Bootstrap 3 讓 UI 看起來漂亮點吧!

layout/application.html.erb 我們新增 Bootstrap 的 css 來源:

<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">

開始 Coding 吧!

使用 AngularJS Nested Controller (Nested Scope)

記得我們上次新增了 scaffold:languages ,並在 LanguageController 新增了一個 action:ng_index,

我們就在 ng_index.html.erb 這個頁面繼續往下完成更多功能吧!

首先我們先重新建立 list 的功能:

<div class="container" ng-controller="indexCtrl">
  <h1>Languages</h1>
  <div class="row">
    <div class="col-md-6">
      <div class="panel panel-default">
        <div class="panel-heading">Language list</div>
        <div class="panel-body">
          <ul class="list-group" ng-controller="listCtrl">
            <li class="list-group-item" ng-repeat="language in languages">
              {{ language.name }}                
            </li>
          </ul>
        </div>
      </div>
    </div>
  </div>    
</div>

我們看到最外層 divng-controller="indexCtrl",而在裡面 ul 上則為 ng-controller="listCtrl"

這是所謂的 Nested Controller ,也就是 html 結構中 controller 中又有一個 controller,

languages.js 中我們可以適合這樣實作:

var languageApp = angularApplication.module('languageApp', []);

languageApp.controller('indexCtrl', function($scope, $http) {
  $scope.languages = [];

  // 更新列表

  $scope.update_languages = function() {
    $http.get('/languages.json').
    success(function(languages) {
      $scope.languages = languages;
    });
  };
});

languageApp.controller('listCtrl', function($scope, $http) {
  $scope.update_languages();
});

這裏我們可以看到 indexCtrl 中有 $scope.languages 這個 model,和 $scope.update_languages() 這個 method,

而在 listCtrl 中去執行了 $scope.update_languages() 這個 method,但為什麼能夠這樣執行?

因為在 AngularJS 中, $scope 如同 DOM 一樣,本身是個樹狀結構,

如果在 listCtrl$scope.update_languages() 這個 method 沒有被定義 ,他會往 parent controller 中去尋找,

而在 li 中取用 $scope.languages 也是異曲同工之妙。

使用表單

我們來新增 create languages 的表單吧!在 ng_index.html.erb<div class="row"> 中新增:

<div class="col-md-6">
  <div class="panel panel-default">
    <div class="panel-heading">New Language</div>
      <div class="panel-body">
        <%= simple_form_for @language, url: '/languages.json', html: {
          :'ng-controller' => 'formCtrl', :'ng-upload' => 'completed(content)', 
          role: 'form'} do |f| %>
          <div class="form-group">
            <%= f.input_field :name, class: 'form-control',  
                              :'ng-model' => 'name', placeholder: 'Name' %>
          </div>
        <button type="submit" class="btn btn-default" ng-disabled="$isLoading">Create</button>
      <% end %>
    </div>
  </div>
</div>

我們用 simple-form 建立了一個表單,這個表單用到 :'ng-controller' => 'formCtrl'

並且使用到 AngularJS 的套件 ngUpload:'ng-upload' => 'completed(content)'

我們也在 input 上 define 了一個 :'ng-model' => 'name'

回到 languages.js 我們必須先載入有用到的套件到 AngularJS App 中:

var languageApp = angularApplication.module('languageApp', ['ngUpload']);

接者我們要新增 formCtrl 給這個表單來使用:

languageApp.controller('formCtrl', function($scope, $http) {
  // 上傳完成

  $scope.completed = function(response) {
    $scope.name = '';
    $scope.update_languages();
  };
});

ngUpload 上傳完後去執行 $scope.completed(content) 這個 method 去更新 languages list,和清掉 input:name 的內容。

而在 model/language.rb 中新增必填屬性 name

validates :name, presence: true

在 languages list 上新增編輯的功能

我們想賦予 li 能夠編輯和刪除的功能,我們修改 li 的內容。

<li class="list-group-item" ng-repeat="language in languages" ng-controller="itemCtrl">
  <div ng-if="!isEdit">
    {{ language.name }}
    <button class="btn btn-danger btn-xs pull-right"  ng-click="delete()">Remove</button>
    <button class="btn btn-default btn-xs pull-right" ng-click="edit_toggle(true)">Edit</button>
  </div>
  <div ng-if="isEdit">
    <input ng-model="onedit_language.name">
    <button class="btn btn-default btn-xs pull-right" ng-click="edit_toggle(false)">Cancel</button>
    <button class="btn btn-primary btn-xs pull-right" ng-click="update()">Update</button>
  </div>
</li>

我們新增 ng-controller="itemCtrl" ,並且透過 $scope.isEdit 來判斷是否進入編輯模式。

languageApp.controller('itemCtrl', function($scope, $http) {
  $scope.isEdit = false;

  // 展開或關閉編輯模式

  $scope.edit_toggle = function(isEdit) {
    $scope.isEdit = isEdit;
    $scope.onedit_language = angular.copy($scope.language);
  };

  $scope.update = function() {
    $http.put('/languages/' + $scope.onedit_language.id + '.json', $scope.onedit_language).
    success(function(response) {
      $scope.update_languages();
    });
  };

  $scope.delete = function() {
    $http.delete('/languages/' + $scope.language.id + '.json').
    success(function(response) {
      $scope.update_languages();
    });
  };
});

$scope.update()$scope.delete() 中,我們使用 $http.put$http.delete 和 LanguageController 互動,

並且在執行完後更新 languages list。

看看成果吧!!

$ rails s

瀏覽剛剛寫好的頁面 http://localhost:3000/languages/ng_index

原始碼在這裏:Github Repo

趕快來嘗試看看吧!

← Rails 與 AngularJS 混搭風 - 1. Basic Rails 與 AngularJS 混搭風 - 3. Unit Testing →
 
comments powered by Disqus