Programmatical form submission

AngularJS 做為一個以建立 Single Page App 為目標的 framework,當然是不希望你在 web app 中碰到需要與後端做資料溝通時會需要 refresh 整個網頁,因此它提供了完整的 HTML <form> 解決方案,諸如:

  • 透過 form directive 攔截預設的 form submission
  • 多樣化而且可以自訂的 validation 功能
  • 提供 ng-submit 以及 [type=“submit”] 上的 ng-click 作為真正送出 form 時候的程式動作

問題

然而,我們不時的還是會碰到真正需要 page refresh 的 form submission

  • 該功能還沒有做 API 的時候(容易發生在從 backend-based 轉移成為 SPA 的時候)
  • 介接第三方網站功能的時候(例如金流業者)

貼心如 AngularJS,當然也理解你的困難,於是

Angular prevents the default action (form submission to the server) unless the <form> element has an action attribute specified.

可惜天下的事情通常都不會如此順利(蛤)。

問題在於,有加上 action 屬性的 <form> 元素只有在直接被使用者操作的情況下才能夠成功送出。

於是我們還是會碰到 AngularJS natively 提供的 solution 無法解決的問題:

  • 需要先與後端確認資料正確性之後再送出 - 無直接操作送出的動作
  • 需要送出隱藏的 <form> - 無法透過使用者操作

過往我們怎麼做的?在 AngularJS 裡我們又該何去何從呢?

// jquery way
$('form')[0].submit()

// angular way?
//   $document injectable
$document.find('form')[0].submit();
//   $element injectable
$element.find('form')[0].submit();

這樣當然不是好的做法!

  • Controller 裡面不要做 DOM 操作
  • 如果純靠 jQLite 的 selector 能力(即不使用 jQuery),頁面上有多個 <form> 的時候使用起來是相對難以選擇到真正的目標(因為 jqLite 的 selector 只支援 tag)

directive: submit-it

/* file: submitIt.js */

angular.module('submitIt', [])

/**
 * @ngdoc directive
 * @description form submission trigger
 *
 * @param {boolean} submit-it submit form on `true`
 * @param {string}  si-action submit action(optional)
 * @param {string}  si-method submit method(optional)
 */
.directive('submitIt', [
         '$timeout',
function ($timeout) {
  return {
    restrict: 'A',
    link: function postLink(scope, iElm, iAttrs) {
      var _submitWatch = scope.$watch(iAttrs.submitIt, _onSubmitWatch),
          _savedAction, _savedMethod;

      iAttrs.$observe('siAction', function (val) {
        if (val) {
          _savedAction = val;
        }
      });

      iAttrs.$observe('siMethod', function (val) {
        if (val) {
          _savedMethod = val;
        }
      });

      function _onSubmitWatch(submit) {
        if (submit && iElm[0].tagName === 'FORM') {
          if (_savedAction) {
            iElm.attr('action', _savedAction);
          }

          if (_savedMethod) {
            iElm.attr('method', _savedMethod);
          }

          $timeout(function () {
            iElm[0].submit();
          });
          _submitWatch();
        }
      }
    }
  };
}]);

用法

  • submit-it 本身做為「送出 <form>」的觸發器,其值為 true 時即會送出 <form>
  • 附加的 si-actionsi-method 則是提供了於送出 <form> 前的瞬間設定 <form> 的 action 及 method 的功能

Demo

在這個 demo 中可以看到該按鈕其實沒有擺在 <form> 裡面,並且點擊後直接對 http://www.google.com 發出了 GET request,又由於 target_top,整頁都將被導至 http://www.google.com。