<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      wexin

      簡潔易用的表單數據設置和收集管理組件

      這篇文章要分享的是我在做表單界面開發的一部分經驗,關于表單數據設置和收集這一塊的。整體而言,這篇文章總結的東西有以下的特點:
      1)api簡單,使用起來很容易;
      2)簡化了表單新增和編輯,可以讓新增和編輯使用同一個表單頁面;
      3)基本上與UI分離,所以很容易應用到各類項目的開發當中。
      涉及到的組件不止一個,而且將來還會擴充,這些組件都是根據以前的工作經驗開發出來的,沒有很高級的東西,每個組件的代碼都很少,所以即使文中有介紹不到的地方,你也能通過閱讀代碼來詳細了解。不過我想大部分人應該沒有見過這樣的使用方式(除了我畢業的時候進的那家公司的同事),我上家公司的朋友剛開始看到我用這種寫法的時候都不太理解,但是大家最后都接受并認可了這種用法,因為在開發的時候效率確實還挺高的,這也是我寫這篇文章分享出來的目的。

      本文相關的代碼我都放在github上面去了,原來我都直接上傳在博客園,后來發現有的時候要改點東西每次都得重新上傳,挺不方便的,還是直接git簡單點,另外git還可以通過gh-pages分支來顯示靜態內容,正好可以用來查看demo。

      代碼地址:
      https://github.com/liuyunzhuge/blog/tree/master/form
      demo地址:
      http://liuyunzhuge.github.io/blog/form/dist/html/demo1.html?mode=1
      http://liuyunzhuge.github.io/blog/form/dist/html/demo1.html?mode=2

      關于demo的簡單說明:

      這兩個地址分別用來模擬了一個表單頁面的新增和編輯時的場景,我用mode這個url參數來區分當前這個頁面是新增還是編輯的狀態,mode=1表示新增,mode=2表示編輯。在這個頁面里面一共有9個表單元素:
      id: 用的是text[type=”hidden”]
      name: 用的是text[type=”text”]
      birthday: 用的是text[type=”text”],但是帶日期下拉選擇的功能
      hobby: 是checkbox
      gender: 是radio
      work:是單選的select
      industry:是多選的select
      desc:是textarea
      detailDesc: 也是textarea,只不過是用富文本編輯器呈現的。

      這9個元素涵蓋了常見了的表單元素類型,即使將來要增加其它的類型,也逃脫不了使用基本的表單元素來存取值,比如你可能見過的帶下拉框或者輸入提示的文本框,從本質上來說,在我們獲取該字段元素的時候,只會從文本框獲取值,而跟下拉框或者輸入提示的框沒有關系,下拉框僅僅起一個輔助錄入的作用,跟我們表單數據收集沒有關系,demo中生日這個表單元素就是一個很好的說明,它雖然用到了日期選擇的插件,但是即使沒有這個插件,也不會影響到文本框值的存取。我把這個說明出來其實是想表達,在表單數據收集或設置的時候,應該考慮一下分離的思想,只有這樣寫出來的組件才能夠不受項目的影響。關于這部分的思想,我推薦一篇更好的文章,感興趣的可以深入閱讀:

      順勢而為,HTML發展與UI組件設計進化

      demo相關的html文件是src/html/demo1.html,js文件是src/js/app/demo1.js。整個項目用了seajs做模塊化,用了gulp來做簡單構建,還用到以前的幾篇博客總結的一些東西:

      1)詳解Javascript的繼承實現:提供一個class.js,用來定義javascript的類和構建類的繼承關系;
      2)jquery技巧之讓任何組件都支持類似DOM的事件管理:提供一個eventBase.js,用來給任意組件實例提供類似DOM的事件管理功能。

      在src/js/app/demo1.js中你可以看到,demo這個表單,在點擊保存,收集數據的時候是多么的簡單:

      image 

      demo如果想在本地運行起來的話,可以參考github上readme.md提供的說明。下面開始詳細介紹這整套組件的內容。

      1. 前言

      在傳統的表單界面開發中,我們可能會碰到以下這些問題:

      1)表單新增跟表單編輯到底是一個頁面還是兩個頁面?
      如果用兩個頁面,開發的時候好像很方便,但是將來維護的時候會很麻煩,因為會存在大量的重復代碼。所以我個人更傾向于用一個頁面,但是用一個頁面的話,在設置表單元素的初始值時會加不少重復的邏輯判斷,因為大部分表單元素在新增的時候初始值都是空的,而在編輯的時候可能都是有值的,而我們的表單元素只有一個value屬性,如果我們是通過jsp或php等模板來個表單元素賦值,這個處理起來也會很繁瑣;

      2)checkbox radio以及設置了multiple屬性的select元素在設置value的時候也很繁瑣,因為它們都不是直接通過value屬性來確定初始化值的,而是通過checked或selected屬性來判斷的,所以在設置初始值的時候需要判斷每一個checkbox radio或select的option元素的value值與要設定的初始值是否相等才能給它添加checked或selected屬性;

      3)select元素的下拉內容有可能不是已知的,需要另外請求再渲染出來,這個時候如果每次都單獨為這種需求的select下ajax邏輯顯得太低效了

      4)在收集表單數據并提交到后臺的時候,現有的DOM方式在獲取的時候不是很方便,雖然jquery簡化了這部分的處理,但是它沒有約定,會將所有的表單數據都收集起來,有時候這里面會有一些不必要的數據,如果能夠提前約定好要收集的表單元素,就可以避免收集不必要的數據;

      5)瀏覽器標準事件中給所有的表單元素都提供了change事件,但是這個事件有時候還不夠方便,要是能把這個事件拆分成一對事件,比如beforeChange跟afterChange,就能適應更復雜的需求場景。從名字大概能猜到這兩個事件的作用和觸發的時機,這兩個事件我沒法說它的具體作用到底比單個的change事件強多少,但是從我以前做ERP管理軟件的經驗來說,beforeChange能夠起到很多控制作用,afterChange也能夠代替原來的change事件;

      6)每個表單元素都有些相似的屬性或者相似的行為,如果我們把這些相似的東西都抽象出來,每個表單元素的使用將會變得非常簡單,而且將來要擴充像下拉輸入這類的表單元素也都會很容易。

      為了解決這些問題我的思路是:

      1)將頁面分為3種模式,新增,編輯,查看模式,分別用url參數mode=1,mode=2,mode=3來區分,新增模式表示當前頁面正在錄入新的數據,是還沒在數據庫保存過的;編輯模式表示當前頁面正在編輯已經在數據庫中存在的數據;查看模式表示當前頁面正在查看從數據庫中查詢出的數據,但是只能看不能改。也許有人會說查看模式沒有什么用處,但是在ERP管理系統數據的修改控制是很重要的,所以曾經公司的開發平臺里面用了這三種模式來控制頁面的狀態。不過這個做法有一定的風險,就是知曉這個原理的人,可能通過修改url后面的參數來看到不用的頁面狀態,比如他只有權限能看到mode=3的頁面,但是只要將地址里的mode=3改成mode=2再回車,就能進入編輯的頁面狀態,所以在一些關鍵的邏輯的處理中,必須用數據的業務狀態來做判斷,而不能使用mode,mode僅僅能做到在UI層面的控制;

      2)用defaultValue這個option來指定表單元素在新增時候的初始值,用value屬性來表示表單元素在編輯或查看模式時的初始值,這樣在jsp或者php模板里面,我們只要把初始值寫在不同的位置即可,當前端根據mode初始化完組件之后就會顯示正確的初始值,而defaultValue這個option我們可以通過data-default-value直接寫在表單元素的html上,value屬性本身就是表單元素的標準屬性,所以可以直接寫,如:
      image

      3)我把表單元素的相似的屬性跟行為統一封裝到了formFieldBase這個組件里面,其它各個表單元素只要繼承它即可。

      下面先來看看formFieldBase.js的內容,它是最基礎最重要的一個組件。

      2. formFieldBase.js

      代碼如下:

      define(function (require, exports, module) {
          var $ = require('jquery'),
              EventBase = require('mod/eventBase'),
              Class = require('mod/class');
      
          var DEFAULTS = {
              name: '',//字段名稱
              type: '',//字段類型:text checkbox radio date number select ueditor等
              value: '',//在mode為2,3時顯示的值
              defaultValue: '',//在mode為1時顯示的值
              mode: 1,//可選值有1,2,3,分別代表字段屬于新增,編輯和查看模式
              onBeforeChange: $.noop,//在字段相關表單元素的value的值發生改變前觸發,這個回調可以對字段的值做一些校驗
              onAfterChange: $.noop,//在字段相關表單元素的value的值發生改變后觸發
              onInit: $.noop//在字段初始化完畢之后調用
          };
      
          function parseValue(value) {
              //特殊情況處理:當initValue是一個函數或者對象時
              typeof(value) == 'function' && (value = value());
              typeof(value) == 'object' && (value = JSON.stringify(value));
              return $.trim(value);
          }
      
          var FormFieldBase = Class({
              instanceMembers: {
                  init: function (element, options) {
                      var $element = this.$element = $(element);
                      //通過this.base調用父類EventBase的init方法
                      this.base($element);
      
                      var opts = this.options = this.getOptions(options), that = this;
                      //獲取field的name
                      //name有三種來源:opts.name,data-name屬性以及name屬性
                      this.name = opts.name || $element.attr('name');
                      //獲取field的mode值:1,2,3分別代表新增,編輯和查看模式
                      this.mode = ~~opts.mode;
                      //獲取field的初始值
                      this.initValue = (function () {
                          var initValue;
                          if (that.mode === 1) {
                              //新增模式時使用defaultValue作為初始值
                              initValue = opts.defaultValue;
                          } else {
                              //非新增模式時一般情況下用value作為初始值
                              //如果value值為空,判斷字段對應的元素有沒有val的jquery方法
                              //有的話通過該方法再獲取一次值
                              initValue = $.trim(opts.value) == '' ?
                                  (('val' in $element) && $element.val()) :
                                  opts.value;
                          }
      
                          return parseValue(initValue);
                      })();
                      //獲取field的類型
                      this.type = opts.type;
      
                      delete opts.value;
                      delete opts.defaultValue;
                      delete opts.mode;
                      delete opts.name;
                      delete opts.type;
      
                      //注冊兩個基本事件的監聽
                      if (typeof(opts.onAfterChange) === 'function') {
                          this.on('afterChange', $.proxy(opts.onAfterChange, this));
                      }
      
                      if (typeof(opts.onBeforeChange) === 'function') {
                          this.on('beforeChange', $.proxy(opts.onBeforeChange, this));
                      }
      
                      if (typeof(opts.onInit) === 'function') {
                          this.on('formFieldInit', $.proxy(opts.onInit, this));
                          this.on('formFieldInit', $.proxy(function(){
                              if(this.mode === 3) {
                                  this.disable();
                              }
                          }, this));
                      }
      
                      $element.data('formField', this);
                  },
                  getOptions: function (options) {
                      var defaults = this.getDefaults(),
                          _opts = $.extend({}, defaults, this.$element.data() || {}, options),
                          opts = {};
      
                      //保證返回的對象內容項始終與當前類定義的DEFAULTS的內容項保持一致
                      for (var i in defaults) {
                          if (Object.prototype.hasOwnProperty.call(defaults, i)) {
                              opts[i] = _opts[i];
                          }
                      }
      
                      return opts;
                  },
                  getDefaults: function () {
                      return DEFAULTS;
                  },
                  triggerInit: function () {
                      this.trigger('formFieldInit');
                  },
                  destroy: function () {
                      this.base();
                      this.$element.data('formField', null);
                      this.options = undefined;
                      this.$element = undefined;
                      this.name = undefined;
                      this.initValue = undefined;
                      this.mode = undefined;
                      this.type = undefined;
                  },
                  setValue: function (value, trigger) {
                      value = $.trim(parseValue(value));
      
                      //如果跟原來的值相同則不處理
                      if (value === $.trim(this.getValue())) return;
      
                      //將input的值設置成value
                      this.setFieldValue(value);
      
                      this._setValue(value, trigger);
                  },
                  //子類實現這個
                  _setValue: $.noop,
                  setFieldValue: $.noop,
                  getValue: $.noop,
                  enable: $.noop,
                  disable: $.noop,
                  reset: $.noop
              },
              extend: EventBase,
              staticMembers: {
                  DEFAULTS: DEFAULTS
              }
          });
      
          return FormFieldBase;
      });

      formFieldBase,為所有的表單元素組件定義了以下基本的option:
      image
      其中:
      name:用來唯一標識一個表單元素,不能重復。如果某個需求中,某個字段需要可能用到多個表單元素,可以在name屬性上添加一些索引前綴或后綴來處理。它除了可以在組件初始化的時候通過options傳遞給組件的構造函數,還可以直接在組件相關的元素上通過name屬性或者data-name來屬性來設置。
      type:用來指定這個組件的類型,它是自定義的,跟input元素上的type完全沒有關系。每一個繼承formFieldBase的組件都有一個type。它要么是options來傳遞,要么就是通過data-type來傳遞,目前已開發的組件有formFieldCheckbox,formFieldDate,formFieldRadio,formFieldText,formFieldSelect,formFieldUeditor,對應的type值是:text,checkbox,radio,select,date跟ueditor。
      value:編輯或查看模式時的初始值。
      defaultValue: 新增模式時的初始值。
      onBeforeChange: 它是beforeChange事件的回調,在值發生改變前觸發,在該事件中,如果通過e.preventDefault()阻止了默認行為,表單元素的值將會被重置為上一次修改的后的值,并且不會再觸發后面的afterChange事件。
      onAfterChange: 它是afterChange事件的回調,在值發生改變后觸發。
      onInit:它formFieldInit事件的回調,這個事件表示組件何時初始化完畢。觸發的時機由具體實現的子類來決定,formFieldBase提供了triggerInit()方法,子類可通過調用這個方法來觸發formFieldInit,之所以這么做,是因為各個表單元素觸發這個事件的時機是不定的,所以不能在formFieldBase里面來做觸發,formFieldBase僅僅提供統一的事件注冊,通常在子類的init方法的最后被觸發,但也可能不是,比如formFieldSelect組件里面,你就可以看到不一樣的觸發邏輯。

      每個表單元素的初始值都是根據mode來判斷獲取的,在mode為1的時候只會通過defaultValue這個option來獲取初始值,在mode=2的時候,還會通過jquery的val方法來進一步獲取值。初始值的設置通過調用reset方法即可,調用時機由各個子類的去決定,一般都是在子類的init方法里面。

      通過formFieldBase為所有的表單元素提供了一下api方法:

      1) setValue(value, trigger)
      用來給表單元素設置值,第二個參數可選,默認調用這個方法的時候都會觸發表單元素的change事件,不然beforeChange跟afterChange都無法正確管理。只有當第二個參數為false的時候,才不會觸發change事件。formFieldBase提供了_setValue方法,子類不需要覆蓋setValue方法,只要覆蓋_setValue方法即可。這么做的原因是setValue方法里面有一些公共的邏輯,可以抽象到formFieldBase里面去。這樣當調用子類的setValue方法時將會調用父類的setValue方法,最后通過_setValue這個方法來實現不同的子類的邏輯。

      2)getValue()
      獲取表單元素的值。

      3)enable()
      啟用

      4)disable()
      禁用

      5)reset()
      重置為初始值,不會觸發change,beforeChange以及afterChange事件。

      希望前面這些內容能夠讓你把formFieldBase這個組件的一些我自己的想法看的明白,如果有不明白的可以直接私信跟我交流。下面基于這個formFieldBase,來看下各個不同的表單元素組件是如何實現的。

      3. formFieldText.js

      代碼如下:

      define(function (require, exports, module) {
          var $ = require('jquery'),
              FormCtrlBase = require('mod/formFieldBase'),
              Class = require('mod/class');
      
          var DEFAULTS = $.extend({}, FormCtrlBase.DEFAULTS);
      
          var FormFieldText = Class({
              instanceMembers: {
                  init: function (element, options) {
                      //通過this.base調用父類FormCtrlBase的init方法
                      this.base(element, options);
                      //設置初始值
                      this.reset();
      
                      var that = this,
                          $element = this.$element;
      
                      //監聽input元素的change事件,并最終通過beforeChange和afterChange來管理
                      $element.on('change', function (e) {
                          var val = that.getValue(), event;
      
                          if(val === that.lastValue) return;
      
                          that.trigger((event = $.Event('beforeChange')), val);
                          //判斷beforeChange事件有沒有被阻止默認行為
                          //如果有則把input的值還原成最后一次修改的值
                          if (event.isDefaultPrevented()) {
                              that.setFieldValue(that.lastValue);
                              $element.focus().select();
                              return;
                          }
      
                          //記錄最新的input的值
                          that.lastValue = val;
                          that.trigger('afterChange', val);
                      });
      
                      this.triggerInit();
                  },
                  getDefaults: function () {
                      return DEFAULTS;
                  },
                  _setValue: function (value, trigger) {
                      //只要trigger不等于false,調用setValue的時候都要觸發change事件
                      trigger !== false && this.$element.trigger('change');
                  },
                  setFieldValue: function (value) {
                      var $element = this.$element,
                          elementDom = this.$element[0];
                      if (elementDom.tagName.toUpperCase() === 'TEXTAREA') {
                          var v = ' ' + value;
                          elementDom.value = v;
                          elementDom.value = v.substring(1);
                      } else {
                          $element.val(value);
                      }
                  },
                  getValue: function () {
                      return this.$element.val();
                  },
                  disable: function () {
                      this.$element.addClass('disabled').prop('readonly', true);
                  },
                  enable: function () {
                      this.$element.removeClass('disabled').prop('readonly', false);
                  },
                  reset: function () {
                      this.setFieldValue(this.initValue);
                      this.lastValue = this.initValue;
                  }
              },
              extend: FormCtrlBase,
              staticMembers: {
                  DEFAULTS: DEFAULTS
              }
          });
      
          return FormFieldText;
      });

      這個組件是最簡單的一個,所以就不過多介紹代碼,簡單說下它的用法。非checkbox和radio的input元素以及textarea元素都能使用它:

      <input class="form-control form-field"
           name="id"
           data-type="text"
           data-default-value=""
           value="1"
           type="hidden"
           placeholder="">
      
      <input class="form-control form-field"
           name="name"
           data-type="text"
           data-default-value=""
           value="felix"
           type="text"
           placeholder="">
      
      <textarea class="form-control form-field"
                name="desc"
                data-type="text"
                data-default-value=""
                rows="3"
                placeholder="">felix</textarea>

      如果是直接通過formFieldText構造函數可以這么用:

      new FormFieldText('#name',{
          onInit: function(){
              console.log(this.getValue());
          },
          onBeforeChange: function(e, val){
              if(val == 'xx') {
                  e.preventDefault();
              }
          }
      });

      (這個例子只是為了說明FormFieldText這個組件的用法,沒有任何需求背景)。

      4. formFieldCheckbox.js

      代碼說明:

      define(function (require, exports, module) {
          var $ = require('jquery'),
              FormCtrlBase = require('mod/formFieldBase'),
              Class = require('mod/class');
      
          var DEFAULTS = $.extend({
                  //defaultValue 以及value使用checkbox的值
                  useInputValue: {
                      forDefaultValue: false,
                      forValue: false
                  }
              }, FormCtrlBase.DEFAULTS),
              INPUT_SELECTOR = 'input[type=checkbox]';
      
          var FormFieldCheckbox = Class({
              instanceMembers: {
                  init: function (element, options) {
      
                      //通過this.base調用父類FormCtrlBase的init方法
                      this.base(element, options);
      
                      var that = this,
                          $element = this.$element;
      
                      //獲取所有的input元素
                      var $inputs = this.$inputs = $element.find(INPUT_SELECTOR);
                      //設置它們的name屬性,以便能夠呈現復選的效果
                      $inputs.prop('name', this.name);
      
                      var opts = this.options;
                      if((this.mode == 1 && opts.useInputValue.forDefaultValue) ||
                          opts.useInputValue.forValue) {
                          this.initValue = this.getValue();
                      }
      
                      //設置初始值
                      this.reset();
      
                      //監聽input元素的change事件,并最終通過$element的beforeChange和afterChange來管理
                      $element.on('change', INPUT_SELECTOR, function (e) {
                          var val = that.getValue(), event;
      
                          if (val === that.lastValue) return;
      
                          that.trigger((event = $.Event('beforeChange')), val);
                          //判斷beforeChange事件有沒有被阻止默認行為
                          //如果有則把input的值還原成最后一次修改的值
                          if (event.isDefaultPrevented()) {
                              that.setFieldValue(that.lastValue);
                              return;
                          }
      
                          //記錄最新的input的值
                          that.lastValue = val;
                          that.trigger('afterChange', val);
                      });
      
                      this.triggerInit();
                  },
                  getDefaults: function () {
                      return DEFAULTS;
                  },
                  _setValue: function (value, trigger) {
                      //只要trigger不等于false,調用setValue的時候都要觸發change事件
                      trigger !== false && this.$inputs.eq(0).trigger('change');
                  },
                  setFieldValue: function (value) {
                      this.$inputs.val(value.split(','));
                  },
                  getValue: function () {
                      var val = [];
                      this.$inputs.filter(':checked').each(function () {
                          val.push(this.value);
                      });
                      return val.join(',');
                  },
                  disable: function () {
                      this.$element.addClass('disabled');
                      this.$inputs.prop('disabled', true);
                  },
                  enable: function () {
                      this.$element.removeClass('disabled');
                      this.$inputs.prop('disabled', false);
                  },
                  reset: function () {
                      this.setFieldValue(this.initValue);
                      this.lastValue = this.initValue;
                  }
              },
              extend: FormCtrlBase,
              staticMembers: {
                  DEFAULTS: DEFAULTS
              }
          });
      
          return FormFieldCheckbox;
      });

      代碼也很簡單,不過有以下幾點值得說明:

      1)getValue時如果有多個checkbox被選中,那么最后會把多個值以英文逗號分隔的方式返回
      2)setValue的時候如果一次性設置多個checkbox被選中,得傳入一個英文逗號分隔的字符串的值
      3)為了避免去設定各個checkbox的checked屬性,這個組件并不是針對單個的checkbox元素來使用的,而是把這些checkbox的某個公共的父元素作為這個組件的關鍵元素,所以這個組件在使用的時候,要用data-name,data-value來指定元素的名稱和編輯時的初始值。

      舉例如下:

      <div class="col-xs-5 checkbox checkbox-md form-field"
           data-name="hobby"
           data-type="checkbox"
           data-default-value=""
           data-value="電影,音樂">
        <label>
          <input type="checkbox" value="電影">
          <i class="fa checked"></i>
          電影
        </label>
        <label>
          <input type="checkbox" value="音樂">
          <i class="fa checked"></i>
          音樂
        </label>
        <label>
          <input type="checkbox" value="游戲">
          <i class="fa checked"></i>
          游戲
        </label>
      </div>

      注意以上代碼中的div,它才是真正使用formFieldCheckbox的element。還需要說明的是,盡管這個div元素上還有一些特殊的css,如checkbox,checkbox-md,這些僅僅是UI相關的,跟js邏輯沒有關系。

      初始化的方式是:

      new FormFieldCheckbox('#hobby',{
          onInit: function(){
              console.log(this.getValue());
          },
          onBeforeChange: function(e, val){
              if(val == 'xx') {
                  e.preventDefault();
              }
          }
      });

      5. formFieldRadio.js

      僅展示代碼,要說明的東西跟formFieldCheckbox區別很小:

      define(function (require, exports, module) {
          var $ = require('jquery'),
              FormCtrlBase = require('mod/formFieldBase'),
              Class = require('mod/class');
      
          var DEFAULTS = $.extend({}, FormCtrlBase.DEFAULTS),
              INPUT_SELECTOR = 'input[type=radio]';
      
          var FormFieldRadio = Class({
              instanceMembers: {
                  init: function (element, options) {
                      //通過this.base調用父類FormCtrlBase的init方法
                      this.base(element, options);
      
                      var that = this,
                          $element = this.$element;
      
                      //獲取所有的input元素
                      var $inputs = this.$inputs = $element.find(INPUT_SELECTOR);
                      //設置它們的name屬性,以便能夠呈現復選的效果
                      $inputs.prop('name', this.name);
      
                      //設置初始值
                      this.reset();
      
                      //監聽input元素的change事件,并最終通過$element的beforeChange和afterChange來管理
                      $element.on('change', INPUT_SELECTOR, function (e) {
                          var val = that.getValue(), event;
      
                          if(val === that.lastValue) return;
      
                          that.trigger((event = $.Event('beforeChange')), val);
                          //判斷beforeChange事件有沒有被阻止默認行為
                          //如果有則把input的值還原成最后一次修改的值
                          if (event.isDefaultPrevented()) {
                              that.setFieldValue(that.lastValue);
                              return;
                          }
      
                          //記錄最新的input的值
                          that.lastValue = val;
                          that.trigger('afterChange', val);
                      });
      
                      this.triggerInit();
                  },
                  getDefaults: function () {
                      return DEFAULTS;
                  },
                  _setValue: function (value, trigger) {
                      //只要trigger不等于false,調用setValue的時候都要觸發change事件
                      trigger !== false && this.$inputs.eq(0).trigger('change');
                  },
                  setFieldValue: function (value) {
                      if (value !== '') {
                          this.$inputs.filter('[value="' + value + '"]').prop('checked', true);
                      } else {
                          this.$inputs.filter(':checked').each(function () {
                              this.checked = false;
                          });
                      }
                  },
                  getValue: function () {
                      return this.$inputs.filter(':checked').val();
                  },
                  disable: function () {
                      this.$element.addClass('disabled');
                      this.$inputs.prop('disabled', true);
                  },
                  enable: function () {
                      this.$element.removeClass('disabled');
                      this.$inputs.prop('disabled', false);
                  },
                  reset: function () {
                      this.setFieldValue(this.initValue);
                      this.lastValue = this.initValue;
                  }
              },
              extend: FormCtrlBase,
              staticMembers: {
                  DEFAULTS: DEFAULTS
              }
          });
      
          return FormFieldRadio;
      });

      7. formFieldSelect.js

      代碼如下:

      define(function (require, exports, module) {
          var $ = require('jquery'),
              FormCtrlBase = require('mod/formFieldBase'),
              Class = require('mod/class'),
              Ajax = require('mod/ajax');
      
          var DEFAULTS = $.extend({}, FormCtrlBase.DEFAULTS, {
              url: '',
              textField: 'text',
              valueField: 'value',
              autoAddEmptyOption: true,
              emptyOptionText: '&nbsp;',
              parseAjax: function (res) {
                  if (res.code == 1) {
                      return res.data || [];
                  } else {
                      return [];
                  }
              }
          });
      
          var FormFieldSelect = Class({
              instanceMembers: {
                  init: function (element, options) {
                      //通過this.base調用父類FormCtrlBase的init方法
                      this.base(element, options);
      
                      var opts = this.options, _ajax;
                      if (!opts.url) {
                          //設置初始值
                          this.reset();
                      } else {
                          _ajax = Ajax.get(opts.url);
                      }
      
                      var that = this,
                          $element = this.$element;
      
                      //監聽input元素的change事件,并最終通過beforeChange和afterChange來管理
                      $element.on('change', function (e) {
                          var val = that.getValue(), event;
      
                          if (val === that.lastValue) return;
      
                          that.trigger((event = $.Event('beforeChange')), val);
                          //判斷beforeChange事件有沒有被阻止默認行為
                          //如果有則把input的值還原成最后一次修改的值
                          if (event.isDefaultPrevented()) {
                              that.setFieldValue(that.lastValue);
                              $element.focus();
                              return;
                          }
      
                          //記錄最新的input的值
                          that.lastValue = val;
                          that.trigger('afterChange', val);
                      });
      
                      if (!_ajax) {
                          this.triggerInit();
                      } else {
                          _ajax.done(function (res) {
                              var data = opts.parseAjax(res);
                              that.render(data);
                              that.reset();
                              that.triggerInit();
                          })
                      }
                  },
                  getDefaults: function () {
                      return DEFAULTS;
                  },
                  _setValue: function (value, trigger) {
                      //只要trigger不等于false,調用setValue的時候都要觸發change事件
                      trigger !== false && this.$element.trigger('change');
                  },
                  render: function (data, clear) {
                      if (Object.prototype.toString.call(data) != '[object Array]') {
                          data = [];
                      }
      
                      var opts = this.options,
                          textField = opts.textField,
                          valueField = opts.valueField,
                          l = data.length,
                          $element = this.$element;
      
                      if(clear === true){
                          $element.html('');
                          this.lastValue = '';
                      }
      
                      if (opts.autoAddEmptyOption) {
                          var o = {};
                          o[textField] = opts.emptyOptionText;
                          o[valueField] = '';
                          //other fileds ?
                          l = data.unshift(o);
                      }
      
                      var html = [];
                      for (var i = 0; i < l; i++) {
                          html.push(['<option value="',
                              data[i][valueField],
                              '">',
                              data[i][textField],
                              '</option>'].join(''));
                      }
      
                      l && $element.append(html.join(''));
                  },
                  setFieldValue: function (value) {
                      this.$element.val(value.split(','));
                  },
                  getValue: function () {
                      var value = this.$element.val();
                      if (Object.prototype.toString.call(value) === '[object Array]') {
                          return value.join(',');
                      }
                      return value === null ? '' : value;
                  },
                  disable: function () {
                      this.$element.addClass('disabled').prop('disabled', true);
                  },
                  enable: function () {
                      this.$element.removeClass('disabled').prop('disabled', false);
                  },
                  reset: function () {
                      this.setFieldValue(this.initValue);
                      this.lastValue = this.initValue;
                  }
              },
              extend: FormCtrlBase,
              staticMembers: {
                  DEFAULTS: DEFAULTS
              }
          });
      
          return FormFieldSelect;
      });

      這個組件功能相對多一點,它還提供了幾個額外的option:

      url: 默認是空的,如果有值的話,將在初始化的時候通過該值發起ajax請求加載下拉的數據。
      textField: 只有在url不為空的情況下才會用到,表示ajax返回的數據中哪個字段是用來顯示<option>的文本的。
      valueField: 只有在url不為空的情況下才會用到,表示ajax返回的數據中哪個字段是用來顯示<option>的value的。
      autoAddEmptyOption: 只有在url不為空的情況下才會用到,表示是否自動添加一個空的option。
      emptyOptionText: 只有在url不為空的情況下才會用到,表示空option的文本。
      parseAjax: 回調,只有在url不為空的情況下才會用到,用來解析ajax返回的數據,需要返回一個數組,存放需要渲染成下拉內容的數據。

      還需要說明的是:
      1)getValue的時候,如果有多個選中的option,它們的值將以英文逗號分隔的形式返回;
      2)setValue的時候,如果要一次性設置多個option的選中狀態,得以英文逗號分隔的字符串傳值;
      3)它還提供了一個render(data, clear),接收2個參數,第二個參數可選,可以用一份新的數據來替換下拉框的內容,第二個參數如果為true,則會把之前的下拉內容清空。

      實際用法:

      <select class="form-control form-field"
            name="work"
            data-type="select"
            data-default-value=""
            data-value="UI設計">
      <option value="">請選擇職業</option>
      <option value="前端開發">前端開發</option>
      <option value="UI設計">UI設計</option>
      <option value="JAVA后端">JAVA后端</option>
      </select>

      構造函數的使用方式與前面的相同,所以不再詳細介紹了。

      8. formFieldDate.js

      代碼如下:

      define(function (require, exports, module) {
          var $ = require('jquery'),
              FormCtrlBase = require('mod/formFieldBase'),
              hasOwn = Object.prototype.hasOwnProperty,
              Class = require('mod/class');
      
          //引入picker組件
          require('mod/datepicker');
      
          var DEFAULTS = $.extend({}, FormCtrlBase.DEFAULTS),
              DATEPICKER_DEFAULTS = $.extend($.fn.datepicker.defaults, {
                  autoclose: true,
                  language: 'zh-CN',
                  format: 'yyyy-mm-dd',
                  todayHighlight: true
              });
      
          function getPickerOptions(options) {
              var opts = {};
              for (var i in DATEPICKER_DEFAULTS) {
                  if (hasOwn.call(DATEPICKER_DEFAULTS, i) && (i in options)) {
                      opts[i] = options[i];
                  }
              }
              return opts;
          }
      
          var FormFieldDate = Class({
              instanceMembers: {
                  init: function (element, options) {
                      //通過this.base調用父類FormCtrlBase的init方法
                      this.base(element, options);
                      //設置初始值
                      this.reset();
      
                      //pickerOptions是datepick組件需要的
                      this.pickerOptions = getPickerOptions(this.options);
      
                      var that = this,
                          $element = this.$element;
      
                      //監聽input元素的change事件,并最終通過beforeChange和afterChange來管理
                      $element.on('change', function (e) {
                          var val = that.getValue(), event;
      
                          if(val === that.lastValue) return;
      
                          that.trigger((event = $.Event('beforeChange')), val);
                          //判斷beforeChange事件有沒有被阻止默認行為
                          //如果有則把input的值還原成最后一次修改的值
                          if (event.isDefaultPrevented()) {
                              that.setFieldValue(that.lastValue);
                              return;
                          }
      
                          //記錄最新的input的值
                          that.lastValue = val;
                          that.trigger('afterChange', val);
                      });
      
                      //初始化datepicker組件
                      $element.datepicker(this.pickerOptions);
      
                      this.triggerInit();
                  },
                  getDefaults: function () {
                      return DEFAULTS;
                  },
                  _setValue: function (value, trigger) {
                      //只要trigger不等于false,調用setValue的時候都要觸發change事件
                      trigger !== false && this.$element.trigger('change');
                  },
                  setFieldValue: function (value) {
                      this.$element.val(value).datepicker('update').blur();
                  },
                  getValue: function () {
                      return this.$element.val();
                  },
                  disable: function () {
                      //datapicker組件沒有disable的方法
                      //所以禁用和啟用只能通過destroy后重新初始化來實現
                      this.$element.addClass('disabled').prop('readonly', true).datepicker('destroy');
                  },
                  enable: function () {
                      this.$element.removeClass('disabled').prop('readonly', false).datepicker(this.pickerOptions);
                  },
                  reset: function () {
                      this.setFieldValue(this.initValue);
                      this.lastValue = this.initValue;
                  }
              },
              extend: FormCtrlBase,
              staticMembers: {
                  DEFAULTS: DEFAULTS
              }
          });
      
          return FormFieldDate;
      });

      這個組件跟formFieldText沒有太多區別,需要說明的是:

      1)它依賴了bootstrap-datetimepicker這個插件,來實現日期選擇的效果。如果想替換成其它的插件來實現日期選擇,需要改這部分的源碼;
      2)它依賴的日期插件僅僅是起到輔助錄入的作用,不影響formFieldBase定義的那些基本的屬性和行為。

      實際使用的時候,只要把Input元素的data-type指定為date即可:

      <input class="form-control form-field"
           name="birthday"
           data-type="date"
           data-default-value=""
           type="text"
           placeholder=""
           readonly
           value="2000-01-01">

      9. formFieldUeditor.js

      代碼如下:

      define(function (require, exports, module) {
          var $ = require('jquery'),
              FormCtrlBase = require('mod/formFieldBase'),
              Class = require('mod/class');
      
          var DEFAULTS = $.extend({
              height: 400,
              ueConfig: {}
          }, FormCtrlBase.DEFAULTS);
      
          var FormFieldUeditor = Class({
              instanceMembers: {
                  init: function (element, options) {
                      //通過this.base調用父類FormCtrlBase的init方法
                      this.base(element, options);
      
                      var that = this,
                          $element = this.$element,
                          opts = this.options;
      
                      //監聽input元素的change事件,并最終通過beforeChange和afterChange來管理
                      $element.on('change', function (e) {
                          var val = that.getValue(), event;
      
                          if (val === that.lastValue) return;
      
                          that.trigger((event = $.Event('beforeChange')), val);
                          //判斷beforeChange事件有沒有被阻止默認行為
                          //如果有則把input的值還原成最后一次修改的值
                          if (event.isDefaultPrevented()) {
                              that.setFieldValue(that.lastValue);
                              $element.focus().select();
                              return;
                          }
      
                          //記錄最新的input的值
                          that.lastValue = val;
                          that.trigger('afterChange', val);
                      });
      
                      var editorId = this.name + '-editor',
                          editorName = this.name + '-editor-text',
                          ueScript = [
                              '<script id="'
                              , editorId
                              , '" name="'
                              , editorName
                              , '" type="text/plain" style="width:100%;height:'
                              , opts.height
                              , 'px;">'
                              , '</script>'
                          ].join('');
      
                      this.$element.before(ueScript);
      
                      //初始化UE組件
                      this.ue = UE.getEditor(editorId, opts.ueConfig);
      
                      this.ue && this.ue.ready(function () {
                          that._ueReady = true;
      
                          /*//粘貼時只粘貼文本
                           that.ue.execCommand('pasteplain');
      
                           //粘貼后再次做格式清理
                           that.ue.addListener('afterpaste', function (t, arg) {
                           that.ue.execCommand('autotypeset');
                           });*/
      
                          //編輯器文本變化
                          that.subscribeUeContentChange();
      
                          //設置初始值
                          that.reset();
      
                          that.triggerInit();
                      });
      
                  },
                  subscribeUeContentChange: function () {
                      var editor = this.ue,
                          $element = this.$element;
      
                      this._ueContentChange = function () {
                          $element.val(editor.getContent()).trigger('change');
                      };
      
                      editor.addListener('contentChange', this._ueContentChange);
                  },
                  offUeContentChange: function offUeContentChange() {
                      var editor = this.ue;
                      editor.removeListener('contentChange', this._ueContentChange);
                      this._ueContentChange = undefined;
                  },
                  getDefaults: function () {
                      return DEFAULTS;
                  },
                  _setValue: function (value, trigger) {
                      //只要trigger不等于false,調用setValue的時候都要觸發change事件
                      trigger !== false && this.$element.trigger('change');
                  },
                  setFieldValue: function (value) {
                      var elementDom = this.$element[0],
                          v = ' ' + value;
      
                      elementDom.value = v;
                      elementDom.value = v.substring(1);
      
                      var ue = this.ue;
                      if (ue && this._ueReady) {
                          this.offUeContentChange();
                          ue.setContent(value);
                          this.subscribeUeContentChange();
                      }
                  },
                  getValue: function () {
                      return this.$element.val();
                  },
                  disable: function () {
                      this.ue && this._ueReady && this.ue.setDisabled();
                  },
                  enable: function () {
                      this.ue && this._ueReady && this.ue.setDisabled();
                  },
                  reset: function () {
                      this.setFieldValue(this.initValue);
                      this.lastValue = this.initValue;
                  }
              },
              extend: FormCtrlBase,
              staticMembers: {
                  DEFAULTS: DEFAULTS
              }
          });
      
          return FormFieldUeditor;
      });

      之所以寫這個完全是為了簡化ueditor的使用,就像前面說的,ueditor不改變表單元素在表單開發中的本質,僅僅是輔助錄入的作用,實現起來也沒什么難度,只要想辦法實現setValue getValue enable disabled reset這些重要的api方法即可,ueditor只是個插件,在init方法內找個合適的位置做一下初始化就好了。

      實際使用:

      <textarea class="form-control form-field"
                name="detailDesc"
                data-type="ueditor"
                data-default-value=""
                rows="3"
                placeholder=""><p>I'm felix.</p></textarea>

      注意data-type。

      10. 文中小結

      以上部分已經把formFieldBase以及現有的各個表單元素組件都介紹完了,但是在實際使用過程中,如果我們每個元素都要手動調用構造函數去初始化的話,那就太麻煩了, 這遠不是本文想要起到的作用,所以為了簡化最終的表單數據設置和收集的功能,我還另外寫了兩個組件:formFieldMap,這就是個映射表;formMap,這是個容器組件,管理內部所有的表單元素組件實例。在實際需求中,一般只要用到formMap即可。

      11. formFieldMap

      這個僅僅是為了formMap服務的,因為formMap會根據某個規則找到所有的待初始化的元素,然后根據元素的data-type屬性,再根據formFieldMap來找到具體的構造函數:

      define(function (require, exports, module) {
          var FormFieldText = require('mod/formFieldText'),
              FormFieldCheckbox = require('mod/formFieldCheckbox'),
              FormFieldRadio = require('mod/formFieldRadio'),
              FormFieldSelect = require('mod/formFieldSelect'),
              FormFieldDate = require('mod/formFieldDate'),
              FormFieldUeditor = require('mod/formFieldUeditor');
      
          return {
              checkbox: FormFieldCheckbox,
              date: FormFieldDate,
              radio: FormFieldRadio,
              text: FormFieldText,
              select: FormFieldSelect,
              ueditor: FormFieldUeditor
          }
      });

      12. formMap

      代碼如下:

      define(function (require, exports, module) {
      
          var $ = require('jquery'),
              FormFieldMap = require('mod/formFieldMap'),
              Class = require('mod/class'),
              hasOwn = Object.prototype.hasOwnProperty,
              DEFAULTS = {
                  mode: 1, //跟FormFieldBase一致
                  fieldSelector: '.form-field', //用來獲取要初始化的表單元素
                  fieldOptions: {} //表單組件的option
              };
      
          var FormMap = Class({
              instanceMembers: {
                  init: function (element, options) {
                      var $element = this.$element = $(element),
                          opts = this.options = this.getOptions(options);
      
                      //存儲所有的組件實例
                      this.cache = {};
      
                      var that = this;
                      //初始化所有需要被FormMap管理的組件
                      $element.find(opts.fieldSelector).each(function () {
                          var $field = $(this);
      
                          //要求各個表單元素必須得有name或者data-name屬性,否則fieldOptions起不到作用
                          that.add($field, $.extend({
                              mode: opts.mode
                          }, opts.fieldOptions[$field.attr('name') || $field.data('name')] || {}));
                      });
                  },
                  getOptions: function (options) {
                      var defaults = this.getDefaults(),
                          _opts = $.extend({}, defaults, this.$element.data() || {}, options),
                          opts = {};
      
                      //保證返回的對象內容項始終與當前類定義的DEFAULTS的內容項保持一致
                      for (var i in defaults) {
                          if (hasOwn.call(defaults, i)) {
                              opts[i] = _opts[i];
                          }
                      }
      
                      return opts;
                  },
                  getDefaults: function () {
                      return DEFAULTS;
                  },
                  add: function ($field, fieldOption) {
                      //要求要被FormMap管理的組件必須有data-type屬性
                      var type = $field.data('type');
      
                      if (!(type in FormFieldMap)) return;
      
                      var formField = new FormFieldMap[type]($field, fieldOption || {});
      
                      this.cache[formField.name] = {
                          formField: formField,
                          fieldName: formField.name
                      };
                  },
                  get: function (name) {
                      var field = this.cache[$.trim(name)];
                      return field && field.formField;
                  },
                  remove: function (name) {
                      var formField = this.get(name);
                      if (formField) {
                          delete this.cache[name];
                          formField.destroy();
                      }
                  },
                  reset: function () {
                      var cache = this.cache;
                      for (var i in cache) {
                          if (hasOwn.call(cache, i)) {
                              cache[i].formField.reset();
                          }
                      }
                  },
                  getData: function () {
                      var cache = this.cache,
                          data = {};
      
                      for (var i in cache) {
                          if (hasOwn.call(cache, i)) {
                              data[cache[i].fieldName] = cache[i].formField.getValue();
                          }
                      }
      
                      return data;
                  },
                  setData: function (data, trigger) {
                      if (Object.prototype.toString.call(data) !== '[object Object]') return;
      
                      var cache = this.cache;
      
                      for (var i in cache) {
                          if (hasOwn.call(cache, i) && (i in data)) {
                              cache[i].formField.setValue(data[i], trigger);
                          }
                      }
                  }
              },
              staticMembers: {
                  DEFAULTS: DEFAULTS
              }
          });
      
          return FormMap;
      });

      它提供了三個option:

      mode: 跟formFieldBase的mode作用是一樣的,只不過因為formMap是作用于form元素上的,所以它的mode屬性相當于是全局的,會對所有的表單元素都起作用;
      fieldSelector: 用來過濾需要被初始化的表單元素的選擇器,默認是.form-field,只要一個元素上有這個class,就會被這個容器管理,通常保留默認值即可;
      fieldOptions:可以通過它傳遞各個表單元素的各自的option。

      使用舉例:

      appForm = new Form('#appForm', {
          mode: Url.getParam('mode'),
          fieldOptions: {
              name: {
                  onInit: function(){
                      
                  }
              },
              work: {
                  onAfterChange: function (e, val) {
                      if(val == 'xxx') {
                          
                      }
                  }
              }
          }
      });

      它還提供了以下api方法,在實際工作中可以用得到:

      1)get(name),用來獲取某個字段的表單元素組件的實例
      2)add($field, option),將一個新的元素添加到容器來管理,這個在一些需要動態對表單元素進行增刪的時候會經常用到
      3)remove(name),移除某個元素的在容器中的組件實例
      4)getData(),獲取容器內所有表單元素組件的值,以Object實例的形式返回
      5)setData(data, trigger),統一設置容器內所有的表單組件的值,第二個參數如果為false,則不會觸發各個組件的change事件
      6)reset(),重置整個容器內所有的表單元素組件的值為初始值。

      13. 本文總結

      本文介紹的內容很多,但從我個人而言,用途還是很大的,去年的公司里面有很多個項目都是用這種方式開發完成的,開發速度很快,而且整體上邏輯都比較清晰,比較好理解,所以非常希望這里面的東西也能夠給其它人帶來幫助。前段時間工作還比較忙,有很多的時間都花在這篇文章現有成果的思考和優化方面,將來也還會繼續改進,目的就是為了希望在項目開發過程中能夠更加省時省力,同時還要保質保量。下一步我會介紹自己如何進一步封裝form組件以及form校驗這一塊的內容,已經有成果了,只是要等下周六日才會有時間來總結,請再關注。

      posted @ 2016-05-09 10:25  流云諸葛  閱讀(2598)  評論(7)    收藏  舉報
      主站蜘蛛池模板: 无码一区中文字幕| h动态图男女啪啪27报gif| 亚洲无av在线中文字幕| 少妇精品无码一区二区免费视频| 欧美做受视频播放| 天天做天天爱夜夜爽导航| 中文字幕人乱码中文| 四虎影院176| 亚洲热妇无码av在线播放| 国产亚洲av手机在线观看| 亚洲精品tv久久久久久久久久| 久久精品无码一区二区小草| 熟女人妇 成熟妇女系列视频| 网友偷拍视频一区二区三区| 亚欧洲乱码视频在线专区| 樱花草在线社区www| 国产激情艳情在线看视频| 欧洲无码一区二区三区在线观看| 亚洲综合成人av在线| 亚洲婷婷综合色高清在线| 四虎影视www在线播放| 综合区一区二区三区狠狠| 91区国产福利在线观看午夜| 日韩一区在线中文字幕| 日韩午夜福利片段在线观看 | 国产毛片三区二区一区| 国产丰满乱子伦无码专区| 亚洲偷自拍另类一区二区| 精选国产av精选一区二区三区| 色综合天天综合网国产人| 国产成人综合在线女婷五月99播放| 亚洲欧美精品综合在线观看| 精品无码国产一区二区三区av| 涩涩爱狼人亚洲一区在线| 黄色免费在线网址| 国产va免费精品观看精品| 欧美搡bbbbb搡bbbbb| 成人免费A级毛片无码片2022| 新婚少妇无套内谢国语播放| 好硬好湿好爽再深一点动态图视频| 中文字幕人妻色偷偷久久|