[javascript] 跨瀏覽器的屬性判斷

今天在抓蟲的時候發現一個很容易出錯的地方。當我們在宣告變數時,常常會因為不同的 browser 有不同的 API 定義,然後用以下的寫法來判斷宣告哪一個屬性,例如:

var fullscreenElement = document.mozFullScreenElement || document.webkitFullscreenElement || document.fullscreenElement;

||來檢查要用哪一個 attribute。

不過要小心 javascript 的值當成條件時的判斷。

例如:

var sLeft = window.screenLeft || window.screenX; //firefox use screenX
console.log(sLeft); 

這段程式希望 screenLeft 會回傳 window.screenLeft ,而在 firefox 會回傳 window.screenX。reference: Window screenLeft and screenTop Properties

但是如果 screenLeft 剛好等於 0 的時候,就會進入||之後的條件了,接著就 gg 了。

因此建議在值的判斷還是正統一點用 hasOwnPropertytypeof 來判斷會比較精準。

var sLeft = window.screenLeft;
if( !window.hasOwnProperty('screenLeft')) sLeft = window.screenX; 

Jenkins Notes

Jenkins

最近把 jenkins 架起來,筆記一下遇到的問題。

Install and Upgrade

安裝方式請參考http://pkg.jenkins-ci.org/debian/,可能我在多年前有安裝過,因此安裝時出現這樣的error:

Preparing to replace jenkins 1.480.3+dfsg-1~exp2 (using .../archives/jenkins_1.535_all.deb) ...
Unpacking replacement jenkins ...
dpkg: error processing /var/cache/apt/archives/jenkins_1.535_all.deb (--unpack):
 trying to overwrite '/usr/share/jenkins/jenkins.war', which is also in package jenkins-common 1.480.3+dfsg-1~exp2
dpkg-deb: error: subprocess paste was killed by signal (Broken pipe)
jenkins stop/waiting

要移除掉重新安裝:

sudo apt-get purge jenkins
sudo apt-get update jenkins

Reference:
problems installing latest version of jenkins

Nginx + Jenkins

jenkins 預設跑在8080 port,因此直接連到 http://[JENKINS_SERVER]:8080 就可以連到。不過我不太喜歡打 port,所以想要把它藏在 nginx 後面。

1. Nginx config

nginx 設定檔可以參考 Running Jenkins behind Nginx

2. Disable external 8080 port

另外還是要將對外的8080 port鎖起來,請在 /etc/default/jenkins最底下的JENKINS_ARGS之前加入這幾行

JENKINS_LOCAL_ONLY="yes"
if [ "$JENKINS_LOCAL_ONLY" = "yes" ]; then
PARAMS="$PARAMS --httpListenAddress=127.0.0.1"
fi

Reference:
Starting and Accessing Jenkins

3. Enable Jenkins log

若要看 jenkins 的log,還要加上 log 的參數設定

JENKINS_ENABLE_ACCESS_LOG="yes"
if [ "$JENKINS_ENABLE_ACCESS_LOG" = "yes" ]; then
  PARAMS="$PARAMS --accessLoggerClassName=winstone.accesslog.SimpleAccessLogger --simpleAccessLogger.format=combined --simpleAccessLogger.file=/var/log/jenkins/access"
fi

Reference:
Starting and Accessing Jenkins

4. No valid crumb was included in the request

設定完之後基本上就可以用了,不過打開瀏覽器的 console 一直會出現 No valid crumb was included in the request,後來我將 http://[JENKINS_SERVER]/configureSecurity/ 最底下的 Prevent Cross Site Request Forgery exploits 的打勾取消掉,就沒事了。(PS.我也不知道有什麼影響)

設定你的應用程式執行環境變數

因為我對系統不太熟,比較麻煩的是要確認你在執行的環境變數,像是你安裝 rvm 之後 ruby 的執行位置等等,我主要是拿來跑 grunt 的 test 與 build,可以參考這篇的 PATH 設定:jenkins configuration yeoman based angularjs web application

Bitbucket hook Jenkins

最後最重要的就是要把你的專案 hook 到 Jenkins 啦,我測試的 project 放在 Bitbucket,所以就用 Bitbucket 來舉例。

  1. 安裝 jenkins plugin: 到 manage plugin/available 安裝 Build Authorization Token Root Plugin。
  2. 到你的 job 設定裏頭打開 Build Triggers/ Trigger builds remotely,設定 Authentication Token,填入自己設定的一組 Token(例如 build1234567)
  3. 測試 http://jenkins-server/buildByToken/build?job=myjob&token=build1234567 是否有觸發 job 的執行。
  4. 到 bitbucket 你自己的 repository -> setting -> hooks -> select a hook -> Jenkins -> Add hook

參考資料 Jenkins hook management

接下來很重要!!

馬的我追 log 追半天一直 hook 不成功,總之是因為 jenkins 的 hook 格式跟 bitbucket 送出的資料對不起來(我不曉得是哪邊的 bug),bitbucket POST 出去的 data,Jenkins 不吃。

螢幕快照 2014-03-07 下午1.18.46.png

請在 Endpoint 裏頭填入第三點的 url 並且加上一個 &,讓他忽略之後的變數,例如:
http://jenkins-server/buildByToken/build?job=myjob&token=build1234567&

剩下都留空白。就好了。

大功告成

push 之後看到 jenkins 開始聽話的工作了,喝杯咖啡犒賞一下自己。

[VIM] ternjs, YouCompleteMe

同事 othree 分享了 ternjs,之前看到沒有支援 coffee 就放棄了,沒想到強者我同事就順手加了 coffee 的支援,看起來蠻不錯用的,記錄一下安裝方式。

Install tern_for_vim

這裡比較麻煩的是因為 tern 的原作者失聯,為了要支援 coffee script ,因此他自己 clone 了一個版本出來 support。

  1. install https://github.com/othree/tern_for_vim with bundle
  2. cd ~/.vim/bundle/tern_for_vim/
  3. cd tern
  4. npm install

Install YouCompleteMe

完整的安裝說明請參考https://github.com/Valloric/YouCompleteMe#mac-os-x-super-quick-installation

  1. brew install vim make sure your vim version is up to 4.7
  2. brew install CMake
  3. install https://github.com/Valloric/YouCompleteMe with bundle
  4. cd ~/.vim/bundle/YouCompleteMe
  5. ./install.sh --clang-completer

Add triggers to .vimrc

設定你的觸發鍵.,例如當你打.時就會跳出自動完成視窗

let g:ycm_semantic_triggers =  {
    \\   'coffee': ['.'],
    \\ }

Add .tern-project file to your project

在你的專案加入一個檔案,內容如下。

{
    "libs": [
    ],
    "plugins": {
        "requirejs": {
            "baseURL": "app/assets/javascripts/"
    },
        "coffee": {}
    }
}

plugin 還支援 node, requireJS, Angular, Doc Comment,有空來玩玩看。http://ternjs.net/doc/manual.html#plugins


Done!

接下來就可以在你的專案裡頭的 coffee 使用了,實際使用大概像這樣,可以直接用 require 來讀取底下的 method or attribute。
Screen Shot 2014-02-10 at 下午4.33.34.png

reference:

  1. http://tilvim.com/2013/08/21/js-autocomplete.html
  2. http://ternjs.net/
  3. https://github.com/Valloric/YouCompleteMe
  4. https://blog.othree.net/

[Angular] grunt-usemin

在使用 Angular generator 建立 project 相當的方便,yo angular [app-name]就可以裝完大部份所需要的東西,執行grunt build就會做好 uglify, minimize, packing, 等等的功能。其中一個對於線上環境很重要的功能就是 usemin。功能很強大,設定有點多,因此我順便筆記一些一開始撞牆的地方。

Blocks 打包某區塊裡頭的檔案(js/css)

例如你可以在 index.html 裡頭看到

<!-- build:js js/app.js -->
<script src="js/app.js"></script>
<script src="js/controllers/thing-controller.js"></script>
<script src="js/models/thing-model.js"></script>
<script src="js/views/thing-view.js"></script>
<!-- endbuild -->

這裡的設定是說在執行 usemin 時會將這幾隻 script 打包起來 output 成 js/app.js。因此如果你有額外新增加的程式需要一起打包的必須放在 block 裡頭,不然不會被打包進去。

不過在 build 完之後會發現有些檔案並沒有被打包進去,問題在於需要 compile 的程式(例如sass, coffee)會先被 compile 到 .tmp/ 的 folder 底下,因此預設的 search path 是搜尋不到這個檔案的。根據文件的說明:

<!-- build:<type>(alternate search path) <path> -->

()來指定要搜尋的路徑,但如果是多個路徑該怎麼指定?查了半天才知道要用{}然後,分隔
例如:

<!-- build:css({app,.tmp}) styles/main.css -->

(ㄜ.......這啥鬼格式?)

revmap

rev 這個 task 會將 js/css/image/fonts 等做重新 rename 的動作,讓程式可以做到快取的優化,例如 foo.png 會變成 foo.1234.png 這樣,而 usemin 的工作就是將 html 裡頭包含該檔案的路徑做取代。例如

<img src="/images/toolset.png" alt="Yo, Grunt, Bower">

會變成

<img src="/images/75bab8-toolset.png" alt="Yo, Grunt, Bower">

不過由於在做 SPA(Single Page Application) 時會將很多 template view 拆開,所以我將一些其他的 view 分開像是這樣:

├── index.html
└── views
   ├── home.html
   ├── navbar.html
   ├── foo.html
   └── bar.html

由於 這些 view 底下的 html 其實是要給 index.html 來使用的,因此他的路徑其實是從 root 開始算的,在開發階段其實都不會有問題,然而在 build 完之後就會發現圖片會找不到的情況。追了一下 log 才發現 usemin 雖然有 parse 這些 html 但並不會將 image tag 替換掉。其實想想也合理,因為根目錄的路徑不同啊!但這樣一來 template 享受不到 usemin 的好處,相當令人沮喪。

網路上好像找不到太到相關的問題,因此我就試著來解決,以下是我解決的步驟:

1. 在package.json加入 "grunt-contrib-symlink": "~0.2.0"
2. 在Gruntfile.js加入以下的設定:
   symlink: {
      views:{
        expand: true,
        cwd: '<%= yeoman.dist %>/views/',
        src: ['*'],
        dest: '<%= yeoman.dist %>/'
      }
    }
3. 在Gruntfile.js加入以下的 task:
 grunt.registerTask('clean-link', function(target){
    var yeoman = grunt.config.get('yeoman');
    var htmls = grunt.file.expand( {
    }, yeoman.dist+'/*.html').forEach( function(path){
      if( grunt.file.isLink(path) )
        grunt.file.delete(path);
    });
  });
4. 在 build task 裡頭的 'usemin' 改成
'symlink:views', //create symlink
'usemin', 
'clean-link' //clear symlink

解決的方式是將 views/ 底下的 html 都建立 soft link 到根目錄,然後讓 usemin 去 parse,並且寫回 views/*.html ,最後將這些 symlink 清掉。

大概是這樣,grunt 真是很難懂。

射後不理設計模式

誒~其實這是我亂取的名字

AJAX Loading Loading ajax-loader (2).gif

大概有好一段時間,AJAX 的互動方式都會增加一個 Loading 的狀態顯示,在 Response 尚未回傳回來時,提醒使用者資料還在傳輸當中,當 Response 結束之後再顯示結果。這個 Loading 的圖示,大概就被當成網站很潮的 AJAX application 代表,像是 ajaxload.info 還有自動產生 Loading 圖示的網站。優點是讓使用者知道資料正在傳輸。但缺點是要等 server 端回傳回來資料才能修改 UI 的狀態讓使用者知道動作已經完成。但其實在某些情況時,使用者根本不需要知道這些狀態,只要系統能夠確保事情是完成的就好 (而且其實看久覺得蠻煩的,尤其是看到他不斷的在 Loading,或者是看到他閃了一下就不見,都會讓人覺得系統有問題)。

perform actions optimistically

Secrets to Lightning Fast Mobile Design 這篇投影片分享了 Instagram 如何讓 Mobile App 更快速的祕訣,其中有一項我覺得很簡單而且可以大幅改善效能的技巧:「perform actions optimistically」。

舉例來說,當使用者按下「Like」時,就會出現「Liked」的狀態,但其實資料還在傳輸。
Screen Shot 2013-11-11 at 下午4.47.08.png

當使用者留言時,就會先呈現留言完成的狀態,但其實資料還在傳輸。
Screen Shot 2013-11-11 at 下午4.48.29.png

所以這種方式其實是將 UI 的 feedback 直接給使用者,讓使用者「感覺」很快。

再來看一個例子:http://localtodos.com/ 這是一個 todo list 的應用程式,所有的資料都存在 client 端的 localStorage裡頭。由於不需要跟 server 端溝通,因此在做 新增/修改/刪除 時,沒有網路傳輸的延遲,當然就不需要做 loading 狀態的顯示,任何操作都可以直接反應給使用者。

那麼如果資料是存在 server 端,又要達到一樣的效果,該怎麼辦呢?其實只要將儲存的動作,改成「射後不理」就可以了。也就是說,使用者的操作並不理會 server 端的回應,只要保證「 server 端的資料與使用者的動作保持一致」即可。

射後不理(fire and forget)

基本上這種設計模式就是以呈現 UI 為主,系統在做什麼事情使用者不管,反正 UI 呈現怎樣,就應該要幫我處理掉。可以想成 UI 跟 backend 是不同步的,彼此只依靠 UI 來同步所該做的事情。

聽起來有點抽象,舉個例子來說,若今天有個資料可以讓使用者自由排序,當使用者進行拖拉的動作時,要將使用者排序的動作儲存起來,如果每次拖拉完成的動作都需要等待 server 端的回應,那就會造成 UI 上的不同步,進而影響到使用者操作時的困擾。這時就會需要射後不理這樣的設計模式來輔助使用者進行操作。

實作方式

聽起來不難,不過實作過程其實遇到很多很麻煩的事情,因此分享一些實作上的心得,為了簡化問題,我用 Like/Unlike 來舉例。需求很簡單:按下Like 按鈕之後切換 Like/Unlike 的狀態。BUT,如果連按兩次?三次?十次? UI 可以即時顯示,但要如何正確的跟 server 端溝通?如果在資料傳輸的過程中,使用者又改變了狀態?那怎麼辦?

  1. 狀態流程圖

    重點其實是狀態的改變,把狀態列出來畫成「狀態流程圖」就會比較好理解。
    Like-Unlike (1).png

    • unknown: 未知狀態
    • fetch: 從 server 取得 Liked 資料
    • mismatch: 使用者按下「Like/Unlike」而且跟fetch的值不一樣
    • flying: 資料正在傳輸中

所以如果資料傳輸回來時,發現使用者的狀態改變的話,又會再觸發 mismatch 跟 server 端 request。

  1. 發 request 的時機

如果是 like/unlike 時就發送 request ,很快就會遇到一個問題,當使用者連點兩下時,就會同時發出 Like 和 Unlike,若狀態是根據 request callback 回來判斷的話,遇到「後發先至」的情況就會讓 UI 造成問題。因此「射後不理」的設計模式其實是可以避免這種情況發生的。

發 request 的時機其實就是在使用者改變狀態時,且跟 fetch 回來的值不同時,為了避免使用者頻繁更新狀態而跟 server 端重複發 request,可以設一個 timeout 來檢查是否觸發 mismatch。

  1. server 端的回應確認 UI 是否保持一致

    最後,當 request callback 回來時,檢查使用者是否又很善變的改變了狀態,如果改變了,再重新做一次 mismatch,並且發 request。如此就可以循環整個流程。

有限狀態機

由於實作部分很難拆解出來,而且不同的 scenario 都會有不同的流程或解法,就不把程式碼貼出來獻醜了。我想表達的重點是「有限狀態機」,其實將流程圖畫出來之後就會發現很適合用「有限狀態機」來表達流程與狀態,而且也可以避免在流程中任意改變狀態(如果你是自己處理事件的觸發),除了幫助自己理解流程之外,程式碼也會比較好維護。

關於有限狀態機我也還在摸索階段,這是第一次嘗試應用在專案裡頭,有興趣歡迎再討論看看有哪些適合的案例。

Reference:

有限狀態機

javascript 是 Event Based 的語言,因此可以很輕易的實作事件綁定來處理非同步的動作,為了將表現層與資料層分離,就會用「值」的改變來 trigger 事件。例如在 Backbone Model 裡頭可以做 change:value 的事件處理。例如:

book.on("change:title change:author", ...);

但是隨著應用程式越來越複雜,value 的 change 可以拿來當作事件的觸發,但流程並不會因此簡化,在設計程式時如果沒有一個更清晰的流程將會在 Event 裡頭的流程迷失方向,或寫出難以維護的程式碼。

Event pubsup

Event Pubsub,利用事件 publish/subscribe 的方式來做流程處理。但 pubsup只是針對事件的「綁定」與「觸發」,缺少了「狀態的轉換」。

有限狀態機(Finite State Machine)

有限狀態機」是描述一種狀態的轉移,它強調過去狀態與當前狀態所引發的事件,因此可以將流程簡化到事件的處理之中。而且將流程簡化出來也能夠幫助自己在分析時更清楚地掌握流程處理。所以有限狀態機的重點是要將現有的流程簡化成一個可循環的狀態,利用狀態的轉移來觸發事件,相反地也可以觸發事件來轉移狀態。

javascript-state-machine

javascript-state-machine 是我覺得很好用的一套小 library ,程式碼也沒有很長,有興趣可以研究看看。裡頭有一個小 Demo,簡單解釋一下。

see demo: http://codeincomplete.com/posts/2011/8/19/javascript_state_machine_v2/example/

條件如下:
  1. 初始化事件:green
  2. warn: green -> yellow
  3. panic: green/yellow -> red
  4. 當 red 時,calm 事件觸發時要等待三秒才會恢復到 yellow
  5. 當 red 時,clear 事件觸發時要等待三秒才會恢復到 green
  6. 當 yellow 時,clear 事件觸發會恢復到 green

流程圖

看敘述有點難理解,可以直接看流程圖
Screen Shot 2013-11-07 at 下午1.09.47.png

事件:
  • start
  • warn
  • panic
  • calm
  • clear
流程:
  • reen
  • yellow
  • red

如果只看事件的話,只能依靠事件的觸發來控制流程,但是加上「狀態」之後,看起來就好理解多了,而且整個是一個可以循環的流程。把事件用箭頭連起來,就可以知道何時可以觸發哪一個事件。

流程圖中的箭頭代表「事件」轉換,框框代表「狀態」的轉移,所以當「狀態」改變,就會觸發「事件」,反之「事件」被觸發,就會改變「狀態」。例如「Warn事件」發生的時候,狀態就會從 green 轉移至 yellow;而「Panic事件」發生時,狀態就會改變成 red。

而設定的方式也很直覺,在 event 的地方設定好事件觸發時狀態如何改變。而 callbacks 可以在事件被觸發前/後 執行 callback unction。

events: [
      { name: 'start', from: 'none',   to: 'green'  },
      { name: 'warn',  from: 'green',  to: 'yellow' },
      { name: 'panic', from: 'green',  to: 'red'    },
      { name: 'panic', from: 'yellow', to: 'red'    },
      { name: 'calm',  from: 'red',    to: 'yellow' },
      { name: 'clear', from: 'red',    to: 'green'  },
      { name: 'clear', from: 'yellow', to: 'green'  },
    ],

    callbacks: {
      onbeforestart: function(event, from, to) { log("STARTING UP"); },
      onstart:       function(event, from, to) { log("READY");       },

      onbeforewarn:  function(event, from, to) { log("START   EVENT: warn!",  true);  },
      onbeforepanic: function(event, from, to) { log("START   EVENT: panic!", true);  },
      onbeforecalm:  function(event, from, to) { log("START   EVENT: calm!",  true);  },
      onbeforeclear: function(event, from, to) { log("START   EVENT: clear!", true);  },

      onwarn:        function(event, from, to) { log("FINISH  EVENT: warn!");         },
      onpanic:       function(event, from, to) { log("FINISH  EVENT: panic!");        },
      oncalm:        function(event, from, to) { log("FINISH  EVENT: calm!");         },
      onclear:       function(event, from, to) { log("FINISH  EVENT: clear!");        },

      onleavegreen:  function(event, from, to) { log("LEAVE   STATE: green");  },
      onleaveyellow: function(event, from, to) { log("LEAVE   STATE: yellow"); },
      onleavered:    function(event, from, to) { log("LEAVE   STATE: red");    async(to); return false; },

      ongreen:       function(event, from, to) { log("ENTER   STATE: green");  },
      onyellow:      function(event, from, to) { log("ENTER   STATE: yellow"); },
      onred:         function(event, from, to) { log("ENTER   STATE: red");    },

      onchangestate: function(event, from, to) { log("CHANGED STATE: " + from + " to " + to); }
    }

不過實際上,有限狀態機可以做什麼用?待續...

延伸閱讀:

AngularJS 根本就是當年的 Atlas

這篇其實只是我自己的碎念。

Atlas

剛開始看到 AngularJS 的時候,突然有種被雷霹中的感覺,不亞於當年(2005)我在學 ASP.NET 時遇到 Atlas (ASP.NET AJAX) 被轟頂的感覺。該怎麼說呢....當年 AJAX 竄起時, Atlas的出現仿佛是個救世主一般的降臨,因為它讓當年活在 windows application 端寫程式的開發者可以不用碰到亂無章法的 javascript,只要專注在原本的 application 觀念就可以寫出強力的 AJAX 網站,強力到我甚至不太懂背後運作的原理,我就可以寫出別人要寫好多行的功能。在我還在幼稚園階段時,Atalas 也讓我見識到 AJAX 驚人的潛力。

也許當初我還很嫩,雖然可以做出一些制式化的功能,但是要做一些細微的調整就會很麻煩,這也是讓我當初不斷卡關的原因。

檢視原始碼

後來我習得了一個技能叫做「按右鍵檢視原始碼」,才發現生出來的原始碼奇醜無比,不騙你,這比 dreamwaver 產生出來的程式碼還要骯髒,你會看到一堆不知為何的 input hidden 欄位然後有很多很奇怪的值,身為一個開發者,檢視原始碼就像被人家脫掉衣服一樣,裡面裝了什麼奇怪東西完全被看光光,但無知的我,只能選擇關掉,然後說:"alright, It works."

不怕被脫衣服

後來參加一些研討會,很多 bad case 就直接「按右鍵檢視原始碼」,然後「哈哈!....你看看你(指)」,大家就會心有靈犀一點通的會心一笑。

我也很怕被脫衣服,所以我希望「按右鍵檢視原始碼」時,不要被看到骯髒的 code,不要一眼就被看穿是 ASP.NET,PHP,JSP等等,例如一堆 hidden input 就是 ASP.NET ,開頭有 10行以上空白行就是 JSP等等。

跟 AngularJS 有啥關係

講了這麼多廢話,我覺得 AngularJS 之於 web application 就好比 Atlas 之於 AJAX,Atlas 在當時為了 windows application developer 開創出開發 web application 的一片天,在如今 web application 當道的年代,相當期待他將會是耀眼的一顆星。

2013 JSDC Web Worker

JSDC 2013

第一次在這種大型研討會跟大家分享,看到這麼多熱血的前端工程師,真的是很高興,收獲頗多。我這次的主題主要是想分享在使用 Web Worker 時的觀念,雖然很多前輩都有分享過了,不過在實際專案上使用時還是有點綁手綁腳。因此我不從API角度來介紹 Web Worker ,而是從觀念與實際案例討論來理解 Web Worker。有任何問題歡迎提出討論。<!--more-->

WorkerD

在開發的過程之中我遇到了一些問題,在傷心難過之餘就生出了一個 Library 叫做 "WorkerD",主要是解決 console 以及 Dependency 上的問題。並開放原始碼在 github

WorkerD Sandbox

Trevor Burnham (Async Javascript 的作者) 寫了一個 Web Worker Sandbox,可以在 Sandbox 裡面測試 Worker 的運作。原本想 Fork 來改成 WorkerD 的版本,不過因為它需要用到 nodeJS 來做後端服務,我的需求很簡單,只是要拿來玩和測試而已,因此我也做了一個 WorkerD Sandbox。這個玩具在我實驗 Worker 的資料傳遞實非常實用。

Read on

Web Worker 經驗分享(二)

本文章同步刊載於IT邦幫忙

那些年,我們一起讓瀏覽器當掉的日子

昨天提到了 Worker 就像是主從式架構一樣,有要求才有產出,而前後端的任務切割,就是在將商業邏輯與頁面呈現區隔開來。但隨著專案越來越複雜,前端越來越包山包海,我們好像一直不斷的在挑戰瀏覽器一樣,在前端做更多更複雜的事情。

chrome ooops

有了 Worker 之後,或許我們會開始回憶起「那些年,我們一起讓瀏覽器當掉的日子」。

Read on