2017年12月5日 星期二

以 Json.Net 取代預設的序列化及反序列化行為

緣由
透過 $http.post 呼叫 action 並帶入參數,但參數值較大(例如檔案內容),會遇到如下訊息
使用 JSON JavaScriptSerializer 序列化或還原序列化期間發生錯誤。字串的長度超過在 maxJsonLength 屬性上設定的值

好處
1. 效能較快
2. action 輸入跟輸出不會有長度限制

作法
1. 建立 JsonNetValueProviderFactory
public class JsonNetValueProviderFactory : ValueProviderFactory
    {
        public override IValueProvider GetValueProvider(ControllerContext controllerContext)
        {
            // first make sure we have a valid context
            if (controllerContext == null)
                throw new ArgumentNullException("controllerContext");

            // now make sure we are dealing with a json request
            if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
                return null;

            // get a generic stream reader (get reader for the http stream)
            StreamReader streamReader = new StreamReader(controllerContext.HttpContext.Request.InputStream);
            // convert stream reader to a JSON Text Reader
            JsonTextReader JSONReader = new JsonTextReader(streamReader);
            // tell JSON to read
            if (!JSONReader.Read())
                return null;

            // make a new Json serializer
            JsonSerializer JSONSerializer = new JsonSerializer() { DateTimeZoneHandling= DateTimeZoneHandling.Local };
            // add the dyamic object converter to our serializer
            JSONSerializer.Converters.Add(new ExpandoObjectConverter());

            // use JSON.NET to deserialize object to a dynamic (expando) object
            Object JSONObject;
            // if we start with a "[", treat this as an array
            if (JSONReader.TokenType == JsonToken.StartArray)
                JSONObject = JSONSerializer.Deserialize>(JSONReader);
            else
                JSONObject = JSONSerializer.Deserialize(JSONReader);

            // create a backing store to hold all properties for this deserialization
            Dictionary backingStore = new Dictionary(StringComparer.OrdinalIgnoreCase);
            // add all properties to this backing store
            AddToBackingStore(backingStore, String.Empty, JSONObject);
            // return the object in a dictionary value provider so the MVC understands it
            return new DictionaryValueProvider < object >(backingStore, CultureInfo.CurrentCulture);
        }

        private static void AddToBackingStore(Dictionary backingStore, string prefix, object value)
        {
            IDictionary d = value as IDictionary;
            if (d != null)
            {
                foreach (KeyValuePair entry in d)
                {
                    AddToBackingStore(backingStore, MakePropertyKey(prefix, entry.Key), entry.Value);
                }
                return;
            }

            IList l = value as IList;
            if (l != null)
            {
                for (int i = 0; i < l.Count; i++)
                {
                    AddToBackingStore(backingStore, MakeArrayKey(prefix, i), l[i]);
                }
                return;
            }

            // primitive
            backingStore[prefix] = value;
        }

        private static string MakeArrayKey(string prefix, int index)
        {
            return prefix + "[" + index.ToString(CultureInfo.InvariantCulture) + "]";
        }

        private static string MakePropertyKey(string prefix, string propertyName)
        {
            return (String.IsNullOrEmpty(prefix)) ? propertyName : prefix + "." + propertyName;
        }
    }

2. Global.asax.cs Application_Start() 增加如下內容
ValueProviderFactories.Factories.Remove(ValueProviderFactories.Factories.OfType<JsonValueProviderFactory>().FirstOrDefault());
            ValueProviderFactories.Factories.Add(new JsonNetValueProviderFactory());         

3. 建立 JsonNetResult.cs
public class JsonNetResult : JsonResult
    {
        public JsonSerializerSettings SerializerSettings { get; set; }
        public Formatting Formatting { get; set; }
        public JsonNetResult()
        {
            SerializerSettings = new JsonSerializerSettings();
        }
        public override void ExecuteResult(ControllerContext context)
        {
            if (context == null)
                throw new ArgumentNullException("context");
            HttpResponseBase response = context.HttpContext.Response;
            response.ContentType =
                !string.IsNullOrEmpty(ContentType) ? ContentType : "application/json";
            if (ContentEncoding != null)
                response.ContentEncoding = ContentEncoding;
            if (Data != null)
            {
                JsonTextWriter writer = new JsonTextWriter(response.Output)
                {
                    Formatting = Formatting,
                };
                JsonSerializer serializer = JsonSerializer.Create(SerializerSettings);
                serializer.Serialize(writer, Data); writer.Flush();
            }
        }
    }

4. 建立 JsonNetController
 public class JsonNetController : Controller
    {
        protected override JsonResult Json(object data, string contentType=null,
                  System.Text.Encoding contentEncoding=null, JsonRequestBehavior behavior= JsonRequestBehavior.AllowGet)
        {
            if (behavior == JsonRequestBehavior.DenyGet
                && string.Equals(this.Request.HttpMethod, "GET",
                                 StringComparison.OrdinalIgnoreCase))
                //Call JsonResult to throw the same exception as JsonResult
                return new JsonResult();
            return new JsonNetResult()
            {
                Data = data,
                ContentType = contentType,
                ContentEncoding = contentEncoding, 
                SerializerSettings= new Newtonsoft.Json.JsonSerializerSettings() { DateTimeZoneHandling= Newtonsoft.Json.DateTimeZoneHandling.Local }
            };
        }     
    }
並讓繼承Controller 的改為繼承JsonNetController 

說明
1. 輸出 json 方式不變,但會改用 json.net 處理
return Json(list);
2. 輸入和輸出預設會考慮本地時區,不需要刻意設定
3. 若還是出現錯誤訊息 "超出最大的要求長度 ",則修改 web.config 的 httpRuntime,加入 maxRequestLength="2147483647"

2017年11月24日 星期五

toaster 技巧

讓滑鼠停在裡面時不會取消計時
<toaster-container toaster-options="{'mouseover-timer-stop':false}"></toaster-container>

背景不要透明
#toast-container > div {
    opacity: 1 !important;
}

2017年11月16日 星期四

使用 linq + lambda + entity framework 查詢資料效能緩慢

1. 避免先撈出資料放在陣列中再進行比對
錯誤範例
var 沖銷支票號碼 = db.傳票.SelectMany(a=>a.傳票明細).Where(a => a.類別 == "K3" && a.借貸 == "1").Select(a => a.支票號碼).ToList();
var list=db.應付票據.Where(a => !沖銷支票號碼.Contains(a.支票號碼));
改良寫法
var list=db.應付票據.Where(a => !db.傳票.Any(b=>b.傳票明細.Any(c => c.類別 == "K3" && c.借貸 == "1" && c.支票號碼==a.支票號碼));

2. 資料量少且存在會重複讀取的資料的時候先 tolist 再 select 會比直接select 快,否則避免先 tolist
資料量少且傳票明細會重複讀取
db.傳票.Select(a => new { 傳票= a, 明細s = a.傳票明細 }).ToList().Select(a => new { id = a.傳票.id, 借方合計 = a.明細s.DefaultIfEmpty().Sum(b => b.借方金額), 貸方合計 = a.明細s.DefaultIfEmpty().Sum(b => b.貸方金額) })
資料量多
db.傳票.Select(a => new { id = a.傳票.id, 借方合計 = a.傳票明細.DefaultIfEmpty().Sum(b => b.借方金額), 貸方合計 = a.傳票明細.DefaultIfEmpty().Sum(b => b.貸方金額) })

3. DefaultIfEmpty() 效能不佳,且用法有限制(某些情況會造成執行錯誤), 請改用 ToList()

4. 避免 select 中使用到無法直接轉成 sql 語法的指令,避免執行時自動tolist再select造成效能緩慢
ex:
public enum enum類別   { 進貨驗收, 進貨退回,}
list = db.進貨驗收明細.Where(a =>... ).Select(a => new { 類別 = enum類別.進貨驗收 }).ToList(); // enum類別.進貨驗收 無法轉換成sql 語法
             
5. 查詢結果一律轉為list,否則之後使用時會重複執行查詢指令產生結果,造成效能低落
var aaa=db.訂單.Where(a=>...).Select(a =>...).ToList();
foreach (var a in aaa) 
...
var bbb=aaa.Where(a=>...); // 若不先tolist 會重新查詢產生aaa

6. 透過 partial class 另外存取關聯資料時,不要直接存取,必須重頭查詢,因為直接存取時會先把關聯資料表全部從資料庫抓到記憶體,造成效能下降,資料越多越明顯
舉例說明:
public partial class 訂單 {
  public bool 包含3C => 訂單明細.Any(b =>b.類別 == "3C"); // 會先把"訂單明細"所有資料抓到記憶體,再找出該訂單相關的明細
}
請改為以下標準寫法
public bool 包含3C(Entities db)
{
  return db.訂單明細.Any(b =>b.訂單編號==訂單編號 && b.類別 == "3C");
}

7. 查詢明細資料直接查詢,不要先查詢主檔再用 .selectmany() 取得明細

8. groupby 之前先把需要的欄位抓出來,且之後盡量不要再用 .FirstOrDefault() 去抓欄位,可以納入 groupby 就納入

9. .find() 會把資料全部都先抓到記憶體,且會自動呼叫DetectChanges (參考這裡),若頻繁呼叫或資料量大(例如用到image 型態儲存圖片)則改用 .where()+.select()+.single() 只抓需要的欄位,或設定AutoDetectChangesEnabled 為 false,或用 .single(a=>a.id==?)

10. 避免這樣處理 : select 分兩次,第一次抓需要的欄位,第二次針對欄位做其他運算 (例如sum)

11. 不要這樣寫 : (產品進出明細s ?? db.產品進出明細).Where(a=>...)

12. 盡量避免把陣列很大的資料透過 sql server 判斷是否存在其中,而是先抓出資料後在記憶體中比對,且不要用 Any() 否則會造成 stackoverflow (對應 sql 的 in 固有限制?)
db.aaa.Where(a => !bbb.Contains(a.id)).ToList(); 
db.aaa.Select(a => a.id).ToList().Except(bbb).ToList(); // 如果只需要單一欄位值
or
db.aaa.Where(a => !bbb.Contains(a.id)).ToList(); 
db.aaa.Where(a => xxx).ToList().Where(a => !bbb.Contains(a.id)).Select(a=>new {xxx}).ToList(); // 需要抓多個欄位,把 contains 放在 memory 中處理

13. 有時查詢條件會跨多個table,造成效能下降,不用table join改用view 會改善

14. 有時會遇到select 關聯table 效能低落甚至 timeout,必須在記憶體中使用二次查詢來處理關聯table,原因不明,若兩個table 資料量都很大可能會遇到
(await db.aaa.Select(a=>new {a.name,a.id}).ToListAsync()).Select(async a=>new {a.name,a.id,list=await db.bbb.Where(b=>b.pid==a.id).Select(b=>new {b.name,b.id}).ToListAsync()}).Select(a=>a.Result)

2017年11月15日 星期三

無法處理型別 '匿名型別',因為它沒有值層的已知對應

這屬於 entityframework concat 用法錯誤
tolist()  和沒有 tolist() 進行 concat,其中一個集合為空 就會拋錯
應統一選擇是否 tolist() 然後再 concat

2017年11月9日 星期四

透過 directive 控制對齊方式

參考現成js : https://gist.github.com/egermano/7451739
或直接把directive 放入自己的 module

.directive('align', function ($timeout) {
        'use strict';

        var linker = function (scope, element, attrs) {
            var options = attrs['align'].split(" "),
                listner = attrs['alignWatch'];

            var positioner = function () {
                angular.forEach(options, function (value, key) {

                    element.css('position', 'absolute');

                    switch (value) {
                        case 'top':
                            element.css({
                                top: '0',
                                marginTop: '0'
                            });
                            break;
                        case 'bottom':
                            element.css({
                                bottom: '0',
                                marginBottom: '0'
                            });
                            break;
                        case 'middle':
                            element.css({
                                top: '50%',
                                marginTop: ((element.height() / 2) * -1) + 'px'
                            });
                            break;
                        case 'right':
                            element.css({
                                right: '0',
                                marginRight: '0'
                            });
                            break;
                        case 'left':
                            element.css({
                                left: '0',
                                marginLeft: '0'
                            });
                            break;
                        case 'center':
                            element.css({
                                left: '50%',
                                marginLeft: ((element.width() / 2) * -1) + 'px'
                            });
                            break;
                    }
                });
            }

            $(window).resize(function () {
                positioner();
            });

            if (listner) {
                scope.$watch(listner, function () {
                    positioner();
                });
            };

            $timeout(function () {
                positioner();
            });
        };

        return {
            restrict: 'A',
            link: linker
        };
    })

2017年11月8日 星期三

轉型為 'System.Decimal' 型別失敗,因為具體化的值為 Null。此結果型別的泛型參數或此查詢,兩者中必須有一個使用可為 Null 的型別

若查詢結果沒有資料,直接Sum 會拋這個錯誤 (某種寫法才發生)

先 DefaultIfEmpty() 再 Sum() 可避免此問題 <= 效能差
Ex:
db.財會系統_帳戶資料期初.Where(a => a.公司id == id && a.id == 帳戶id && a.年度 == 期初日期.Year).DefaultIfEmpty().Sum(a => a.期初金額 ?? 0)

若先 ToList() 再 Sum() 可以避免錯誤,但查詢結果沒有資料時則結果會不如預期
Ex:
var list = db.傳票明細.Where(b => b.傳票.公司id == 公司id && b.沖帳發生 == true && b.金額 - b.沖銷.Where(c => c.沖帳發生 != true).ToList().Sum(c => c.金額) > 0)
b.沖銷.Where(c => c.沖帳發生 != true) 沒有資料的不會列出,推測是 b.沖銷.Where(c => c.沖帳發生 != true).ToList().Sum(c => c.金額) 被當作 null 處理
改用如下寫法則結果就會如預期
var list = db.傳票明細.Where(b => b.傳票.公司id == 公司id && b.沖帳發生 == true && b.金額 - (b.沖銷.Where(c => c.沖帳發生 != true).Sum(c => c.金額)??0) > 0)
若 c.金額 型態不可為null,則先強制轉型
var list = db.傳票明細.Where(b => b.傳票.公司id == 公司id && b.沖帳發生 == true && b.金額 - (b.沖銷.Where(c => c.沖帳發生 != true).Sum(c =>(decimal?)c.金額)??0) > 0)

2017年9月13日 星期三

透過 $http.post 傳遞參數到 server 出現訊息 : JSON 要求太長,無法還原序列化

通常是因為傳遞陣列時,陣列元素總數超過預設上限300,可透過以下方式加大上限
於 web.config 中加入
<add key="aspnet:MaxJsonDeserializerMembers" value="30000" />

2017年9月12日 星期二

自訂錯誤驗證

整體判斷 : if (!$scope.form1.$valid)
單獨判斷 : if (!$scope.form1.$error.requirepaycode)
強制判斷 : $scope.form1.報件單號.$validate(); (考慮有些情況不會自動觸發函數)

.directive("requirepaycode", function () {
            return {
                restrict: 'A',
                require: "ngModel",
                link: function (scope, element, attributes, ngModel) {
                    ngModel.$validators.requirepaycode = function (modelValue, viewValue) {
                        return !scope.selectingPayCode() || (modelValue != null && modelValue != "");
                    }                    
                }
            };
        })

<input requirepaycode name="報件單號" entertab ui-mask="X999999999999" model-view-value="true" class='form-control' ng-focus="focusPayCode()" ng-disabled="!newmode && !editmode" ng-model='selectData["報件單號"]'><span style='color:red' ng-show="form1['報件單號'].$error.requirepaycode">*</span>

2017年9月8日 星期五

ng-if 使用注意事項

如果同時使用 ng-model,因為 ng-if是自己的 scope,所以需要用物件綁定才能正常運作,否則切換 ng-if 的值會重新產生scope,ng-model value 會回到預設值
ng-model="word.value" ng-if="show"

或者可以直接綁定parent 避免此問題
ng-model="$parent.wordvalue" ng-if="show"

ui-mask 使用方式

讓提示字元變成 ng-model 的一部分
ui-mask="X999999999999" model-view-value="true"

若輸入不完整也允許接受
ui-options="{clearOnBlur:false,allowInvalidValue:true }"

改變提示字元
ui-mask-placeholder-char="space"

自訂遮罩字元(以卡號為例)
.config(['uiMask.ConfigProvider', function (uiMaskConfigProvider) {
    uiMaskConfigProvider.maskDefinitions({ 'C': /[a-zA-Z0-9*]/ });
}])
 <input entertab ui-mask="CCCC CCCC CCCC CCCC CCCC" ui-mask-placeholder-char="space" ui-options="{clearOnBlur:false,allowInvalidValue:true }" model-view-value="false" type='text' class='form-control' ng-model='selectData["卡號"]'>

2017年8月24日 星期四

透過 FreeSpire.Office 轉成 PDF 後紙張設定會跑掉,例如 A5 變A4、 橫向變直向

以 Spire.PDF 讀入資料並轉成另一個 PDF 同時重設紙張設定 document.SaveToStream(memStreamPDF, Spire.Doc.FileFormat.PDF); // 以 Spire.Doc 為例
using (var pdf = new PdfDocument(memStreamPDF))
using (var newPdf = new PdfDocument()) {
foreach (PdfPageBase page in pdf.Pages) {
PdfPageBase newPage = newPdf.Pages.Add(PdfPageSize.A5, new PdfMargins(0), PdfPageRotateAngle.RotateAngle0, PdfPageOrientation.Landscape);
PdfTextLayout loLayout = new PdfTextLayout();
loLayout.Layout = PdfLayoutType.OnePage;
page.CreateTemplate().Draw(newPage, new System.Drawing.PointF(0, 0), loLayout); } newPdf.SaveToStream(memStreamPDF);
...
}

※多頁word 轉PDF 會有問題,且格式會有出入,調整紙張格式也不成功,目前不建議把word轉成PDF

使用 epplus 產生檔案後轉成 PDF

使用 FreeSpire.XLS
...
ep.Save();
using (var workbook = new Workbook())
using (var memStream = new MemoryStream()) {
workbook.LoadFromStream(ep.Stream as MemoryStream);
workbook.SaveToStream(memStream, Spire.Xls.FileFormat.PDF);
...
}

※FreeSpire.Office 包含 FreeSpire.XLS 和其他相關套件,可以操作 word,excel,... 並轉為PDF

2017年8月18日 星期五

Datepicker Popup 使用範例

<span class="input-group"><input uib-datepicker-popup="yyyy/MM/dd" ui-mask="9999/99/99" model-view-value="true" is-open="popupInvoice.opened" type='text' class='form-control' ng-model='selectData["憑證日期"]'><span class="input-group-btn"><button type="button" class="btn btn-default" ng-click="popupInvoice.opened=true"><i class="glyphicon glyphicon-calendar"></i></button></span></span>

若容器有設定overflow,日期選擇視窗不要被容器遮住,造成容器出現捲軸,請在 module 加入
.config(['uibDatepickerPopupConfig', function (uibDatepickerPopupConfig) {
            uibDatepickerPopupConfig.appendToBody = true;
        }])

若在 $uibModal 中使用,須設定 z-index 避免被遮住
.uib-datepicker-popup {
    z-index: 1060 !important; /*避免被$uibModal遮住*/
}

讓文字顯示成中文
====
安裝 angular-i18n

2017年8月17日 星期四

按鈕群組按下效果

靜態寫法(單選)
====
<div class="btn-group" style="margin-left:10px"> <label class="btn btn-primary" ng-model="pass" uib-btn-radio="">全部</label> <label class="btn btn-primary" ng-model="pass" uib-btn-radio="false">未過帳</label> <label class="btn btn-primary" ng-model="pass" uib-btn-radio="true">已過帳</label> </div>

動態寫法(單選)
====
<div class="btn-group"> <label class="btn btn-primary" ng-model="$parent.from" uib-btn-radio="'{{obj.from | date:'yyyy/M'}}'" ng-repeat="obj in dateRange">{{(obj.from | date:'yyyy/M')+'~'+(obj.to | date:'M')}}</label> </div>

動態寫法(多選)
====
<div class="btn-group"> <label class="btn btn-primary" ng-repeat="date in dates" ng-model="date.checked" uib-btn-checkbox>{{date.shortText}}</label></div>

自訂css
====
.btn-date {
        color: black;
        background-color: #f8f8f8;
        border-color: #ccc;
    }
.btn-date.active, .btn-date:active, .open > .dropdown-toggle.btn-date {
        color: #ffffff;
        background-color: #449d44;
        border-color: #398439;
    }

2017年7月17日 星期一

$http.post 錯誤處理

c#
====
try{
...
} catch (Exception ex)
{
     Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
     Response.Write(ex.詳細訊息());
     return null;
}

 public static string 詳細訊息(this Exception ex)
        {
            if (ex is System.Data.Entity.Validation.DbEntityValidationException)
                return ((System.Data.Entity.Validation.DbEntityValidationException)ex).EntityValidationErrors.SelectMany(a => a.ValidationErrors.Select(b => b.ErrorMessage)).Join("\n");
            else
                return ex.InnerException?.InnerException?.Message ?? ex.InnerException?.Message ?? ex.Message;
        }


javascript
====
 $http.post('@Url.Action("匯出")')
                 .then(function (response) {
                     ...
                }, function (response) {
                // response.data = ex.詳細訊息()
            });


注意 : web.config 需加入以下設定,否則 response.data 會固定為 iis 針對Response.StatusCode預設的資料
<system.webServer>
    <httpErrors existingResponse="PassThrough"></httpErrors>


asp.net webmethod 寫法
====
HttpContext.Current.Response.Write(ex.Message);
HttpContext.Current.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
HttpContext.Current.Response.Flush();
//忽視之後透過Response.Write輸出的內容
HttpContext.Current.Response.SuppressContent = true;
//忽略之後ASP.NET Pipeline的處理步驟,直接跳關到EndRequest
HttpContext.Current.ApplicationInstance.CompleteRequest();

2017年7月14日 星期五

複製 Entity Framework 的 Entity

(TEntity)db.Entry(obj).GetDatabaseValues().ToObject();

此種方式只會複製值,不會複製物件或其參考,不過效能不佳,不適合用在大量複製
若 model 有關聯其他 model,導覽屬性會造成以下兩種作法都無法複製
使用序列化方式
使用無差別屬性對應方式

2017年6月29日 星期四

使用 ng-repeat 時綁定 ng-model 注意事項

ng-repeat 使用自己的 scope,無法直接綁定原本 scope 的變數,前面需要加上"$parent."

 

2017年6月23日 星期五

欄位防呆檢查機制

以必要欄位為例

html
====
<form novalidate name="form1">
<select required name="帳戶編號" class='form-control' ng-model='selectData["帳戶編號"]' ng-options='a.value as a.text for a in accounts'><option value='' disabled>請選擇</option></select><span style='color:red' ng-show="form1['帳戶編號'].$error.required">*</span>
</form>

javascript
====
if ($scope.form1.$error.required) {
                toaster.error("必要欄位輸入不完整!!");
                return;
}

2017年6月19日 星期一

複製文字到剪貼簿

var currentFocus = document.activeElement;
var textArea = document.createElement("textarea");
textArea.value = "123";
document.body.appendChild(textArea);
textArea.select();
document.execCommand("copy");
document.body.removeChild(textArea);
if (currentFocus) currentFocus.focus();

jquery
====
var $temp = $("<input>");
$("body").append($temp);
$temp.val($(element).text()).select();
document.execCommand("copy");
$temp.remove();

2017年6月15日 星期四

等待非同步執行完畢

寫法1
var deferred = $q.defer();
var promise = deferred.promise;
promise.then(function () {   
...
}).then(function () {
...
});
deferred.resolve();

寫法2
$q.all([$scope.refreshSelectData()]).finally(function () {
                    ...
                });

PS. 若呼叫 $http.post 則寫成return $http.post 否則還是不會等待

2017年6月7日 星期三

使用 UNC 方式存取資料夾

先下載此檔案 UNCAccessWithCredentials.cs

使用方式
const string path = @"\\172.16.1.1\aaa";
 using (UNCAccessWithCredentials unc = new UNCAccessWithCredentials())
            {
                unc.NetUseWithCredentials(path, 帳號, 網域, 密碼);
                var files = Directory.GetFiles(path).OrderBy(a => new FileInfo(a).CreationTime);
...

2017年5月18日 星期四

angularjs 的 document.ready

angular.element(document).ready(function () {
            $scope.get();
        });


PS. 有些於 controller 中要直接顯示 html 的情況必須在 ready 中呼叫,才能正確顯示,例如透過 AngularJS Toaster 顯示小視窗
PS. Toastr  於 angularjs 中會有 blocking 問題,必須改用 AngularJS Toaster

2017年5月15日 星期一

enter 模擬 tab 行為

.directive("entertab", function () {
    return {
        link: function (scope, element, attrs, ngModel) {
            element.on('keydown', function (event) {
                var key = event.which || event.keyCode;
                if (key == 13) {
                    var parent = $(element).parent()[0].localName == "div" ? $(element).parent():$(element).parent().parent(); // 依階層結構自行調整寫法
                    for (var i = 1; i <= 30; i++) { // 避開ng-hide,最多找30次,不然就放棄
                        parent = $(parent).next($(parent)[0].localName);
                        if (parent.length==0) break;
                        if ($(parent).attr("class") != "ng-hide") {
                            var nextchild = $(parent).find('input:not(.ng-hide,:disabled, [readonly="readonly"], [disabled="disabled"]),select:not(:disabled, [readonly="readonly"], [disabled="disabled"]),button:not(:disabled, [disabled="disabled"])');
                            if (nextchild.length > 0) {
                                $(nextchild).first().focus();
                                event.preventDefault();
                                break;
                            }
                        }
                    }
                }
            });
        }
    }
})

2017年5月8日 星期一

ng-options 正確顯示空白字元且不要被合併

必須把空白字元轉成 String.fromCharCode(160),無法使用 &nbsp; 或標準空白(ascii 32)
&nbsp; 於 ng-options 中會直接顯示成 &nbsp;
標準空白於 html 中連續出現則會自動合併
ascii 160 表示不換行空格,同時具備不會於 html 中自動被合併的特性
不換行空格 - 維基百科

javascript
====
angular.module('commonModule', []).service('commonService', function () {
    var self = this;
    self.replaceSpaces = function (str) {
        return str.replace(/ /g, String.fromCharCode(160));
    };
});
angular.module('module1', ['agGrid', 'ui.bootstrap', 'fiestah.money', 'commonModule'])
.controller('detailCtrl', function ($scope, $http, $window, $filter, $timeout, $uibModalInstance, commonService) {
        $scope.commonService = commonService;


html
====
<select style="font-family:MingLiU !important" class='form-control' ng-change="selectATLINK(1)" ng-model='selectData.ATLINKObject_1' ng-options='commonService.replaceSpaces(a.text) for a in selectData.atlinks_1' ng-show='selectData.atlinks_1.length>0'>

2017年4月7日 星期五

設定div 高度為最大可用高度

.directive('grid', function () {
        return {
            link: function (scope, elem, attrs) {
                $(elem).height($(window).height() - $(elem).offset().top);
            }
        };
    })

2017年3月18日 星期六

透過 ng-click 動態改變 href 並自動點選

html
===
<a target="中獎發票" detail ng-model="data.id">詳細</a>


javascript
===
.directive("detail", ['$http', function ($http) {
            return {
                require: "ngModel",
                link: function (scope, element, attributes, ngModel) {
                    element.on('click', function (event) {
                        $http.post('@Url.Action("詳細資料")', { id: ngModel.$viewValue })
                            .then(function (result) {
                                element.attr('href', result.data);                              
                       }).catch(function (data) {
                           console.log(data);
                           if (data.statusText != '') alert(data.statusText);
                           else alert('發生錯誤,請重新整理頁面後再試一次');
                       });
                    });
                }
            }
        }])

2017年2月16日 星期四

讓網頁超連結指向 .apk 可以正確下載

<system.webServer>
<staticContent>
<mimeMap fileExtension=".apk" mimeType="application/vnd.android.package-archive" />
</staticContent>
</system.webServer>

2017年2月7日 星期二

把 MVC 的 Model 轉為 javascript 物件

.net
@Html.Raw(Json.Encode(Model))

.net core
@Html.Raw(Json.Serialize(Model))

datetime 變數需另外轉換成 date 型態
ex. data.保存期限 = new Date(parseInt(data.保存期限.substr(6)));

input 連結 datalist 用程式控制彈出選項

範例: nextTick(() => document.querySelector('input').showPicker());  ※僅支援現代瀏覽器