Quantcast
Channel: 黑暗執行緒
Viewing all 2429 articles
Browse latest View live

Coding4Fun-K Sum問題求解

$
0
0

小學一年級生數學題目一枚:

請從 1 - 10 取出 4 個數字,4 個數字不可重複,總和必須為 15。例如:1, 2, 3, 9。

答案紙有七組空格,嘗試排列組合卻只能找到六組,小朋友心靈受挫,也成了大人間的討論話題。大家紛紛手算,「只有六組解」幾乎已成共識。但,程式魔人壓根沒算,而始默默在心中草擬演算法,決心要搞支程式暴力破解兼練功。

在FB專頁貼了題目,看到上官神人留言才知這問題在電腦科學領域很有名,是複雜度理論(Complexity Theory)與密碼學(Cyptography)裡重要的數學問題 - Subset Sum Problem。維基百科裡還提到  Q(i,s) := Q(i− 1,s) or (xi == s) orQ(i− 1,sxi)   for AsB 之類的數學公式,害我有點昏眩。我對數學理論全無興趣,純粹喜歡想方法用程式解決問題。(當年若真唸了資工、資科,會不會被離散數學、資料結構的一堆公式擊倒,頓失寫程式的熱情呢?)

不懂理論無妨,這問題難不倒有經驗的老鳥,就算不花太多腦筋,用「愚公移山」都可搞定。跑幾個迴圈湊數字,為避免數字順序換調的組合重複出現,限定後方數字一定要比前方大。而第四位不需要跑迴圈,直接用總和15反推即可。簡單幾行,程式完成!

using System;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
 
publicclass Program
{
class AnswerFinder
    {
public List<string> Answers = new List<string>();
 
publicvoid Explore()
        {
for (int i = 1; i <= 9; i++)
            {
//限定後一位數字要比前一位大
for (int j = i + 1; j <= 9; j++)
                {
for (int k = j + 1; k <= 9; k++)
                    {
if (k == j || k == i) continue;
int l = 15 - k - j - i; //計算湊成15所需的第四位數字
if (l <= k) continue; //第4位數字不得小於前一位
if (l < 1) break; //前三位數字已過大,中斷k迴圈
if (l == k || l == j || l == i) continue; //與前面數字重複
                        Answers.Add(string.Format("{0}{1}{2}{3}", i, j, k, l));
                    }
                }
            }
        }
    }
 
publicstaticvoid Main(string[] args)
    {
        AnswerFinder t = new AnswerFinder();
        Stopwatch sw = new Stopwatch();
        sw.Start();
        t.Explore();
        sw.Stop();
        Console.WriteLine("Done! {0} answers found in {1:n0}ms.",
                          t.Answers.Count, sw.ElapsedMilliseconds);
        Console.WriteLine(string.Join("\n", t.Answers.ToArray()));
        Console.Read();
    }
}

執行結果瞬間噴出:

Done! 6 answers found in 0ms.
1239
1248
1257
1347
1356
2346

問題已輕鬆解決,但對程式魔人尚未達止癢效果。前述做法寫死數字位數(4)、數字範圍(1-9),只有總和可調。如果題目改成 8 位數字,數字範圍 1-32,總和 128,得 i, j, k, l, m, n, o 一路寫到到 p,搞出七層巢狀迴圈,而且位數長度一改程式就得重寫…

俗話說:「迴圈只應凡間有,魔人該當用遞迴」

遞迴上場的時間到了!

我想了一個以 Stack 為核心的演算法,將數字長度、範圍、總和都改為可調參數,每 Push 進一個數字,就重算下一個可 Push 數字的範圍(不得小於前一位數字,不得大於數字範圍上限並考慮總和不可破表),依依序 Push 範圍內的可用數字,再重算下一位的數字範圍,依序 Push…,直到只剩一位數字要填時,再以總和反推求解。當下一位已無數字可用,則 Pop 退回上一位繼續嘗試,有點類似探索迷宮路徑的概念。

程式範例如下:

using System;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
 
publicclass Program
{
class AnswerFinder
    {
publicstaticint MaxLength = 4;
publicstaticint Sum = 15;
publicstaticint MinNumber = 1;
publicstaticint MaxNumber = 9;
publicstaticvoid Set(int maxLen, int sum, int min, int max)
        {
            MaxLength = maxLen;
            Sum = sum;
            MinNumber = min;
            MaxNumber = max;
        }
 
public Stack<int> Digits = new Stack<int>();
bool lastDigFixed = false;
int delta = Sum;
publicint Min = MinNumber, Max = MaxNumber;
publicbool Perfect = false;
 
privatevoid updateStats()
        {
int currLen = Digits.Count; //目前數字長度
            lastDigFixed = MaxLength - currLen == 1; //最後一位數確定
            delta = Sum - Digits.Sum(); //目前總和與目標總和的差額
            Perfect = MaxLength == currLen && delta == 0; //是否符合要求
 
//下個可用數字為為已使用數字之最大者+1
            var nextNum = Digits.Max() + 1;
//若只剩一位且差額在可用數字範圍,以差額為Min;否則取下一可用數字
            Min = lastDigFixed && delta >= nextNum ? delta : nextNum;
 
//若只剩一位且差額在可用數字範圍內,以差額為Max;否則取數字上限及差額較小者
            Max = lastDigFixed && delta <= MaxNumber ? 
                       delta : Math.Min(MaxNumber, delta);
 
        }
 
publicvoid Push(int num)
        {
            Digits.Push(num); //將數字放進組合
            updateStats();
        }
 
publicvoid Pop()
        {
            Digits.Pop(); //將數字從組合換下來
if (Digits.Count > 0) //長度為0表處理完畢,不需更新統計
                updateStats();
        }
 
public List<string> Answers = new List<string>();
 
publicvoid Explore()
        {
//下一位數字可用範圍為Min to Max
int min = Min, max = Max;
for (var n = min; n <= max; n++)
            {
                Push(n);
if (Perfect)
                    Answers.Add(string.Join(", ", Digits.Reverse().ToArray()));
else
                    Explore();
                Pop();
            }
return;
        }
 
    }
 
publicstaticvoid Main(string[] args)
    {
        AnswerFinder.Set(4, 15, 1, 9);
        AnswerFinder t = new AnswerFinder();
        Stopwatch sw = new Stopwatch();
        sw.Start();
        t.Explore();
        sw.Stop();
        Console.WriteLine("Done! {0} answers found in {1:n0}ms.", 
                                       t.Answers.Count, sw.ElapsedMilliseconds);
        Console.WriteLine(string.Join("\n", t.Answers.ToArray()));
        Console.Read();
    }
}

遞迴版本邏輯比較複雜,跑起來比迴圈版慢,4 位數總和 15 需要 5ms 才能算出答案。

Done! 6 answers found in 5ms.
1, 2, 3, 9
1, 2, 4, 8
1, 2, 5, 7
1, 3, 4, 7
1, 3, 5, 6
2, 3, 4, 6

但它的優勢在於參數可調,我們來試試更複雜的題目,例如:1-32,8 位數,總和 224,耗時 3.3 秒得解。

Done! 5 answers found in 3,285ms.
21, 26, 27, 28, 29, 30, 31, 32
22, 25, 27, 28, 29, 30, 31, 32
23, 24, 27, 28, 29, 30, 31, 32
23, 25, 26, 28, 29, 30, 31, 32
24, 25, 26, 27, 29, 30, 31, 32

我對執行效能不滿意,祭出新武器Visual Studio Performance Analyzer,找出效能瓶頸落在 Stack.Max() 及 Stack.Sum() 兩個彙總方法上:

小幅改寫,改用 Stack.Peek() 取最大值(循序 Push,最後一位數字永遠最大)會比 .Max() 有效率;另外,每次 Push 及 Pop 時改為自行重算數字總和,預期也會比 .Sum() 快:

int currSum = 0;
 
privatevoid updateStats(int sumChange)
        {
 
            currSum = currSum + sumChange;
 
int currLen = Digits.Count; //目前組合的數字長度
if (currLen == 0) return; //組合已無數字不需重算
            lastDigFixed = MaxLength - currLen == 1; //最後一位數確定
            delta = Sum - currSum; //目前總和與目標總和的差額
            Perfect = MaxLength == currLen && delta == 0; //是否符合要求
 
//下個可用數字為為已使用數字之最大者+1
            var nextNum = Digits.Peek() + 1;
//若只剩一位且差額在可用數字範圍,以差額為Min;否則取下一可用數字
            Min = lastDigFixed && delta >= nextNum ? delta : nextNum;
 
//若只剩一位且差額在可用數字範圍內,以差額為Max;否則取數字上限及差額較小者
            Max = lastDigFixed && delta <= MaxNumber ?
                  delta : Math.Min(MaxNumber, delta);
 
        }
 
publicvoid Push(int num)
        {
            Digits.Push(num); //將數字放進組合
            updateStats(num);
        }
 
publicvoid Pop()
        {
int num = Digits.Pop(); //將數字從組合換下來
            updateStats(-num);
        }

改寫後,耗時縮短到 0.8 秒,加快四倍,效果不錯。

Done! 5 answers found in 807ms.
21, 26, 27, 28, 29, 30, 31, 32
22, 25, 27, 28, 29, 30, 31, 32
23, 24, 27, 28, 29, 30, 31, 32
23, 25, 26, 28, 29, 30, 31, 32
24, 25, 26, 27, 29, 30, 31, 32

針對位數、範圍較大的題目,我猜演算法還可以再優化,但再鑽下去已感覺枯躁,失去 Coding4Fun 初衷,就此打住囉~

Live Demo

【後記】回到 4 位1-9 總和 15 的小學數學題,總人腦與電腦驗證,答案確實只有六組,答案紙有七格是怎麼一回事?原來老師的用意是若答案只有六格,小朋友在找出六組答案後就會停止,減少反覆嘗試練習加法的次數,也失去思索「是否答案就只有六組」的機會。嗯,很有教育意涵。


【茶包射手日記】在VS2013修改SCSS後未產生CSS

$
0
0

接獲同事報案,在 Visual Studio 2013 發生修改 SCSS 後未更新 CSS 的狀況,檢查確認 WebEssentials 已設定成存檔後編譯(如下),重啟 VS2013 問題未排除。

最後想到一個可能,SCSS 有錯導致編譯失敗,因而沒更新 CSS。回頭檢視 SCSS,果然找到一處宣告變數寫在呼叫處後方的錯誤,修正後 SCSS 就順利編譯產生 CSS了。

回頭檢討,一開始未考量語法錯誤是因為SCSS 語法出錯時預覽視窗應有明顯提示,很難被忽略,下方 Error List 也會有錯誤項目(雖然不一定會有檔名及錯誤訊息):

當 SCSS 有錯,編譯網站仍會出現編譯成功訊息(Build succeeded),但 Output 有紅字提醒編譯錯誤:

而同事遇到的狀況很不相同,SCSS 右方的 CSS 預覽未出現編譯錯誤訊息,Error List 無錯誤項目,編譯時下方的訊息則為
SCSS: test.scss compilation failed: The service failed to respone to this request. Possible cause: Syntax Error!

SCSS 編譯服務似乎因語法錯誤當掉了,未回報錯誤訊息給 Visual Studio,這可解釋預覽視窗及錯誤清單為何未出現錯誤訊息。由於缺乏明確錯誤提示,必須仔細檢視建置 Log 才能找到線索(甚至沒標成紅字),難怪 SCSS 錯誤被無視。

比對了 VS2013 版本,發現一個關鍵差別:我的環境是 VS2013 Update 2,同事的則為 VS2013 Update 3!在 WebEssentials 的 QA 討論找到幾則關於 Possible cause: Syntax Error 的回報,推測可能為 VS2013 Update 3 與 WebEssentials 版本搭配上的問題。將我的機器升級到 VS2013 Update 3 應可驗證,不過若驗證成功代表我也會遇到 SCSS 編譯錯誤不明顯的狀況,就決定撐著囉~ 順便請使用 VS2013 U3 / VS2013 U2 的朋友熱心回報協助驗證。

John Papa Angular Style Guide筆記

$
0
0

微軟的老牌技術傳教士 John Papa前些時候寫了一份 Angular 開發風格指南,近來打算在專案正式使用 AngularJS,便花了點時間詳讀,特筆記備忘兼分享。

先聲明一點:「開發風格」並無對錯可言,不同做法各有優劣,開發團隊可自行評估利害,取得共識維持一致即可。故文件所提並不是唯一正確的做法,只能說是蠻多人認同的一種選擇。很棒的一點是 John 花了不少篇幅解說「為什麼選擇這種做法?」,方便大家評估是否採納。筆記未能詳述之處,建議大家可以看原文,收獲會更多。

  1. 單一責任原則(Single Responsibility Principle)
    • 每個元件一個檔案,每個 Module、Controller、Service 也都要獨立成檔
      (註:實務上得配合打包壓縮機制,千萬別在 <script src="…"> 一個一個檔案載入)
    • 所有宣告都用 Immediately Invoked Function Expression (IIFE) 包起來
      (function() { 
          angular.module('app').factory('logger', logger); 
      function logger() { … }; 
      })(); 

      目的:將自用函式、變數名稱全部區域化,避免與其他 JavaScript 併用及打包壓縮時出現撞名。
      (註:若使用 TypeScript,可善用 module 特性,不需要手工寫。 )
  2. Module
    • 直接寫 angular.module('app', ['ngAnimate','app.shared']).controller('booCtrl', booCtrl);
      不要宣告 app 變數,如:var app = angular.module('app'… 可減少變數撞名及Memory Leak風險
    • 以具名函式取代匿名函式,以提高可讀性,有利偵錯。
      避免
      angular.module('app').controller('dashboard', function() { … }); 

      建議
      angular.module('app').controller('dashboard', dashboardFunction); 
      function dashboardFuncion() { … };
  3. Controller
    • controllerAs(稱為 Controller As)取代 $scope(稱為 Classic Controller),直接將 Controller 執行個體視為繫結對象,像這樣:

      function theController() { 
      var self = this; 
        self.propA = '…'; 
        self.propB = '…'; 
      } 

      (註:John Papa 慣用 var vm = this,我選擇沿用 var self = this;)
      但如果要動用 $scope.$watch(),建構時仍需傳入 $scope。而 controllerAs 屬語法甜頭(Syntax Sugar),背後仍靠 $scope 運作。
    • 為 controller 取別名,繫結時寫成 controllerName.propName。如下所示:

      <divng-controller="Customer as customer">
        {{ customer.name }} 
      </div>

      如此,當出現繼承關係時,父、子物件可透過別名存取,不需再搬出 $parent。參考
    • 將可被繫結的屬性及方法寫在 Controller 程式的前段方便閱讀(姑且叫它「托高集中」原則吧!XD ),較複雜的匿名函式改用具名函式,讓宣告區整齊劃一,增加可讀性。
      例如:self.method1 = function() { … }; 改為 self.method1 = method1,再將 function method1 { … } 寫在宣告區之後。

      function Sessions() {
      var self = this;
       
          self.gotoSession = gotoSession;
          self.refresh = refresh;
          self.search = search;
          self.sessions = [];
          self.title = 'Sessions';
       
      ////////////
       
      function gotoSession() {
      /* */
          }
       
      function refresh() {
      /* */
          }
       
      function search() {
      /* */
          }

    • 用 function method() { … } 取代 var method = function() { … } 可以避免宣告順序調整造成的錯誤。延伸閱讀

    • 將延遲執行的作業邏輯由 Controller 搬至 Service(例如:透過 AJAX 讀取資料)。
      優點:Service 的邏輯可以被多個 Controller 共用、方便單元測試、減少 Controller 對實作細節的依賴性(例如:在 Controller 寫 $http.get() 會綁死 XHR)

    • 避免在 View 裡寫死 Controller,例如:<div ng-controller="Avengers as vm">…</div>
      建議透過 Route 設定

      $routeProvider.when("/avengers", { 
        templateUrl: 'avengers.html', 
        controller: 'Avengers', 
        controllerAs: 'vm'
      }); 

      如此 View 寫 <div>…</div> 就好。
  4. Service(服務)
    • 可以寫函式讓 Angular 透過 new 建立執行個體(在函式中用 this.method = function() {…}宣告公開方法及屬性),也可以透過 Factory 模式建立,擇一並統一為宜。
    • 所有的 Service 都是 Singleton,只有一個執行個體。
  5. Factory
    • 單一職責:目標不同就拆成另一個 Factory。
    • Singleton:所有 Factory 都是 Singleton,負責傳回包含服務方法或屬性的物件。
    • 托高集中:將服務的公開方法、屬性宣告移到前段,實作內容放在後段,比照在 Controller 的做法。
    • 透過 service.$injector = ['a','b','c'] 配合 function service(a,b,c) {…} 解決相依要求。
  6. Data Service
    • 分離資料呼叫:將 XHR 呼叫、localStorage、記憶體暫存等資料操作邏輯移至 Factory。
      考量:Controller 只負責資料呈現及蒐集,不要涉及資料取得及傳輸以求單純、聚焦。(如同 MVC的 SoC 原理)如此將有利測試以及抽換實作(如:由 XHR 改 localStorage)
    • 利用 Deferrer 物件處理非同步呼叫的銜接順序。
  7. Directive
    • 將每個 Directive 寫成獨立檔案 (註:這點我持保留看法,可能造成檔案數驟增,我的想法是將 Directive 實作寫成獨立函式,集中在單一 TypeScript,透過強型別關聯應不難追蹤管理)
    • 避免在 Directive 中直接增刪變更 DOM,考慮以 CSS、動畫服務、樣版(Templating)、ngShow/ngHide 取代之。減少對 DOM 的依賴,將有助於測試。
    • Directive 採用自訂元素或 Attribute 宣告就好,透過 class="…" 宣告可讀性不佳。
  8. 處理 Controller 的非同步作業
    • 將非同步作業(例如:XHR 取值)包進 function activate() {…} 再呼叫,不要直接寫成建構式裡的程式片段,這樣比較一致好找。
    • 透過 $routeProvider.when("/avengers", { …, resolve: { 必備資料: function() { … }); 確保 Controller 在建構時,資料已備妥。延伸閱讀
  9. 依賴注入(Dependency Injection,DI)
    • 為避免 JavaScript 打包壓縮時變數更名破壞 DI,過去常見以下寫法
      .controller('Dashboard', ['$location','$routeParams','common',
      'dataService',Dashboard]); 

      建議改成
      Dashboard.$inject=['$location','$routeParams','common','dataService']; 

      可讀性較佳。使用於 Directive,Dashboard.$inject = […] 記得寫在 return { controller: Dashbaord }; 之前。
    • 在寫 $routeProvider.when() resolve 時,也請改用 .$inject 處理相依變數傳遞
  10. Minification 及 Annontation
    • 利用 ng-annotate讓 Gulp 或 Grunt 偵測程式自動加入 $inject 宣告
    • 自動偵測失效時,使用 /* @ngInjnect */ 註解提示
  11. 例外處理
    • 為 Module 加入一致的例外處理邏輯
      /* recommended */
      angular
          .module('blocks.exception')
          .config(exceptionConfig);
       
      exceptionConfig.$inject = ['$provide'];
       
      function exceptionConfig($provide) {
          $provide.decorator('$exceptionHandler', extendExceptionHandler);
      }
       
      extendExceptionHandler.$inject = ['$delegate', 'toastr'];
       
      function extendExceptionHandler($delegate, toastr) {
      returnfunction (exception, cause) {
              $delegate(exception, cause);
      var errorData = { 
                exception: exception, 
                cause: cause 
              };
      /**
               * 例外處理邏輯,例如:將錯誤呈報給$rootScope,傳送錯誤訊息至伺服器備查…
               */
              toastr.error(exception.msg, errorData);
          };
      }
    • 用 Factory 提供統一的例外處理機制
    • 透過 $rootScope.$on('$routeChangeError', …) 處理路由錯誤
  12. 命名原則
    • 建議:feature.type.js
      例:avengers.controller.js、logger.service.js、constants.js、avengers.module.js、avengers.routes.js、avenger-profile.directive.js
      測試檔 avengers.routes.spec.js、logger.service.spec.js
    • Controller 名稱:Pascal
    • Factory 名稱:Camel
    • Directive 名稱:加上統一的前置詞,Camel。ex: dkUserProfile,<dk-user-profile>
    • Module 檔名:主 Module 為 app.module.js,其餘自取,如 admin.module.js
    • Configuration 檔名:app.config.js、admin.config.js
    • Route 檔名:app.route.js、admin.route.js
  13. LIFT 原則
    • LIFT
      Locate our code is easy
      檔案、目錄結構分明
      Identify code at a glance
      每個元件一個檔案,並與命名相符,讓團隊成員能快速找到程式
      Flat structure as long as we can
      目錄不要超過兩層,儘可能扁平化
      目錄超過7-10個檔案,考慮建立子資料夾
      Try to stay DRY or T-DRY
      DRY!DRY!DRY!很重要,所以說三次而且不解釋。
  14. 應用程式架構
    • 看實際範例最清楚
    • 網頁配置框架、選單、導覽列等相關 View、Controller 集中在 Layout 目錄
    • 以功能來區分資料夾!
      但也有另一種選擇是用型別來區分:Views、Controllers、Directives、Services… 但很容易一個資料夾出現數十個檔案,違背 LIFT 原則。
  15. Modularity切割粒度 
    • 切割成多個功能聚焦的小型 Module:SRP(單一職責原則),方便組裝運用
    • 用一個 App Module 把模組功能組合起來,構成應用程式。
    • App 要輕薄,只負責組裝,實作邏輯留在各 Module 中。(如 MVC 中的 Controller 角色)
    • 將功能目標相近的邏輯切割成獨立 Module,例如:Layout、共用服務、系統服務項目(客戶模組、管理模組…)
    • 將可重複使用的區塊切成模組,例如:例外處理、Log、偵錯、安控、本機資料管理
    • Module 相依性:將跨 App 參照的相依模組(Cross App Modules)放入 app.core、App 主模組再照 app.core,以及其他功能性的 Module:

      (圖片來源:https://github.com/johnpapa/angularjs-styleguide
  16. $Wrapper
    • 使用 $document、$window、$timeout、$interval 取代 document、window、setTimeout、setInterval,測試時可改用假物件模擬,擺脫對 DOM 的依賴。
  17. 單元測試
    • 用故事描述的方式撰寫測試案例,先寫好案例說明,內容留白,再一一補上程式。(類似 TDD 的精神)
    • 用大家都在用的 Jasmine 或 Mocha 寫單元測試
    • 用大家都在用的 Karma 跑測試(可與 Grunt、Gulp、Visual Studio 整合)
    • 用 Sinon 做 Stubbing 及 Spying
    • 用 PhatomJS 跑網頁測試
    • 用 JSHint 分析程式碼(用 /*global sinon, describe, it, afterEach, beforeEach, expect, inject */ 排除測試程式,不做檢查)
  18. 動畫
  19. 註解
    • 使用 jsDoc 格式
      /**
      * @name logError
      * @desc Logs errors
      * @param {String} msg Message to log
      * @returns {String}
      */
      function logError(msg) {
  20. JSHint
    • 使用 JSHint 檢核 JavaScript 程式碼規範
    • 團隊協調使用一致的 JSHint 要求準則
  21. 常數
    • 將全域變數集中在 app.core:angular.module('app.core').constant('var1','value1');
[NG系列]
http://www.darkthread.net/kolab/labs/default.aspx?m=post&t=angularjs

【茶包射手日記】在電腦顯示Nexus 7畫面

$
0
0

為確認網頁在行動裝置能正確呈現,拿了 Nexus 7 平板來做實驗,打算搞個遙控程式在電腦顯示平板畫面,要抓畫或展示比較方便。上網找了一遍,圖文並茂的教學文有好數篇,得知有個 Android Screen Monitor 不用 root 能輕鬆實現。照著步驟演練,開好 USB 偵錯也接了線,連線清單卻沒有半個裝置出現。茶包射手無語問蒼天,拉弓試瞄準備射箭…

我參考的是這篇教學。先補充一點:Nexus 7 的設定選單,預設沒有「開發人員選項」,要用密技開啟,如下圖在「關於平板電腦/版本號碼」連按七下(連續技來著),按到第三下時下方會出現提示:

按到第七下,薑!薑!薑!薑~

「您現在已成為開發人員!」

嘖嘖嘖,原來在 Android 按七下就能成為開發人員,我還以為必須不斷學習持續苦練才能成功咧!(大誤)

在平板開啟 USB 偵錯模式後,平板會離線重新連上,但仍然只有外接儲存媒體功能,不會出現在 Android Screen Monitor 的裝置選單。依直覺是 Driver 有問題,果然:

先前安裝 SDK 時,預設已下載 Google USB Driver,不知何故未自動安裝。選擇sdk\extras\google\usb_driver 目錄更新驅動程式:

更新後出現 Android Composite ADB Inteface,才代表驅動程式安裝成功:

驅動更新後重新啟動USB偵錯,出現RSA金鑰確認:

終於,平台畫面同步顯示在電腦上(雖然延遲蠻嚴重的),未來要展示或抓圖,就方便多了!

Gulp, Grunt, Bower以及npm

$
0
0

Scotte Hanselman 前陣子寫了一篇文章,提到 Visual Studio 開始支援 Gulp、Grunt、Bower 以及 npm!一些寫 ASP.NET 的朋友,看到文章標題,心中的OS八成是「阿鬼,你還是說國語吧!」這些是什麼妖怪,為什麼我通通沒聽過?

雖然之前學 NG 時試用過一丁點 node.js,但這些名詞對我來跟克林貢語沒有兩樣。為了讀懂 Scott 的文章,做了點功課,試著了解這些名詞,以下是筆記。

【node.js】


Node.js是一個事件驅動I/O伺服端JavaScript環境,基於Google的V8引擎。目的是為了提供撰寫可擴充網路程式,如Web服務。Node.js並不是在Web瀏覽器上執行,而是一種在伺服器上執行的Javascript伺服端JavaScript。Node.js實作了部份CommonJS規格(Spec)。Node.js包含了一個互動測試REPL環境。[wiki]

node.js 開源、跨平台,加上 JavaScript 差不多已成為前端工程師說夢話的官方語言,node.js 在前端社群形成活躍的生態系統(Ecosystem),擁有極豐富的工具、套件及技術資源。而這股龐大力量也影響到微軟開發社群,開始出現整合 node.js 相關應用的 Visual Studio 套件,也是 Scott 寫文章背後的緣由。

node.js 的安裝超級簡單,下一步下一步就搞定,如果你有在寫前端程式,花個幾分鐘下載安裝,絕對值回票價。
參考:在 Windows、Mac OS X 與 Linux 中安裝 Node.js 網頁應用程式開發環境 - G. T. Wang

安裝好,打開命令列視窗輸入 node 就可開始體驗:

【npm】

npm 是 node.js 用來下載及安裝套件的工具,使用方法很簡單,但如果不會用便寸步難行,主要有以下幾種使用方法

  • npm install <package name> -g
    安裝套件的執行檔部分(全域套件)
  • npm install <package name>
    先 cd /project_folder,再執行 npm install <package name> 將套件安裝到專案目錄
  • npm uninstall <package name> -g
    npm uninstall <package name>
    移除全域套件以及專案套件(記得先切目錄)
  • npm ls -g
    列出已安裝的全域套件(npm ls -gl 包含詳細資訊)
  • npm ls
    列出專案已安裝的套件(npm ls -l 包含詳細資訊)
  • npm update -g
    npm update

    更新全域套件或專案套件
  • package.json
    將要安裝套件清單寫成 package.json,可使用 npm install –l 批次安裝
  • npm install <package name> --save
    npm install <package name> --save-dev
    npm install <package name> --save-optional

    安裝時一併將套件資訊寫入 package.json。--save 用於部署到上線環境必須的套件、--save-dev 則限定於開發環境(例如:單元測試、JS 打包壓縮…)

參考:npm 基本指令 – DreamersLab

【bower】

bower 由 Twitter  團隊研發,能自動下載安裝 CSS 及 JavaScript 套件,並可自動處理相依性,角色與 NuGet 相當。延伸閱讀
安裝:npm install bower -g

【Grunt】

Grunt是一個 Task Runner,常用來執行 JS/CSS 打包壓縮、SASS/LESS/CoffeeScript 編譯、單元測試… 等工作,常被拿來當成前端開發自動化的引擎。
安裝:npm -g install grunt-cli

【Gulp】

用來簡化 Grunt 設定,透過 gulpfile.js 按排作業流程:task, run, watch, src, dest
npm -g install gulp
npm install --save-dev gulp(安裝到專案目錄,限定開發才用到)
npm install gulp-compass --save-dev(gulp-compass 是常用 Gulp 外掛,內含大量 Mixin 定義)

一種常見應用是讓 Gulp 透過 watch 監看目錄,一旦檔案發生異動,就立即啟動編譯、打包等動作,實現連續整合的目標。

參考:Gulp.js:比 Grunt 更简单的自动化的项目构建利器

補充一下,透過 package.json 裡的 scripts 設定,npm 也可用來編譯,因此我們有三種編譯專案的選擇:Grunt, Gulp 以及 npm,這也是 Scott 文章標題的由來。這裡有一篇文章提供三者的比較分析。

同場加映

還有一些你可能會聽到的工具、套件,在此一併列出:

  • Yeoman
    程式產生器,可以快速產生 Angular、Backbone、Ember 所需的專案類別程式,以及單位測試程式。
  • Karma
    單元測試執行工具,常被用於 Angular 單元測試
  • Jasmine
    遵循 BDD 精神的 JavaScript 測試語言

以上工具如想進一步了解,保哥剛好有篇相關文章,推薦參考:如何在 Windows 平台安裝與使用 Yeoman 1.0 相關工具

最後,來一支再見全壘打!

如果你意猶未盡,擔心上述只談了部分,可能遺漏掉某些好用的前端工具,保哥還有一份海陸雙霸全餐 - 各式 Web 前端開發工具整理! 請慢用。(嗝~)

Bash漏洞檢測

$
0
0

9/24 US-CERT、RedHat及多家資安業者揭露一則消息:Bash 存在嚴重安全漏洞

Bash Shell 從 2004 年 7 月起存在一個安全漏洞,允許環境變數設定指令夾帶惡意指令被一併執行。當今世界上運作中的 Linux / *nix 系統(連 Mac OS 也算)的數量驚人,甚至在你料想不到的裝置裡也有個 Linux 默默工作著(除了智慧型手機外,許多電視機上盒、網路攝影機裡面也住著一隻小企鵝),它在我們的生活周遭幾乎已是無所不在,故評估這個漏洞影響範圍頗大。不過,較大的被攻擊風險主要來自 CGI 網站介面(是的,CGI! 清朝最流行的網站開發技術)送入惡意指令,滿足特定條件才具有高度風險,不用過度恐慌。

RedHat 資安部落格提供了一個簡單的測試指令

$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
 vulnerable
 this is a test

若出現 vunlerable 字樣,表示 Bash 有漏洞!

 $ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
 bash: warning: x: ignoring function definition attempt
 bash: error importing function definition for `x'
 this is a test

若出現 warning 及 error 訊息,表示 Bash 已是被修補過的版本,Safe!

好奇兼手癢,就找了手邊的 Linux 系統測試,第一開刀的是恰巧擺在桌上的新玩具 Raspberry Pi:

中!

原本想挖出之前裝過的 Linux VM 來現,後來想起 MSDN 送了免費 Azure 時數,不用可惜,索性就試試在 Azure 雲端養白老鼠。雖然工作專案沒什麼機會放上雲端跑,但 Azure 的 VM 建立程序還真方便,網頁上點幾下,當場生出兩台 Linux 讓我亂玩,玩完就砍掉,很酷!

先試了 Ubuntu,發現 Azure 提供的 Ubuntu 磁碟映像檔(Image)版本, Bash 已是修正過的版本。

原本想來個修補前修補後的對照,這下沒搞頭了。

再試另一台SUSE Linux Enterprise Server 11 SP3。Oh Yeah~ 捕獲有洞的 Bash 一隻!

臨時惡補,學會怎麼更新 SUSE。將系統更新到最新版,漏洞消失!

檢測方法很簡單,建議有在用 Linux 的朋友順手檢查,勤於更新,才能永保安康囉!

【參考資料】

無法安裝Evernote 5.6.4

$
0
0

Evernote 5.5.3 版執行時自動更新提示升級成 5.6.4 版,安裝程式要求先移除 5.5.3,但移除時出現找不到 Evernote.msi 錯誤。試著由「控制台\所有控制台項目\程式和功能」移除,一樣出現找不到Evernote.msi 錯誤,卡在舊版移不掉,新版裝不了的尷尬處境。

 

嘆了口氣,捲起袖子動手修理。選擇先手動移除 Evernote 5.5.3,參考MS KB(http://support.microsoft.com/kb/314481)刪除Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\ Uninstall\{B1A0F908-1448-11E4-8684-00163E98E7D0}
*警告:人工修改Registry有可能導致作業系統毁損,操作前請自行衡量風險

擔心新版程式裝在原目錄新舊交雜會出亂子,先把 C:\Program Files (x86)\Evernote\evernote 搬到他處保存。再執行一次安裝程式,還是一模一樣的訊息!

以為又遇上難纏妖怪,開了 Process Monitor 準備收妖,卻意外發現 Evernote.msi 明明就好好躺在AppData\Local\Temp,點開就能執行。

就這樣,Evernote 5.6.4 安裝成功!發現新版程式的安裝路徑已經改成C:\Users\Jeffrey\AppData\Local\Apps\Evernote\Evernote\Evernote.exe
或許就是這次升級失敗的原因。

CORS OPTIONS Preflight Request與IIS設定

$
0
0

ASP.NET WebApi 內建跨網域支援(參考:進擊的 ASP.NET Web API 2 巨人 – 打造支援各種裝置及平台的服務 - MSDN 台灣部落格),但基於專案的特殊需求,最後我還是決定自己寫 CORS 支援。

程式在 IIS Express 測試正常,搬到 IIS 後部分呼叫正常,部分失效。經分析問題如下:

瀏覽器在發出跨網域請求時,若符合以下條件:(參考:MDN

  • It uses methods other than GET, HEAD or POST. 
    執行 GET/HEAD/POST 以外的方法
  • Also, if POST is used to send request data with a Content-Type other than application/x-www-form-urlencoded, multipart/form-data, or text/plain, e.g. if the POST request sends an XML payload to the server using application/xml or text/xml, then the request is preflighted.
    使用 POST 且使用 application/x-www-form-urlencoded, multipart/form-data, or text/plain 之外的 Content-Type,例如:以 POST 傳送 XML、JSON 等。
  • It sets custom headers in the request (e.g. the request uses a header such as X-PINGOTHER)
    使用自訂 Header

瀏覽器會在正式 Request 發送前,先送出一個 OPTIONS 的行前檢查請求(Preflight Request)。例如以下範例,開啟 Chrome 連上 jQuery 官網,於 F12 開發工具主控台透過 jQuery.ajax 分別發出三個 Request 到本機 localhost(標準跨網域情境),第一個為 POST、第二個為 GET,第三個 POST Request 指定 ContentType="text/xml"[ 註:測試時誤寫成plain/xml(羞),寫到昏頭,但仍吻合條件,擷圖就容我偷懶不修正,順便當成彩蛋(謎:這樣也成?I 服了U) ]。前兩次測試都是單一 Request 完成,第三個測試則會先送出一個 OPTIONS 請求,接著才正式送出 POST。

同樣的測試搬到 IIS,重覆前述的第三個 Request,送出的 OPTIONS Preflight Request 會因 Server 端未正確傳回 Access-Control-Allow-Origin 而失敗!

探究其原因,在 IIS 失敗是因為 OPTIONS Request 被預設載入的 OPTIONSVerbHandler 攔截,直接回應,未傳回對應的 Access-Control-Allow-Origin 標頭,導致 CORS 呼叫失敗。

要校正問題,可透過 IIS 管理員介面或修改 web.config 將其移除:

<system.webServer>
<handlers>
<removename="OPTIONSVerbHandler"/>
<!-- 略 -->
</handlers>
</system.webServer>

測試結果便正常了!

註: 在 IIS8,該 Handler 名稱為 OPTIONS。參考


NG筆記17-範本(Template)

$
0
0

範本(Template)是MVVM的基本功能之一,與KO相比,NG的範本功能多了將範本存在外部HTML檔的彈性。開始前,先回味本次復刻對象: KO範例13 - Template範本功能

先前介紹過的ng-repeat Directive已內含範本概念,例如:

<trng-repeat="user in model.users"ng-class="user.addFlag ? 'new' : ''"
anim-hide="user.removeFlag"anim-hide-done="model.removeUser()">
<td><span>{{ user.id }}</span>
</td>
<td><span>{{ user.name }}</span>
</td>
<td><spanstyle='text-align: right'>{{ user.score }}</span>
</td>
<td><ang-click="model.markUserRemoved(user)"class="btn">移除</a>
</td>
</tr>

在<tr>與</tr>間以HTML穿插ng-* Directive或{{ prop }}方式定義呈現樣式,即相當於內嵌範本。而NG提供了ng-include可將範本提取成獨立片段加以共用。例如:

<scripttype="text/ng-template"id="/contactTmpl.html">
<dl>
<dt>Name</dt><dd>{{ member.name }}</dd>    
<dt>Phone</dt><dd>{{ member.phone }}</dd>
</dl>    
</script>
</head>
<bodyng-controller="defaultCtrl">
<h3>Leader</h3>
<divclass='memb-list'>
<spanng-repeat="member in [model.leader]">
<spanng-includesrc="'/contactTmpl.html'"
ng-show="member"class='cont-block'>
</span>
</span>
</div>
<br/>
<h3>Members</h3>
<divclass='memb-list'>
<spanclass='cont-block'ng-repeat="member in model.members"
ng-includesrc="'/contactTmpl.html'">
</span>
</div>

以上範例,範本內容被寫進<script type="text/ng-template" id="/contactTmpl.html">放在<head>區段,而底下的兩處ng-repeat透過<span ng-include src="'/contactTmpl.html'">指定由其決定連絡人呈現樣式,達到共用。Live Demo

範本src="'/contactTmpl.html'"有一些提示:1) 設定值被寫成'/…'字串常數,代表可用ViewModel屬性動態指定 2) id名稱刻意取成contactTmpl.html,暗示範本也能獨立存成HTML檔案。

來看以下的例子:

在這個範例中,我們另外建立兩個HTML檔:

lab13at1.html (不需要<html>、<head>、<body>,只要寫入範本片段就好)

<dl>
<dt>Name</dt><dd>{{ member.name }}</dd>
<dt>Phone</dt><dd>{{ member.phone }}</dd>
</dl>

lab13at2.html

<dl>
<dt>姓名</dt><dd>{{ member.name }}</dd>
<dt>電話</dt><dd>{{ member.phone }}</dd>
</dl>

主程式新増範本下拉選單,由model.tmpls = ["lab13t1.html","lab13t2.html"]取得選項,並繫結到selTmple屬性。引用範本時寫成<span ng-include="model.selTmpl">,使用範本將由下拉選單控制,由XHR取回範本內容套用,每次切換後也會立即反應。

<selectng-model="model.selTmpl"
ng-options="data for data in model.tmpls">
</select>
 
<h3>Leader</h3>
<divclass='memb-list'>
<spanng-repeat="member in [model.leader]">
<spanng-include="model.selTmpl"
class='cont-block show-anim'>
</span>
</span>
</div>
<br/>
<h3>Members</h3>
<divclass='memb-list'>
<spanclass='cont-block'ng-repeat="member in model.members"
ng-include="model.selTmpl">
</span>
</div>

 

[NG系列]

http://www.darkthread.net/kolab/labs/default.aspx?m=post&t=angularjs

2014葡萄馬拉松

$
0
0

第19馬,葡萄馬連續參加第三年,葡萄依舊香甜,原住民朋友熱力不減。

       

早上四點多到接駁車集合點,現場不見任何跑友侯車,心頭一驚,莫非我搞錯時間或記錯地點?原來遊覽車已到,司機大哥讓跑友上車等發車,虛嚇一場。清晨交通順暢,不到六點就抵達目的地-梅子夢工廠。

       

烤山豬肉是萄葡馬的特色之一,今年也不例外,準備了 19 隻山豬供跑友大快朶頤。

 
      

7點整準時起跑,獵槍鳴則槍提前一分鐘(改為嗚槍祈福而非起跑,猜想是為了方便拍照),還出動四軸空拍。但今年充氣拱門橫樑的紅布條只裝了一面,起跑集結照看不到2014葡萄馬字樣,覺得缺了點什麼。 XD

晨霧未散,遠處的山峰被染出不同深淺層層相疊,有幾分山水畫的意境。

沒多久跑進葡萄園間。右邊有葡萄,左邊是馬,我想這張照片拍出了葡萄馬的精髓~ XD

賽道沿著山谷的兩側圍成環狀,上演有趣的情境:

先在右側看到遠方高山上的道路,那是等下會經過的地方。媽呀!有沒有那麼高?
過了幾小時,向右方鳥瞰,那條麵條粗細的路是我剛才跑過的地方?媽呀!我已經爬這麼高了?

來個示範:左圖可看到右側山上有白色護欄,當時所在位置就是右圖的高架橋。

        

看到遙遠的山下有座紅色鐵橋,沒多久,鐵橋已經在眼前…

        

今年沒吃到甜椒,但葡萄較去年鮮甜多汁。補給有香蕉、葡萄、番茄、檸檬、酸梅、鹽、梅粉、黑糖、水、運動飲料,供應充足,甚至有兩個水站搞了小火爐現烤起山豬 XD

        

忘了準備上回光橋夜路發現的新法寶 – 隨身小水瓶,在起跑沒多久的私人補給站要了一支 600ml 礦泉水瓶頂著用,直到 10K 遇到第一家 7-11,繞進去挑了燕麥奶瓶(咦?是這樣縮寫嗎?),礦泉水瓶功成身退,感謝某縣長侯選人一路相挺。新瓶子的瓶口寬更方便加水,方形瓶身也挺好握,顯然又比上回的小水瓶更優。

       
 
       
 
       

原住民朋友的熱情加油是葡萄馬無可匹敵的特色,每經過村落聚點總是鑼鼓喧天,擊掌擊不停,看到小朋友伸長了手加上期待的眼神,不來個 Give Me Five 怎麼成? (註:加油團照片集左下有亮點,很棒吧!)

原住民率真的天性為馬拉松添加很多歡樂。在羅娜村的陡坡,一位大姐坐在路旁喊加油,卻不時補刀:「唉,怎麼都用走的?跑馬拉松要跑起來呀!」。水站義工大姐一邊剪葡萄一邊么喝:「我們信義鄉的葡萄最甜了」,一位跑友回捧:「謝謝啦,信義鄉的女生也很甜。」,大姐突然冒出一句:「哦?你怎麼知道?你有吃過?」。差點笑倒!

依慣例要來一張 32 公里紀念照.很認真地跟去年相比,白色部分應有重新補噴漆,另外太陽也比去年熱情一些。

一路上邊吃邊玩邊拍照(謎:你到底有沒有認真在跑啦?),成績就放一邊了。最後以 5:34:22 完賽(GPS錶計時),成績證明的大會時間相近,晶片時間卻少了13分鐘,莫非是程式故障,不過因為沒有 Source Code 跟原始資料可以分析,就不射茶包了 XD

好吃的山豬肉跟竹筒飯,還有一杯烏梅汁,領到後不到 30 秒就一飲而盡,沒能入鏡 XD

        

最後,補記這回看到的強者跑友,穿拖鞋,一路滾鐵圈滾上海拔一千公尺,再滾完全程 42 公里,而且速度頗快,在羅娜村折返線再次相遇已足足領先我兩公里有餘,很神! orz

【答客問】使用jQuery.ajax傳送複雜參數到ASP.NET MVC

$
0
0

讀者小黑提問:使用jQuery.ajax傳送物件陣列給ASP.NET MVC一文已示範如何使用jQuery.post()傳遞List<Player>到ASP.NET MVC,但依該做法傳送List<string>卻無法成功,應如何解決?

熬不住手癢,就來動手實測玩看看吧!改寫前文的MVC Action,加入接收string[] mylist參數,同時結果改為players及mylist一併回傳:

///測試用Action,前端接入List<Player>,轉JSON後傳回
public ActionResult Update(List<Player> players, string[] mylist)
        {
return Json(new
            {
                P = players,
                L = mylist
            });
        }

前端程式也得稍做修改,postMvc()時多傳送mylist字串陣列,data參數改為{ players: array, mylist: list }:

如小黑所言,MVC端未接收到mylist,呈現null。

使用瀏覽器偵錯工具檢查POST內容,mylist陣列被轉為兩個欄位名稱為mylist[]的參數。

陣列元素命名成arrayName[]似乎是PHP慣例,故被jQuery選為預設.ajax()編碼規則,但顯然與ASP.NET MVC不同。印象中看過?ary=1&ary=2&ary=3的URL參數陣列表示法,加上ASP.NET MVC需將player[0][Id]改成player[0].Id的經驗,直覺認定mylist[]改成mylist可過關,因此補上一段.replace(/%5B%5D=/g, "="),將所有blah[]=改為blah=應該就搞定了。

重新測試,欄位名稱修改成功!

MVC端也一如預期順利接到mylist字串陣列囉~

不過,以上做法有點Hacking。jQuery要傳送多個複雜參數給ASP.NET MVC,其實有更簡單的做法 - 用JSON格式就對了!

如以下範例,我寫了一個postJson()方法,只有兩個重點:將contentType設為application/json,data內容則改為JSON.stringify(data)。應用時以postJson()取代postMvc(),其餘不變:

<script>
        (function ($) {
            $.extend($, {
                postJson: function (url, data, succ) {
return $.ajax({
                        url: url,
                        type: "POST",
                        contentType: "application/json",
                        data: JSON.stringify(data),
                        dataType: "json",
                        success: succ
                    });
                }
            });
        })(jQuery);
 
        $(function () {
var array = [], list = ["Jeffrey", "Darkthread"];
            array.push({ Id: "P01", Name: "Jeffrey" });
            array.push({ Id: "P02", Name: "Darkthread" });
            $.postJson(
"@Url.Content("~/AjaxPost/Update")",
                {  players: array, mylist: list },
function(res) {
                    alert(JSON.stringify(res));
                },
"json"
            );
        });
</script>

如此,jQuery會以application/json Content-Type送出data資料物件的JSON字串(包含players及mylist兩個屬性)。當ASP.NET MVC偵測到Request Content-Type為application/json,會執行JSON反序列化,並取出其中的players與mylist,對應到Update(List<Player> players, List<string> mylist)的兩個參數,完成參數傳遞。

相形之下,採用JSON格式傳送不必擔心 beforeSend 遺漏轉換邏輯,再複雜的物件都能轉換,會是較簡潔可靠的做法,可優先考慮。

NG筆記18-訂閱屬性變更事件$watch()

$
0
0

KO是用ko.computed()及subscibe()追蹤ViewModel屬性變動做出反應,在NG中則可透過$scope.$watch()實現,寫法為$watch(觀察對象, 連動函式, 值比對開關)。

觀察對象可以是字串或函式,使用字串時完全比照data-bind="propName"的寫法,也支援運算,例如:"model.firstLame + model.lastName";若使用函式,輸入參數為$scope物件,再依需求傳回要觀察對象屬性或其組合運算內容,例如:function(scope) { return scope.firstName + model.lastName }。

連動函式規格為function(newVal, oldVal, scope),newValue即為前述關察對象字串或函式傳回結果。注意觀察對象函式會在每次$digest()時執行(例如:Scope其他屬性變動或ng-click、$apply()執行時),連動函式則在觀察對象改變時才觸發。另外,NG會一併傳回oldValue及整個Scope物件,以滿足複雜的應用情境。

值比對開關為Boolean,傳入true時,NG會改用angular.equals處理物件比對,新舊值可為不同物件,只要angular.equals()結果相等(物件各屬性都相同)就判為相等。但這種比對方式應用於大型或複雜物件時將耗用較多記憶體跟CPU,尤其在NG Dirty Check機制裡觀察對象字串或函式呼叫機率頗高,要小心對效能的影響。

以下是比照KO範例的簡單示範:

<!DOCTYPEhtml>
<htmlng-app="sampleApp">
<head>
<metacharset="utf-8">
<title>Lab 18 - 訂閱屬性變更事件</title>
</head>
<bodyng-controller="defaultCtrl">
<dl>
<dt>Name</dt>
<dd><inputng-model="model.name"/></dd>
<dt>Score</dt>
<dd><inputng-model="model.score"/></dd>
</dl>
<scriptsrc="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.js"></script>
<script>
    angular.module("sampleApp", [])
    .controller("defaultCtrl", function($scope) {
function myViewModel() {
var self = this;
        self.name = "Jeffrey";
        self.score = 32767;
      }
var vm = new myViewModel();
      $scope.model = vm;
//透過$watch關注Scope內屬性的變化
      $scope.$watch("model.name", function(newValue, oldValue) {
        console.log("name=" + newValue);
      });
      $scope.$watch(function(scope) {
return scope.model.score;
      }, function(newValue, oldValue) {
        console.log("score: " + oldValue + "->" + newValue);
      });
    });
</script>
</body>
</html>

Live Demo

這裡有個小問題。KO繫結<input>時預設onchange才會觸發重算(但可透過valueUpdate調整),比每按一個鍵就觸發一次連動有效率。但NG 1.2.*預設<input>一改變就連動,到1.3版才有updateOn參數可調,另一種做法是使用debounce設定將改變後延遲一段時間,累積多次變動只執行一次,也能解決問題,如以下示範:

<!DOCTYPEhtml>
<htmlng-app="sampleApp">
<head>
<metacharset="utf-8">
<title>Lab 15 - 訂閱屬性變更事件</title>
</head>
<bodyng-controller="defaultCtrl">
<dl>
<!-- ver 1.3+支援updateOn設定 -->
<dt>Name</dt>
<dd><inputng-model="model.name"ng-model-options="{ updateOn: 'blur' }"/></dd>
<dt>Score</dt>
<dd><inputng-model="model.score"
ng-model-options="{ updateOn: 'default', debounce: { default: 500 } }"/></dd>
</dl>
<div>
      {{model.name}}
</div>
<scriptsrc="http://code.angularjs.org/1.3.0-beta.10/angular.js"></script>
<script>
    angular.module("sampleApp", [])
    .controller("defaultCtrl", function($scope) {
function myViewModel() {
var self = this;
        self.name = "Jeffrey";
        self.score = 32767;
      }
var vm = new myViewModel();
      $scope.model = vm;
//透過$watch關注Scope內屬性的變化
      $scope.$watch("model.name", function(newValue, oldValue) {
        console.log("name=" + newValue);
      });
      $scope.$watch(function(scope) {
return scope.model.score;
      }, function(newValue, oldValue) {
        console.log("score: " + oldValue + "->" + newValue);
      });
    });
</script>
</body>
</html>

Live Demo Name欄位要移開焦點才更新,Score欄位則是停頓超過0.5秒就觸發

[NG系列]

http://www.darkthread.net/kolab/labs/default.aspx?m=post&t=angularjs

NG筆記19-資料檢核

$
0
0

與KO相比,NG的內建資料驗證功能強大許多。先看示範:

載入網頁時欄位均為白底,輸入資料後會觸發檢核,輸入值有效呈現綠底,不合要求則為紅底。變色關鍵來自以下CSS設定,NG會在使用者輸入資料後新增ng-dirty class,依檢核成功或失敗加上ng-valid或ng-invalid:
form .ng-invalid.ng-dirty { background-color: pink; }
form .ng-valid.ng-dirty { background-color: lightgreen; }

第一欄文字屬必填,加上required Directive即可,寫成<input name="text" ng-model="model.text" required />。

電子郵件欄位限定需為有效電子郵件地址,格式不符時後方會顯示錯誤提示,做法如下:

<inputtype="email"name="emailAddr"ng-model="model.emailAddr"required/>
<spanclass="invld-hint"
ng-show="myForm.emailAddr.$invalid && myForm.emailAddr.$dirty">
<spanng-show="myForm.emailAddr.$error.email">Email格式不正確</span>
<spanng-show="myForm.emailAddr.$error.required">必填欄位</span>
</span>

model為ViewModel物件,model.emailAdddr為普通的JavaScript屬性,NG提供.$error.emai檢測其值是否為有效Email地址格式(符合格式時傳回false),透過ng-show="myForm.emailAddr.$error.email"可決定後方錯誤提示顯示或隱藏。NG內建required, pattern, minlength, maxlength, min, max六種檢核Drective。[參考]

第三欄使用ng-minlength、ng-maxlength限制長度需為4-6個字元,ng-pattern指向Regular Expression限定[0-9A-Za-z],範例中ng-pattern繫結到ViewModel屬性,必要時可動態指定。

<inputtype="text"name="code"ng-model="model.code"ng-minlength="4"ng-maxlength="6"
ng-pattern="model.codePattern"/>
<spanclass="invld-hint"ng-show="myForm.code.$error.pattern">需為0-9, A-Z, a-z</span>

輸入內容繫結到{{model | json}},相當於整個ViewModel JSON序列化的結果,實測可發現,NG會自動排除檢核失敗欄位。

由上述觀察足以證明NG的ViewModel具備資料狀態概念,能掌握欄位是否被修改過(一開始白底,輸入後呈現綠底或紅底)以及欄位是否符合檢核要求,故在網頁下方加上表單狀態旗標顯示加以佐證。本範例表單命名為myForm(<form name="myForm">),由$scope.myForm.$pristine等參數可取得表單狀態,包含:[參考]

  • $pristine: 是否為原始狀態,使用者尚未更動?
  • $dirty: 使用者是否有更動任何內容?
  • $valid: 是否所有欄位都通過檢核?
  • $error: 表單各欄位的檢核結果,範例使用$error | json已濾掉$開頭的Angular專用參數,實際上我們可由$error取得欄位名稱等細節。

另外,利用<input type="submit" value="送出" ng-disabled="myForm.$invalid" />限定通過檢核才允許送出表單,也是常見的規格需求。

以下為完整範例:Live Demo

<!DOCTYPEhtml>
<htmlng-app="sampleApp">
<head>
<metacharset="utf-8">
<title>Lab 17 - 資料檢核</title>
<style>
    body, input
    {
        font-size: 9pt;
    }  
    form .ng-invalid.ng-dirty { background-color: pink; }
    form .ng-valid.ng-dirty { background-color: lightgreen; }
    .invld-hint { color: red; }
    .invld-hint span { margin-right: 6px; }
</style>
</head>
<bodyng-controller="defaultCtrl">
<formname="myForm">
<p>
請輸入文字:
<inputname="text"ng-model="model.text"required/>
</p>
<p>
請輸入電子郵件:
<inputtype="email"name="emailAddr"ng-model="model.emailAddr"required/>
<spanclass="invld-hint"
ng-show="myForm.emailAddr.$invalid && myForm.emailAddr.$dirty">
<spanng-show="myForm.emailAddr.$error.email">Email格式不正確</span>
<spanng-show="myForm.emailAddr.$error.required">必填欄位</span>
</span>
</p>
<p>
請輸入4-6位英數字
<inputtype="text"name="code"ng-model="model.code"ng-minlength="4"ng-maxlength="6"
ng-pattern="model.codePattern"/>
<spanclass="invld-hint"ng-show="myForm.code.$error.pattern">需為0-9, A-Z, a-z</span>
</p>
<p>
輸入內容={{model | json}}
</p>
<div>
表單狀態: 
      $pristine={{myForm.$pristine}}, 
      $dirty={{myForm.$dirty}},
      $valid={{myForm.$valid}}, <br/>
      $error={{myForm.$error | json}}
</div>
<hr/>
<inputtype="submit"value="送出"ng-disabled="myForm.$invalid"/>
<inputtype="button"value="Debug"ng-click="model.debug()"/>
</form>
<scriptsrc="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.js"></script>
<script>
    angular.module("sampleApp", [])
    .controller("defaultCtrl", function($scope) {
function myViewModel() {
var self = this;
          self.text = "";
          self.emailAddr = "";
          self.code = "";
          self.codePattern = new RegExp("^[0-9a-zA-Z]*$");
      }
      $scope.model = new myViewModel();
    });
</script>
</body>
</html>

[NG系列]

http://www.darkthread.net/kolab/labs/default.aspx?m=post&t=angularjs

【茶包射手日記】CSS Bundle路徑有"."造成HTTP 404

$
0
0

解掉一個古老懸案!

在ASP.NET MVC 4中使用Kendo UI Grid文章曾提過一個古怪問題:

發現StyleBundle的virtualPath參數出現2012.1.322時,會導致Styles.Render("~/Content/kendo/2012.1.322/css”)時傳回HTTP 404錯誤~ 為克服問題,我將2012.1.322目錄的內容向上搬一層,直接放在~/Content/keno目錄下,並將virtualPath設成"~/Content/kendo/css"避開問題。

但說來奇怪,問題後來好像自行消失,之後寫了好多專案,Bundle寫成Styles.Render("~/Content/kendo/yyyy.m.nnn/css”)都未再出狀況,這條密技也漸被淡忘。

最近接到同事回報,問題在某Windows 2003測試台出現,症狀很像:將檔案向上搬一層,或將路徑由2012.1.322改成2012_1_322或2012-1-322都可避開。很神奇地,若使用IIS Express或本機IIS7,即便用2012.1.322路徑也不會有任何問題。由這些線索,可歸納出以下幾點:

  1. 問題只出現在Windows 2003 IIS6,在Windows 7/2008R2 IIS7及IIS Express上不會發生。
  2. 另一個構成問題的條件是路徑中出現"."!將"."改為"-"或"_"就沒事。

為了調查,我建了一個MVC小專案,在其中使用Styles.Render("~/Content/kendo/2012.1.322/css”),將專案部署到出問題的Windows 2003主機,居然也沒出錯。跟同事要來問題專案Source,抽離掉無關模組,只留下一個cshtml引用Styles.Render("~/Content/kendo/2012.1.322/css”),部署到問題主機,你猜怎麼著,一切正常。一發狠,將整個問題專案檔案搬到為測試另建的IIS Web Appliation資料夾,噗!問題也沒出現。

經過這番比對,茶包呼之欲出 - 關鍵應在IIS的網站應用桯式設定上!很快地,一把抓出這枚躲了兩年多的茶包:

問題網站少了萬用字元應用程式對應(需加入的理由可參考保哥文章的常見問題3),而新建測試網站有加,這解釋檔案搬移到測試網站目錄何以問題消失。但有一點與我的認知不同,記得在古早時代,少了這條連/home/index都會不會通,但問題網站沒設定也跑得很好,只有KendoUI CSS Bundle出問題,莫非是新版MVC的魔法?

在一篇MSDN Blog找到答案:

In v4.0 there is a new feature that allows extensionless URLs to be directed into managed code, without impacting static requests (HTML, JPG, GIF, CSS, JS, etc).  Because of this feature, on IIS 6 you no longer need a wildcard mapping and on IIS 7 you no longer need to set runAllManagedModulesForAllRequests=”true” or remove the "managedHandler" precondition for the UrlRoutingModule.  It works by default on both IIS 6 and IIS 7, except that you need a QFE from the IIS team to make this work on Windows Vista SP2, Windows Server 2008 SP2, Windows Server 2008 R2, and Windows 7.  Once you obtain the IIS 7 QFE and install v4.0 ASP.NET, you’ll be able to route extensionless URLs without impacting static requests.  The QFE enables a new “*.” handler mapping—the notation may seem weird, but all you care about is the fact that this maps to URLs without an extension.  ASP.NET registers a “*.” handler mapping when v4.0 is installed.  If you don’t have the IIS 7 QFE, that handler mapping does nothing.  If you have the IIS 7 QFE, extensionless URLs are mapped to our handler, which enables them to be routed by the UrlRoutingModule.  Information about the IIS 7 QFE and steps to download it can be found at http://support.microsoft.com/kb/980368.  For the record, the implementation of this feature on IIS 6 is done quite differently, and I won't go into that here.

簡單來說,ASP.NET 4.0註冊了一個副檔名為"*."的對應,而IIS6/7更新後也實做了"*."的對應邏輯,藉以將無副檔名URL交給ASP.NET ISAPI處理(例如:ASP.NET MVC的/home/index、ScriptBundle/StyleBundle的"bundles/jquery"、"~/Content/kendo/2012.1.322/css"都屬於無副檔名URL),比過去使用萬用字元對應將html、jpg、css、js等靜態檔也一併納入有效率多了!

這次遇到的問題,應屬IIS6處理*.對應邏輯的瑕疵,當路徑出現"."時,會落入原有的IIS靜態檔案邏輯,因而得到HTTP 404回應。為了驗證這點,我做了一個小實驗,在RouteConfig加入一個包含"."的特殊URL "foo.bar/{id}:

publicclass RouteConfig
{
publicstaticvoid RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
        routes.IgnoreRoute("favicon.ico");
 
        routes.MapRoute(
            name: "Foo",
            url: "foo.bar/{id}",
            defaults: new { controller = "Home", action = "Foo", id = UrlParameter.Optional }
        );
 
        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
}

HomeController則加入Foo Action:

publicclass HomeController : Controller
    {
public ActionResult Index(string culture)
        {
return View();
        }
 
public ActionResult Foo(string id)
        {
return Content("Foo: " + id);
        }
    }

本機測試OK:

部署到IIS6,/home/index正常,一如預測,foo.bar/Blah出現HTTP 404:

在IIS6加入萬用字元應用程式對應後運作正常,證實了我們的推測!

【結論】

IIS6的無副檔名URL處理邏輯在遇路徑有"."(dot)時會失效,導致Styles.Render("~/Content/kendo/2012.1.322/css”)出現HTTP 404錯誤,若不調整路徑,需加入萬用字元應用程式對應解決(但會因所有靜態檔都經過ASP.NET ISAPI影響效能),此問題在IIS7+則不會發生。另外,若ASP.NET 4.0在IIS6未遇URL有"."的特殊狀況,已不需設定萬用字元應用程式對應。

PS:我沒能找到官方文件佐證,在ASP.NET論壇找到一篇討論,MS員工回覆相似的結論
I think with IIS 6, your only solution is going to be to set up a wildcard extension.  The decimal is going to be seen by IIS 6 as an extension, so setting up the extensionless url feature won't address the issue. 另外,回覆還提到明確指定由StaticFileHandler處理css, js等靜態資源請求,可以減緩萬用字元應用程式對應造成的效能影響,值得參考。

【茶包射手日記】無法使用滑鼠操作及捲動IE

$
0
0

筆電的IE10@Windows 8近來怪怪的,網頁開啟後無法上下或左右捲動。更棈準地說,網頁本身對滑鼠移動、點擊有反應,IE部分都無法用滑鼠操作。如下圖黃色部分即對滑鼠毫無反應,使用滾輪也不會捲動,圖中的檢視選單還是靠Alt、右鍵、下鍵才叫得出來,顯示後卻仍不能用滑鼠點選,只能靠上下鍵及Enter操作。

按F12可以叫出Dev Tools,但一樣不能用滑鼠操作,按Ctrl+P切成獨立視窗,無法放大縮小移動,只能靠快捷鍵操作。

最好笑的是這個,JavaScript彈出的alert對話框,滑鼠點到生火也關不了,只能按Enter或Alt-F4關閉。

網頁關閉提示也是,只能靠上下鍵或Tab加Enter回應。

綜合以上觀察,網頁自身還算正常,所有IE10管轄的UI元素及視窗都對滑鼠沒反應(包含網頁按右鍵叫出的右鍵選單及alert對話框),使用鍵盤倒可正常操作,嘗試過重新開機,問題依舊。

爬文相似個案不多,找到一篇相關文章,一樣是Win8+IE10,只提到滑鼠對捲軸失效(觸控仍可捲,我的案例是觸控也失效)鍵盤可用,未涉及IE UI大規模對滑鼠無感。討論並無解法共識,甚至有一起修復Office後問題消失的奇妙案例。

我第一步想到的招式是Windows Update。21個更新與IE相關的只有KB3001237 Flash Player及KB2987107 IE10積存安全更新,而說明文件未提及能修復類似問題。姑且一試,問題在裝完更新重開機後消失,但難以驗證與安裝更新有關,僅留下記錄供有緣人參考、回饋。


【茶包射手日記】Angular 1.3升級踩雷記

$
0
0

孵了八個月,AngularJS 1.3版終於在前幾天破殼而出

一直很期待的ngModelOptions updateOn功能隨著1.3版問市,未來繫結到<input>可指定移開焦點才觸發,比每敲一個字母重算一次有效率,另外也可選擇debouncing累積多次變化只重算一次(相當於KO的throttle擴充方法),這些都是之前在寫KO MVVM慣用的做法,1.3起正式支援。

另外還看到一些亮點:

有這些新武器,Angular就更好用了。

迫不及待地把進行中專案的Angular從1.2.25升到1.3。薑!薑!薑!薑~ 換裝新引擎的網站… 整個爛掉了! 無法建立Controller,大小繫結隨之全軍覆沒… orz

才一升級就被迫觀摩新版Source Code,莫非是程式魔人的宿命。使用消去法逐一拔掉專案模組縮小範圍,最後鎖定一段參考stackoverflow討論加入的$controller Decorator邏輯,如下所示:Demo

<!DOCTYPEhtml>
<htmlng-app="app">
<head>
<metacharset="utf-8">
<title>Controller Decorator</title>
</head>
<bodyng-controller="myCtrl">
<div>{{test}}</div>
<scriptsrc="//code.angularjs.org/1.3.0/angular.js"></script>
<script>
//http://stackoverflow.com/questions/23382734/angularjs-get-controller-name-from-scope
    angular.module("app",[])
    .config(["$provide", function($provider) {
      $provider.decorator("$controller", [
"$delegate", function($delegate) {
returnfunction(constructor, locals) {
//do nothing, just to test decorator
return $delegate(constructor, locals);
          }
        }
      ])
    }])
    .controller("myCtrl", function ($scope) {
        $scope.test = "TEST";
    });    
</script>
</body>
</html>

程式執行會出現以下錯誤,Controller建立失敗,後面全都不用玩:

"TypeError: object is not a function
    at https://code.angularjs.org/1.3.0/angular.js:7594:13
    at forEach (https://code.angularjs.org/1.3.0/angular.js:343:20)
    at nodeLinkFn (https://code.angularjs.org/1.3.0/angular.js:7593:11)
    at compositeLinkFn (https://code.angularjs.org/1.3.0/angular.js:6991:13)
    at compositeLinkFn (https://code.angularjs.org/1.3.0/angular.js:6994:13)
    at publicLinkFn (https://code.angularjs.org/1.3.0/angular.js:6870:30)
    at https://code.angularjs.org/1.3.0/angular.js:1489:27
    at Scope.$eval (https://code.angularjs.org/1.3.0/angular.js:14123:28)
    at Scope.$apply (https://code.angularjs.org/1.3.0/angular.js:14221:23)
    at bootstrapApply (https://code.angularjs.org/1.3.0/angular.js:1487:15)"

將angular換成1.2.25或將$provider.decorator()片段,問題就會消失。迫不得已,開始Line By Line Debug追蹤,比對加上及移除$provider.decorator("$controller", …)的行為差異,最後找到一處關鍵:

加入decorator時,later = false;未加時,later = true。再往源頭找,真相大白:

angular.js 1.2.25版

/**
     * @ngdoc service
     * @name $controller
     * @requires $injector
     *
     * @param {Function|string} constructor If called with a function then it's considered to be the
     *    controller constructor function. Otherwise it's considered to be a string which is used
     *    to retrieve the controller constructor using the following steps:
     *
     *    * check if a controller with given name is registered via `$controllerProvider`
     *    * check if evaluating the string on the current scope returns a constructor
     *    * check `window[constructor]` on the global `window` object
     *
     * @param {Object} locals Injection locals for Controller.
     * @return {Object} Instance of given controller.
     *
     * @description
     * `$controller` service is responsible for instantiating controllers.
     *
     * It's just a simple call to {@link auto.$injector $injector}, but extracted into
     * a service, so that one can override this service with [BC version](https://gist.github.com/1649788).
     */
returnfunction(expression, locals) {
var instance, match, constructor, identifier;

angular.js 1.3.0版

/**
     * @ngdoc service
     * @name $controller
     * @requires $injector
     *
     * @param {Function|string} constructor If called with a function then it's considered to be the
     *    controller constructor function. Otherwise it's considered to be a string which is used
     *    to retrieve the controller constructor using the following steps:
     *
     *    * check if a controller with given name is registered via `$controllerProvider`
     *    * check if evaluating the string on the current scope returns a constructor
     *    * if $controllerProvider#allowGlobals, check `window[constructor]` on the global
     *      `window` object (not recommended)
     *
     * @param {Object} locals Injection locals for Controller.
     * @return {Object} Instance of given controller.
     *
     * @description
     * `$controller` service is responsible for instantiating controllers.
     *
     * It's just a simple call to {@link auto.$injector $injector}, but extracted into
     * a service, so that one can override this service with [BC version](https://gist.github.com/1649788).
     */
returnfunction(expression, locals, later, ident) {
// PRIVATE API:
//   param `later` --- indicates that the controller's constructor is invoked at a later time.
//                     If true, $controller will allocate the object with the correct
//                     prototype chain, but will not invoke the controller until a returned
//                     callback is invoked.
//   param `ident` --- An optional label which overrides the label parsed from the controller
//                     expression, if any.
var instance, match, constructor, identifier;

1.3.0版的$controller()增加兩個新參數later及ident,而先前decorator()程式針對1.2.X撰寫,只有expression與locals,傳遞到$delegate時漏了later及ident,導致跑錯邏輯。$controller被定義成Private API,故Angluar自由調整無可厚非,但decorator()無可避免與其產生相依,因而被波及。

知道原因,稍加修改補上later及ident,裝妥Angular 1.3新引擎的專案就順利起飛囉~

【茶包射手日記】Outlook在轉寄、回覆或暫存信件時程式凍結無回應

$
0
0

症狀如後:Outlook 2010收到來自其他部門的郵件,開啟信件準備回覆或轉寄,在按下暫存或傳送時Outlook UI凍結無回應。

經檢查比對,推測為信中包含無效URL的<img>所致(URL指向寄件者才存取得到的內部IP),試用一小段程式重現問題:

    ExchangeService ews = new ExchangeService(ExchangeVersion.Exchange2007_SP1);
    ews.Credentials = new WebCredentials("userid", "pwd", "domain");
    ews.Url = new Uri(ewsUrl);
    EmailMessage newMsg = new EmailMessage(ews);
    newMsg.ToRecipients.Add(new EmailAddress("receiver@mail.com"));
    newMsg.Subject = "無效外部圖檔測試";
    newMsg.Body = new MessageBody(BodyType.HTML, 
"<html><body>Outlook Killer! " + 
"<img src=\"http://172.16.1.88/images/encrypt.gif\" >" +
//"<img src=\"http://wwww.no-such-domain.com.tw/images/encrypt.gif\" >"
"</body></html>");
    newMsg.Send();

以上程式將送出一封內文採HTML格式的信件,其中包含<img> src指向不存在的內部IP(172.16.1.88),信件圖檔無法顯示出現叉燒包,轉寄或回覆時按左上方磁片圖示存檔或傳送鈕,將導致Outlook 2010操作介面凍結無回應。但測試發現,耐心等待撐得夠久,作業仍會完成並恢復正常運作:

經過測試觀察,分析如下:

  1. 由於信件來自公司Exchange內部傳送,故Outlook不會阻擋圖片下載。(以下為外部信件,如圖所示需使用者授權,Outlook才會下載內嵌圖檔。)
  2. 在轉寄、回覆或暫存時(甚至在草稿匣中點選預覽也是),Outlook會試圖下載擷取<img>圖檔,由於其src非有效IP,而Outlook使用前景執行緒下載,在連線逾期之前,程式呈現無回應狀態。
  3. 在網路找到一篇文章提及類似狀況及一個Hotfix,安裝後並無改善。仔細推敲,該案例的無效URL是(102.112.2o7.net),本例則為IP。做了另一個測試,將無效IP改為www.no-such-domain.com.tw,則不會造成UI凍結。因此猜測該Hotfix修正Domain Name無效凍結問題,但未能涵蓋無效IP的情境。

【結論】

這個問題只發生在<img> src使用無效IP的Exchange內部信,由於Outlook使用前景執行緒下載內嵌圖屬不良設計,造成操作界面長時間凍結。嚴格來說,此狀況不算常見情境,遇到時宜將無效<img>刪除可解決,就不積極圖謀改善了。

Android SDK模擬器安裝筆記

$
0
0

為了測試網頁在不同Android版本的瀏覽效果,我想用Android SDK提供的模擬器跑不同版本Image,省去找實體機的麻煩。雖然最後因效果不佳放棄,但耗了大半天,多少對Android SDK、Eclipse多了一些了解,整理筆記以備日後不時之需:

  1. 一開始我是在Windows 2008 R2安裝ADT,使用SDK Manager下載好System Image,以Android 4.0.3為目標,成功啟動模擬器。但鑑於ARM版本速度不佳,而x86版硬體加速需要Intel Virtualization Technology(VT)支援,在我的Win2008R2已被Hyper-V佔用,便把腦筋動到另一台拿來跑iOS模擬器的iMac上,打算把Android模擬器的重任也交給它。
  2. http://developer.android.com/sdk/index.html可以下載Mac版Eclipse,卻一啟動就觸礁,Eclipse抱怨沒有Java SE 6 Runtime可用:
  3. 下載安裝JDK 8,訊息依舊,爬文知道這是Oracle設下的陷阱JRE VM設定短缺所致,依文章指示,將Info.plist複製到~/Downloads,修改後複製回去(需sudo),缺少Java Runtime的問題即告排除。(PS:學會Command Line開文字編輯器的技巧,open –a TextEdit ~/Downloads/Info.plist)
  4. 閃過第一個陷阱,妖怪一號(史萊姆)登場。Eclipse未經簽署,需手動放行。

    找到「系統偏好設定/安全性與隱私」

    Mac直接列出放行Elipse的選項,很貼心。
  5. 開啟Eclipse後由Windows選單啟動Android SDK Manager,卻因公司防火牆偷換SSL憑證,Android SDK Manager認定憑證無效拒絕下載,理論上只需調整SDK Manager設定,不走HTTPS改用HTTP即可解決。妖怪二號(巴風特)上陣,我找不到Android SDK Manager的設定選單!
  6. 爬文找到密技,得從Shell下指令./android sdk開啟SDK Manager,才會出現偏好設定選單:
  7. SDK Manager下載好Intel x86 Emulator Accelerator (HAXMM Insatller)還要手動執行安裝。在Mac上以OSX 10.10為界,分成兩種不同版本,用的iMac是10.9,所以要執行sdk/extras/intel/Hardware_Accelerated_Execution_Manager/IntelHAXM_1.1.0_below_10.10.dmg (Windows的安裝程式是intelhaxm.exe,我的Windows 2008R2啟用了Hyper-V裝不了)
  8. 啟動模擬器時,記得要指定顯示尺寸。依原解析度,裝置一轉向常常下半截就超出電腦螢幕下緣,看不到拖不動捲不了。另外,Android模擬器沒有UI按鈕可以旋轉裝置,不少操作得透過快捷鍵
  9. 順利在Mac啟動4.0.3 Intel x86 Atom System Image模擬器,大魔王登場了-我無法在4.0.3模擬器執行Chrome?由於Android 4.0.3未內建Chrome瀏覽器,得自己設法安裝,在這裡卡了大半天, 以下是我嘗試的過程。
  10. 在網上找到在Android模擬器安裝Google Play商店的方法,照方煎藥真的成功哩!設好帳號進入Goolge Play,搜尋"Chrome"卻找不到Chrome瀏覽器項目。爬文找到修改Google Play篩選器的解法(篩選器會依據硬體、軟體、電信商決定使用者可以查詢及下載的App範圍),但要Unzip apk修改裝置識別資料,破解程序看似複雜,擔心一陷進去不知何年何月才能脫身(我只不過是想用模擬器跑Chrome讓專案測試簡便一點的Android麻瓜,並不想鑽進去當駭客啊~),放棄!
  11. 在APK4Fun可以找到Chrome Browser APK,下載後用adb install chrome-browser-38.0.2125.102-www.apk4fun.com.apk三兩下就裝好,可惜…

    執行後出現「找不到執行Chrome所需的重要功能;Chrome安裝程序可能未完成,或是Chrome與這個Android版本不相容。」錯誤,App自動結束。
  12. 在一篇文章找到說明:作者說在模擬器使用Chrome時常會遇到此一錯誤,建議改用Chromium,並寫了自動下載安裝的Script。試著下載安裝Content Shell及Chrome Shell,但一執行就閃退沒搞頭。
    爬文找到答案:Chromium is built for ARM, not x86, so it won't run on the x86 emulator unless you build it yourself for x86.
  13. 改用ARM(armeabi-v7a)版Image跑模擬器,安裝Chromium,終於能啟動Chrome Shell成功瀏覽網頁。如圖所示,Chromium的操作介面與Chrome有點出入,但二者核心相近,應可勉強頂著用:
  14. 抱著姑且一試的心情,在ARM版模擬器也裝上Chrome Browser APK,意外發現可以成功執行耶~
    之前遇到的「找不到執行Chrome所需的重要功能…」錯誤,看來也跟x86 vs ARM有關。

 

雖然成功在Android模擬器跑Chrome瀏覽器,短暫試用後還是打消了用模擬器測試的念頭,有幾個原因:

  1. ARM版模疑器實在太 慢 了!每一步操作後就是等等等,急驚風如我,操作個三分鐘就差不多到達人體極限,再多用兩分鐘,如果我沒把電腦砸爛,八成是因為中風了。
    另外,我還遇到等待網頁下載時性急在空白處多點一下,Chrome就直接跳到點選位置連結所指的下一頁,模擬器已經慢到觸控與顯示無法同步囉~
  2. 既然得改用ARM,我試著改在Windows 2008R2跑模擬器,結果慘不忍睹,三不五時就Crash或Not Responding… 多來個幾次,如果我沒把電腦砸爛,八成是因為中風了。
  3. 不管在Mac或Windows,操作常會被「很抱歉,系統UI已停止」的訊息打斷,很不流暢,多看上幾回,如果我沒把電腦砸爛,八成是因為中風了。

為了健康與效率,最後還是Android測試還是回歸實體機,忙了半天回到原點,唯一的收獲是Android模擬器與Mac經驗點數+1。

[2014-10-25更新]許多朋友強力推薦Genymotion,原本以為它基於VirtualBox只能跑x86,但似乎有解,但仍有跟Hyper-V搶Intel VT的問題待處理

2014貓空半馬

$
0
0

貓空半馬,連續第五年。今年跑了八場馬拉松,海山馬體驗人生首次落馬後,後面賽事就改走荒唐墮落風,美其名享受比賽,其實是吃吃喝喝拍照片看風景,一直玩一直玩… 甚至以跑關門為樂。直到入秋後天氣漸涼練得較勤,速度有些起色,貓空半馬是我的主場,在自家後院跑成績可不能太難看。馬場荒唐大叔洗心革面,這回要認真跑!

賽前一天早上再去政大操場小繞10K,氣溫22度多雲微雨,跑來十分舒適,幾乎全程維持5:30,自我感覺十分良好,一度深感本次破PB有望。10/26比賽當日,六點才起床,不慌不忙整裝,七點出頭才從家裡出發(這是地主隊獨有優勢啊),抬頭望見僅有薄雲的晴空… 不妙~

十月進入賽事高峰,一到週末多場全馬半馬同時登場,疏散了人流,本屆貓空半馬的參賽人數感覺沒有往年多。

抱著拼成績的決心全力以赴,途中沒拍照,連進水站都不敢多逗留,只喝一杯水就連忙啟程趕路。只是今年氣溫比去年高出許多(查了舊文,去年氣溫不到20度,今年起跑就27度),稍一加速心跳就直往上衝,四十多年的老爺車可經不起亂操,小心翼翼維持在可容忍的高檔。途中也不時停下來小走一段讓引擎冷卻,花了33分爬上杏花林,草湳橋折返已耗時1h05m,到此心裡有數,破PB應已無望,甚至想保Sub2都很有困難。回程通過杏花林已1h37m,僅剩23分鐘要跑完5K對我是天方夜譚,僅能把抱握下坡用小碎步追一點時間回來。回程接近政大後門時,右腳跟一陣緊繃,有瀕臨抽筋的不祥預感,趕緊放慢速度,蔣公銅像到藝文中心的斷魂坡只敢快走與碎步跑交替,體育場的最後半圈跑道也沒本錢衝刺,只跑出2:02:42的成績。

今年補給中規中矩,水、運動飲料、糖果、香蕉,供應充足。但我每個水站都只喝一杯水就跑,沒多品嚐享用,當一個認真的跑者來著。

大會紀念排汗衫,首次由白色配有色短袖(淺藍/深藍/淺綠)萬年款換成螢光綠加藝術字體洞洞衣,質感不錯。

最值得一提的,總算不再是公版獎牌配紅白藍條紋掛繩,今年的完賽獎牌為鏤刻浮雕的金屬獎牌,好看!

獎牌背面的設計也很對味!

從GPS錶取出資料跟去年對照,除了最後5K有幾公里均速快了幾秒,其餘每公里幾乎慢20秒以上,總成績比去年慢5分鐘一點也不冤枉,氣溫偏高推測是最主要原因… 但話說回來,怪東怪西找藉口,說穿了不就是實力不足努力不夠,回去好好練過明年再來吧!

配速分析(2013): 5:25 / 5:18 / 6:36 / 6:55 / 7:09 / 5:57 / 6:13 / 5:22 / 5:31 / 5:43 / 5:09 / 5:07 / 5:10 / 5:17 / 5:20 / 5:25 / 4:39 / 5:00 / 4:50 / 5:16 / 5:31 / 4:26

配速分析(2014): 5:43 / 5:51 / 6:26 / 7:15 / 7:50 / 6:29 / 6:41 / 5:27 / 5:52 / 6:11 / 5:34 / 5:19 / 5:35 / 5:50 / 5:30 / 6:00 / 4:40 / 4:47 / 4:41 / 6:19 / 5:01

TIPS-Chutzpah回報需要ECMAScript 5+

$
0
0

使用Chutzpah執行TypeScript單元測試,出現以下錯誤:

foo.ts(14,9): error TS1056: Accessors are only available when targeting ECMAScript 5 and higher.

檢查foo.ts發現其中使用get/set宣告屬性,比照C#在屬性讀取及設定時加入特定邏輯,如以下範例(Demo),foo.Name="Jeffrey",但alert(foo.Name)會得到"Jeffrey$"(Jeffrey變有錢了 XD)。而這需要用到ECMAScript 5+ JavaScript規格。

class Foo {
private name: string;
    get Name() {
return name;
    }
    set Name(n) {
        name = n + "$";
    }
}
var foo = new Foo();
foo.Name="Jeffrey";
alert(foo.Name);

TypeScript在編譯時可選擇TypeScriptTarget,配合瀏覽器支援程度編譯成符合ECMAScript 3或ECMAScript 5規範的JavaScript,專案本身編譯OK,是因為.csproj中TypeScriptTarget預設為ES5(可參考相關文章的csproj範例),推測是因為Chutzpah的TypeScriptTarget預設為ES3,導致get/set語法編譯失敗。幸好Chutzpah提供很彈性的設定方式,只需在資料夾新増chutzpah.json檔案填入設定值,該資料夾內的TypeScript或JavaScript就會套用該組設定。而且跟web.config一樣,chutzpah.json支援繼承概念,子資料夾設定只需加入差異項目,其他設定會沿用父資料夾的設定,十分方便。

如圖所示,加入chutzpah.json,指定TypeScriptCodeGenTarget為ES5後,問題消失!

為Chutzpah按個讚!

Viewing all 2429 articles
Browse latest View live