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 會改善

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)));

Entity Framework 建立新物件並儲存後馬上取得關聯資料

使用 CreateProxy 建立物件,不要直接 new var newmodel = _contextXXX.CreateProxy<yyy>(); ... _contextXXX.yyy.Add(newmodel); await _contextXXX.SaveC...