2017年12月20日 星期三

Kendo ui Combobox 在 blur 時觸發change 事件 | Kendo ui Combobox blur trigger change event

同事遇到的BUG,
在Kendo ui Combobox選完值,blur時會觸發change事件,
經過實驗(還有我一直google)同時發現,
如果在text field使用number類型欄位,就會這樣,
而且官方好像沒有要改的意思

ref: https://github.com/telerik/kendo-angular/issues/1137

2017年12月19日 星期二

Telerik Report Nullable Date Parameter



背景

有個報表,搜尋條件是兩種日期選一種,日期欄位就是區間開始與區間結束,
在報表檔案設定四個日期變數:A起始、A結束、B起始、B結束,都可以為空值,
然後還要做民國年轉換,
結果在表頭顯示輸入變數的地方就調了很久,終於成功...

解法


原來要做以下處理:

  1. 判斷變數是否為null
Parameters.P_InitDateStart is null



  1. 調整UTC時區問題,沒有自動+8,要手動+8
AddHours(Parameters.P_InitDateStart,8)



  1. 加上小時前,還要做IsNull判斷讓他一定要回傳一個非空的日期值
AddHours(IsNull(Parameters.P_InitDateStart, Date(1,1,1)),8)



  1. 把年份減去1911
(AddHours(IsNull(Parameters.P_InitDateStart, Date(1,1,1)),8).Year-1911)



5.最後拼起來這些處理就變成這樣:

= IIF(Parameters.P_InitDateStart is null,
(AddHours(IsNull(Parameters.P_ArriveDateStart, Date(1,1,1)),8).Year-1911)+"/"
+AddHours(IsNull(Parameters.P_ArriveDateStart, Date(1,1,1)),8).Month+"/"
+AddHours(IsNull(Parameters.P_ArriveDateStart, Date(1,1,1)),8).Day,
(AddHours(IsNull(Parameters.P_InitDateStart, Date(1,1,1)),8).Year-1911)+"/"
+AddHours(IsNull(Parameters.P_InitDateStart, Date(1,1,1)),8).Month+"/"
+AddHours(IsNull(Parameters.P_InitDateStart, Date(1,1,1)),8).Day)



 暈...

2017年12月8日 星期五

Kendo UI Date Picker 民國年格式 | Kendo UI Date Picker Taiwan calander format



背景

一個Grid,在編輯時用popup,內容是template,
有個日期欄位,
使用者想要輸入1060101或者10601011735這種日期或日期時間格式
不想要選取也不想要打「-」、空白或「:」

研究了一下也提問在官方討論區了,官方是說不支援民國年這種格式,
實際上你輸入1900年之前的日期,也會無法set,
所以也不能用民國106年直接套上DatePicker/DateTimePicker,而且萬年曆也會不對,

這裡就用textbox自幹一個字串轉日期的方法
(如果想要讓後端收到的值仍然有「-」、空白或「:」,是可以搭配mask)

首先在原本的template的時間輸入框前面加入一個input textbox

<input class="k-textbox " data-bind="value:myDateTW" id="myDateTW" name="myDateTW" placeholder="民國年yyyMMdd" style="width: 200px;" tabindex="110">
<span class="k-invalid-msg" data-for="myDateTW"></span>
<input data-bind="value:myDate" id="myDate" name="myDate" style="display: none; width: 250px;" tabindex="110">
<span class="k-invalid-msg" data-for="myDate"></span>


然後準備好字串轉日期、日期轉字串的處理,
這裡列的方法是我很直觀的去寫,沒有做什麼效能分析

//傳入值與格式,分析與轉換成JS日期物件
function parseTWDate(val, format) {
  var result = { result: true, msg: "", DateObj: null };

  if (typeof val === "undefined") {
    result.result = false;
    return result;
  }

  //只接受數字與字串型態
  if (typeof val !== "number" && typeof val !== "string") {
    result.result = false;
    return result;
  }
  //檢查格式字串
  if (
    format !== "yyyMMdd" &&
    format !== "yyyMMddHHmm" &&
    format !== "yyyMMddHHmmss"
  ) {
    result.result = false;
    result.msg = "不支援的日期格式設定";
    return result;
  }
  //只接受數字不接受符號
  if (/^\d+$/.test(val) === false) {
    result.result = false;
    return result;
  }
  var str = "";

  //數字型態轉文字
  if (typeof val === "number") {
    str = val.toString();
  } else {
    str = val;
  }

  //從後往前兩位兩位取出字串,轉數字後做驗證
  var sec = 0;
  var min = 0;
  var hr = 0;
  var day = 0;
  var mon = 0;
  var year = 0;
  var movingCount = 0;
  if (format === "yyyMMddHHmmss") {
    if (str.length < 11) {
      result.result = false;
      return result;
    }
    sec = parseInt(
      str.substring(str.length - movingCount - 2, str.length - movingCount),
      10
    );
    if (sec === NaN || sec > 59 || sec < 0) {
      result.result = false;
      return result;
    }
    movingCount += 2;
  }
  if (format === "yyyMMddHHmm" || format === "yyyMMddHHmmss") {
    if (str.length < 9) {
      result.result = false;
      return result;
    }
    min = parseInt(
      str.substring(str.length - movingCount - 2, str.length - movingCount),
      10
    );
    if (min === NaN || min > 59 || min < 0) {
      result.result = false;
      return result;
    }
    movingCount += 2;

    hr = parseInt(
      str.substring(str.length - movingCount - 2, str.length - movingCount),
      10
    );
    if (hr === NaN || hr > 23 || hr < 0) {
      result.result = false;
      return result;
    }
    movingCount += 2;
  }
  if (str.length < 5) {
    result.result = false;
    return result;
  }
  day = parseInt(
    str.substring(str.length - movingCount - 2, str.length - movingCount),
    10
  );
  if (day === NaN || day > 31 || day < 1) {
    result.result = false;
    return result;
  }
  movingCount += 2;

  mon = parseInt(
    str.substring(str.length - movingCount - 2, str.length - movingCount),
    10
  );
  if (mon === NaN || mon > 12 || mon < 1) {
    result.result = false;
    return result;
  }
  movingCount += 2;

  year = parseInt(str.substring(0, str.length - movingCount), 10);
  if (year === NaN || year < 1) {
    result.result = false;
    return result;
  }
  //JS的日期的月份是0 base
  result.DateObj = new Date(year + 1911, mon - 1, day, hr, min, sec);

  return result;
}
//將日期選擇轉成民國年
function getTWDate(val, format) {
  var rawYear = val.getFullYear();
  var twYear = rawYear - 1911;
  var rawMonth = val.getMonth() + 1;
  var twMonth = rawMonth.toString().padStart(2, "0");
  var rawDay = val.getDate();
  var twDay = rawDay.toString().padStart(2, "0");
  var strTWDate = twYear + twMonth + twDay;
  if (format === "yyyMMddHHmm" || format === "yyyMMddHHmmss") {
    var hr = val
      .getHours()
      .toString()
      .padStart(2, "0");
    var min = val
      .getMinutes()
      .toString()
      .padStart(2, "0");
    strTWDate = strTWDate + hr + min;
    if (format === "yyyMMddHHmmss") {
      var sec = val
        .getSeconds()
        .toString()
        .padStart(2, "0");
      strTWDate = strTWDate + sec;
    }
  }
  return strTWDate;
}


然後在原本的datasource的schema中加入一個自訂的民國年欄位,類型是字串
這個欄位不會回傳值給API,只是用來data bind,
裡面做自定義驗證方法,在民國年字串輸入框跳開時,去跑日期解析,
成功就把日期物件帶回model,失敗就給予錯誤訊息並回傳false;
原本的欄位也給他一個驗證是如果跑到了驗證就檢查他的值是否可以解析成日期,
這裡弄了很久是不知如何避免偶爾會出現原欄位跳出必填的問題,
還有編輯時無法透過model.set讓他自己data bind去改變前端DOM的值,
set下去model的值竟然不會改,只能手動去塞值&改DOM,
改值的時候也要先改DOM在做SET,不然也會有怪現象

var dataSource = {
  schema: {
    model: {
      fields: {
        myDate: {
          type: "date",
          validation: {
            required: true,
            dateFormat: function(input, option) {
              if (input.is("[name='myDate']")) {
                if (Date.parse(input.val()) !== NaN) {
                  $("#myDateTW").removeAttr("data-TWdate-msg");
                  $("#myDate").removeAttr("data-required-msg");
                  return true;
                } else {
                  $("#myDateTW").attr(
                    "data-TWdate-msg",
                    "格式民國年yyyMMdd"
                  );
                  return false;
                }
              } else {
                return true;
              }
            }
          }
        },
        myDateTW: {
          type: "string",
          validation: {
            required: true,
            TWdate: function(input, option) {
              if (input.is("[name='myDateTW']")) {
                var val = input.val();
                var format = "yyyMMdd";
                var result = parseTWDate(val, format);
                if (result.result) {
                  input.removeAttr("data-TWdate-msg");
                  //帶回model
                  var source = $("#myDateTW")[0].kendoBindingTarget
                    .source;
                  $("#myDate").val(result.DateObj);
                  source.set("myDate", result.DateObj);
                  return true;
                } else {
                  input.attr("data-TWdate-msg", "格式民國年" + format);
                  //帶回model
                  var source = $("#myDateTW")[0].kendoBindingTarget
                    .source;
                  $("#myDate").val(val);
                  source.set("myDate", val);
                  return false;
                }
              } else {
                return true;
              }
            }
          },
        }
      }
    }
  },
};
};



再來是grid新增或編輯時需要把model的date欄位清空與轉換

edit: function(e) {
  var arg = e;
  if (e.model.isNew()) {
    //日期型態先清空
    e.model.set("myDate", null);
  } else {
    //民國年要先轉好
    var myDate = e.model.get("ㄗㄩDate");
    var strMyDateTW = getTWDate(myDate, "yyyMMdd");
    e.model.myDateTW = strMyDateTW;
    e.model.set("myDateTW", strMyDateTW);
    $("#myDateTW").val(strMyDateTW);
  }
}



這樣就可以輸入民國年日期轉成西元年日期,編輯時也可以把西元年日期轉回民國年日期了

2017年12月4日 星期一

Kendo UI Grid + datasource 錯誤處理 | Kendo UI Grid datasource error handle



(我的Kendo版本是2017.3.913)

最常用的CRUD應用元件,KendoUI Grid + Datasource transport,
可以簡單串起Restful API,
但錯誤處理上有一些地方要注意

使用transport時API如果不拋出500或者404之類的錯誤,
而是用處理完成,但處理結果有誤,錯誤訊息放在一個字串屬性裡面的這種模式,
datasource無法去抓出那個物件秀出來,
這時就要用因為有錯誤所以要拋exception的概念,
讓API拋出HttpStatusCode.InternalServerError,
這樣前端ajax呼叫時就會抓到錯誤,
然後丟給datasource裡的error屬性事件處理,
就可以用e.xhr.responseJSON.Message抓出錯誤內容,
但也要加上undefined偵測,不是只有API邏輯錯的時候才會丟錯
所以error物件裡面也不是常常都有xhr


如果是自己寫的介面,例如另外寫kendo window,
並且自訂按鈕送出ajax,你就可以控制ajax的邏輯,
要用「有錯就是要拋錯」或者「你成功執行那就給我200,商業邏輯錯誤再另外包」
哪一種抓錯方式都可以處理了。

另外如果新增刪除修改的API用void類型,datasource的transport會拋一個JSON parsererror的錯,但若是給他回傳值,則不會有這個錯
我用的回傳物件格式:

public class OperationOutModel
{
    ///<summary>執行結果</summary>
    public bool result { get; set; }
    ///<summary>錯誤訊息</summary>
    public string msg { get; set; }
}