[react] selection auto-complete component

最近在找一個 selection 的 react component,但希望可以支援 auto-complete,找了好幾個不是整合有問題,就是彈性不夠,試了幾個都不太好整合。後來找到這個,http://jedwatson.github.io/react-select/,蠻不錯的 UI,想要的功能也都支援。特此記錄一下。

react-select

demo

螢幕快照 2015-04-15 下午2.45.56.png
蠻喜歡這種 UI 的,兼具 input 和 select 的控制項。

以下是其他類似的,但用過都還是有缺點

  1. https://github.com/asbjornenge/react-datalist 利用 html5 datalist,用 react 的特性其實很好操作,缺點是 支援瀏覽器有限(http://caniuse.com/#search=datalist),polyfill 太醜然後某些 action 操作不太一樣。
  2. https://github.com/fmoo/react-typeahead 看到 typeahead 我以為是 twitter 的 typeahead 的 react 版本,後來看了 sourcecode 才發現作者是重頭實做了一次 typeahead ,但是很多部分跟原本的 typeahead 不太一樣。操作起來綁手綁腳的。
  3. https://github.com/eliseumds/react-autocomplete
    這個 repository 取名取得很好不過跟 npm 的 react-autocomplete 又不一樣,然後 npm 的 react-autocomplete 的命名其實是 combobox ,而且沒在維護,連 source code 也不在 github 上了。

  4. https://github.com/prometheusresearch/react-selectbox
    因為星星數不夠多,所以根本沒用過。

以上。雖然網路資源很多,但是找起來好像在查 paper 的感覺,希望有幫助到有相關需求的人。

Secrets in npm package.json

講個秘訣

在用 npm 來管理 node package 時,愚昧如我只會用 npm install 來 install package,在歷經一些痛苦之後決定好好來看一下 package.json 的秘密。

dependencies V.S. devDependencies

區分專案的開發時的模組相依性。一般來說開發時都會用 npm i $PACKAGE_NAME --save-dev 這樣就會設定成該模組的 devDependency。但如果參數是給 --save 就會是 dependency。這是用來區別 production mode 時不需要下載 devDependency 的 package。

需要注意的是,npm install 預設會載入 devDependency 的 package,若是在 production mode 時,要做
npm i --production 指定只載入 dependency 的 package,如此就不會多載入一些開發才需要用到的 package。

npm scripts

不知道有沒有人跟我一樣,看到 Caesar 大大這個討論串,多想三秒,其實可以不用 grunt & gulp.,才發現原來 npm 可以自訂 scripts,然後傻傻的打 npm watch,結果不能執行,後來查了一下才知道 npm 有預設的指令,如果要執行自訂的指令要用 npm run xxx來執行。所以可以搭配一些 shell 指令來簡單的做你想做的事情。

pre/post scripts

然後在看 npm scripts 時看到一個蠻有趣的用法。

Pre and post commands with matching names will be run for those as well (e.g. premyscript, myscript, postmyscript).

簡單來說可以分開執行 script 的順序,在做複雜的指令還蠻好用的。 例如你可以自訂:

{
  "scripts": {
    "bingo": "echo Bingo"
  }
}

所以當執行 npm bingo 就會 echo Bingo,接著加上 pre/post 的話,例如:

{
  "scripts": {
    "bingo": "echo Bingo",
    "prebingo": "echo Hello",
    "postbingo": "echo World"
  }
}

這樣執行 npm bingo 就會 echo

Hello
Bingo
World

舉例來說,我會搭配 bower 來管理前端需要用的 package,我就可以加上 postinstall 的指令來做 bower install。

{
  "scripts": {
    "postinstall": "bower install"
  }
}

當執行 npm install 之後就會接著做 bower install

所以 ReadME 就不用再寫 Step1, Step2, ...。只要 npm install 就可以搞定啦,而且 jenkins 的 job 設定也可以變的比較一致性了,專案自己該做的事情就寫在這些 scripts 即可。回過頭來說,「多想三秒,你真的可以不用 grunt & gulp.」

package version control

不知道有沒有人注意到,在執行 npm install $PACKAGE_NAME 時,他會指定成 "^1.2.3",這是 follow https://github.com/npm/node-semver 的版本號規則。

需要注意的是預設的 "^1.2.3",意思是 [ '>=1.2.3-0', '<2.0.0-0' ],所以若 package 版本有更新,會自己升上去。最近幾次有遇到 jenkins 自己莫名其妙 build fail 就是因為版本不同而造成的問題。建議可以改成 "~1.2.3" [ '>=1.2.3', '<1.3.0' ] 或是寫死版本 "=1.2.3",再視專案而定更新就好。團隊開發時版本的控制很重要,沒有指定好大家拉不同的 code 常常會造成浪費時間的 debug。其他更詳細的用法就在參考 https://github.com/npm/node-semver 囉。

npm outdated

有空可以執行 npm outdated 來檢查 package 的更新,視情況而決定是否要更新 package。建議是開 feature branch 來做 upgrade package,沒問題再 merge 回去。

以上,這些大概是開發常會遇到的一些心得與秘訣,如果想看更詳細的解釋就請直接看 doc 啦。

References:

Wedding Party Hackday

16.jpg
最近完成了終身大事,也終於有點時間記錄一下了,身為一個宅宅工程師,我堅持自己的婚禮也要充斥一點宅味,當然也要感謝我的老婆容忍我的任性(疑~)。

其實很久以前就想過婚禮時可以做哪些系統了,有鑑於大家都說到時候不會有時間做這些事情,於是我提早開始準備我要做的系統。

互動式婚紗照片

我一直覺得婚紗本又重又難收藏,所以我很早就想把婚紗照直接用螢幕輸出,但只做播放太無聊了,所以我想要加入一些互動的元素,很直覺就想到用「motion detect」來做翻頁。

gest.js

gest.js這套 Library 可以用 camera 來辨識揮手的動作,辨識上下左右。試了一下辨識度還不錯。

jssor slider

要找適合的 slider library 反而找比較久,因為我希望可以支援上下左右的滑動效果,自動播放時也有一些客製化的特效,但大部分的 slide show 都只有左右滑動,最後找到這個客製化程度蠻高的 library jssor slider ,實際使用時其實他也沒辦法直接用上下滑動,所以我有修改了他的原始碼,讓他可以支援動態改變左右滑動與上下滑動。

最後就生出來這個版本了:DEMO,有興趣玩的人只要把 /image/photos/ 底下的照片換掉即可。有空我再寫 README。

10850072_10152841915926539_3126894732238031620_n.jpg

現場是用 iMac 顯示,然後在桌上弄點布置這樣,真是頗具巧思(自己說)。不過事實證明,沒有人介紹的話其實鮮少有人會發現可以用手控制,然後應該是小孩比較有興趣而已XD。

婚禮留言板

這個是我一直想做的東西,每次參加婚禮在開場前賓客都會不知道要幹嘛,早到的朋友會有點無聊,為了增加一點互動性,應該安排一些小活動給大家玩,類似的東西就是實體的留言箱,大家發一張小卡,然後上面些一些祝福的話,最後新人抽獎出來之類,不過因為沒有辦法看到大家的留言,少了一點即時的參與感。於是我又打算 web 化(沒辦法,我只會這個XD),讓賓客用手機留言,然後即時顯示在投影布幕上。

桌卡

桌卡的範本來自於 mybigday (個人覺得不好用),不過我用了他的桌卡範本改成我要的格式,放了 QR code 和連結上去。
螢幕快照 2014-12-25 13.43.32.png

手機留言

c21c5f114d9bbcba443f0d334b36f5ed (1).jpg

留言板

螢幕快照 2014-12-25 13.20.21.png

抽獎活動

留言當然要用點誘因來讓大家留言囉,於是我做了一個透過手機抽獎,可以讓主持人邀請重要嘉賓抽獎的部分。
c21c5f114d9bbcba443f0d334b36f5ed.jpg

最後的重頭戲就是抽獎了,由於前陣子很愛玩 ranger ,而且他的抽獎動畫實在是太可愛了,所以我就把裡面的動畫擷取出來做抽獎,效果不錯(自己說)。
螢幕快照 2014-12-25 13.19.11.png

小插曲

千古不變的定律, live demo 一定會出狀況,當天果然也是,原因是我以為 facebookid 都會拿到一串數字,結果我自己白痴塞了一些預設的 string id 進去,本來是拿數字 id 來取 mod 所以就出狀況了。好在我有堅強的 HTC FE 團隊協助我當場的 debug(hug)。
1619141_10201994217896298_4293646646054564328_n.jpg

10806202_10201994262017401_6262181042209528548_n.jpg

總結

  1. QR code 很不普及。
  2. 桌卡做的太假掰,有人以為是餐廳的根本不想理。
  3. 非常非常需要 promote,下次有機會(疑?)應該要請工作人員一桌一桌介紹或施加壓力(?)留言,要讓大家更有參與感一點,最後的抽獎才會 high。
  4. 其實只是搞死自己而已,其實我程式早在兩個月前就準備好了,不過婚禮前幾個禮拜測試發現 firebase 的 auth 部分改版了,結果為了要測 auth 的部分又花了一些時間修改程式。中間一度想要放棄這個活動。下次有機會(疑?)就乖乖的走完流成就好了。
  5. 我老婆好票釀。
  6. 謝謝大家的參與,大家下次見(疑?)
  7. 我愛我老婆。

62.jpg

最後順便打個廣告,如果有人對這個留言系統和抽獎程式有興趣的話請來信聯絡,保證以友情價情義相挺。

[ReactJS] tips of Writting JSX with CoffeeScript

由於一開始我沒有認真看 JSX (而且也很懶得看)就開始使用 coffeescript 來寫 React 了,所以一直沒有去搞懂 JSX 到底要怎麼寫,直到最近才開始認真使用 JSX 來開發,詳看上集。關於 jsx 的 tip 其實官方有介紹一些技巧與注意事項:可以參考 React Tips Introduction

因為自己習慣用 coffee 的關係,所以會遇到一些小問題,這篇文章主要紀錄一下用 coffee 寫 react 需要注意的地方與心得,本篇文章的程式碼也都是 coffeescript。

browserify

強烈建議一定要使用 browserify ,官方寫的 todomvc-flux就是用 browserify 來打包程式的,用了 browserify 之後就可以用 coffeeifyreactify 來 compile 程式了。

關於 browserify 的設定可以參考我的專案設定 https://github.com/blackbing/htccs-webapp/blob/master/gulp/tasks/browserify.coffee

jsx header

若程式之中有需要做 reactify 的程式要在第一行加上這行,讓 reactify 認得要 parse jsx 語法。

`/** @jsx React.DOM */`

jsx component

jsx 特有的語法就是用 element tag name 來當語法,例如:

var HelloMessage = React.createClass({
  render: function() {
    return <div>Hello {this.props.name}</div>;

  }
});

用 coffee 來撰寫時就會用到``來轉成 javascript 再交給 reactify 來處理。

HelloMessage = React.createClass(
  render: ()->
    return `<div>Hello {this.props.name}</div>`
  }

this and @

coffee 用 @ 代表 this,但小心不要在 jsx 語法裡頭用 @,因為被 ``包住的部分會被當成 javascript 來處理,coffeeify不會去處理這部分。舉例來說。

HelloMessage = React.createClass(
  onClick: ->
    alert 'onClick'
  render: ()->
    `<div onClick={@onClick}>Hello {@props.name}</div>`
  }

如此@onClick 就會變成 undefined。應該要改成 this.onClick 來處理。像這樣:

HelloMessage = React.createClass(
  onClick: ->
    alert 'onClick'
  render: ()->
    `<div onClick={this.onClick}>Hello {this.props.name}</div>`
  }

every component need a root parent

每一個 component 都需要一個 parent 來包住,例如你不能這樣寫 jsx:

HelloMessage = React.createClass(
  render: ()->
    `<div>Hello foo</div>
    <div>Hello bar</div>
    <div>Hello foobar</div>`
  }

而是要用一個 element 包住:

HelloMessage = React.createClass(
  render: ()->
    `<div>
      <div>Hello foo</div>
      <div>Hello bar</div>
      <div>Hello foobar</div>
    </div>`
  }

由於這可能會影響 HTML 的結構,需要跟 Designer 確定一些實做細節。

Empty text node will wrap by <span>

例如

`<th>type{foo}</th>`

會變成這樣

這可能會跟原本預期的不太一樣,為了避免樣式問題,可能要跟 Designer 確認避免產生空的文字節點。

Sperate component if there's any logic control

用了 jsx 之後會發現遇到邏輯部分的程式會不太好寫。例如用 js/coffee 來寫的話會像是這樣。

HelloMessage = React.createClass(
  render: ()->
    R.div className="form-component"
     if type is 'image'
       R.img src:data.src
     else if type is 'input'
       if inputType is 'text'
         R.input type:"text"
       else if inputType is 'radio'
         R.input type:"radio"
    #...(skip)

而用 jsx 要在一個 render function 做這些判斷會看起來很可怕。建議盡量拆 component,拆 component 是不用錢的。由於 component 都是單一物件的設計,所以盡可能的將一個 component 的邏輯簡化。如上例,可以將裡頭的 if-else 判斷拆出去 FormItem,像這樣:

FormItem = React.createClass
  render: ->
    data = @props.data
    if data.type is 'image'
       `<img src={data.src} />`
    else if data.type is 'input'
      if data.inputType is 'text'
        `<input type="text" value={data.value} />`
      else if data.inputType is 'radio'
        `<input type="radio" value={data.value} />`
        
HelloMessage = React.createClass(
  render: ()->
    `<div className="form-component"
      <FormItem data={data} />
     </div>`
    #...(skip)

這是一個很簡單的技巧也可以將 component 快速的拆開,不要嘗試在一個 component 裡頭包入太複雜的邏輯判斷。另一個好處是可以針對單一個 component 來做 unit-test。

false in jsx

false 在 jsx 裡頭有些不同的意義,在 http://facebook.github.io/react/tips/false-in-jsx.html 有說明,不過因為很重要所以要講三次

其中最重要的是 false element

`<div>{false}</div>`

這樣會變成 <div></div>,成為一個空的節點。在做一些邏輯判斷時還蠻好用的。

Never modify DOM by yourself

如果你開始使用 React 了,請戒掉用 jquery 操作 DOM 的「壞習慣」。真的,一開始會很不習慣,例如addClass('loading')太方便了,但是摻雜使用的後果就是很難 debug。建議要做任何 DOM 的操作都交給 state/props 來決定。

如果有其他使用 React 的小技巧也歡迎分享喔~

[ReactJS] jsx or coffee style

JSX

在寫 ReactJS 時,官方建議使用 jsx 這樣的語法來處理 html,這有點像是 template engine,不知道什麼是 jsx 的同學可以在 jsx compiler 玩玩看。jsx 其實不是必要的東西,如果你開心的話也可以直接用 React.DOM.div來撰寫,實際在使用時,對 jsx 充滿了疑惑,要做一些邏輯性的操作(例如 if-else, foo-loop)等等,相對的有點麻煩。

後來看到了一篇文章,推薦用 coffeescript 的特性來直接撰寫 React component:React, JSX, and CoffeeScript。文章提到用 coffeescript 的特性來操作 component 會更加的得心應手,於是我也動手把 component 直接改成 coffee style。寫出來的程式大概長成這樣:

  R.div id: 'list-view', className: 'view tab-pane fade in active',
    if listType is 'foo'
        R.div className: 'back', '←'
            R.p className: 'alert alert-warning',
            R.i className: 'fa fa-exclamation-circle'
    if listType is 'bar'
        R.span null, 'Hello world'
    else
        R.span null, 'Magic!'

優點

利用縮排來 beautify 程式
R = React.DOM
R.p null,
    R.a href:'foo', 'bar'  # note omitted comma here
    R.a href:'foo2', rel:'nofollow', 'second link'

利用 foo-loop 來產生 template

R.ol null,
  for result in @results
    R.li key:result.id, result.text

相比之下用 jsx 就囉唆許多:http://facebook.github.io/react/docs/multiple-components.html

寫 if-else 像是在喝水的簡單

  if not @state.editing
    R.div null,
      @props.text
      ' ' 
      R.span className:'link-button mini-button', onClick:@edit, 'change'
  else
    R.div style:{position:'relative'},
      R.input
        style:{position:'absolute', top:-16, left:-7}
        type:'text', ref:'text', defaultValue:@props.text
        onKeyUp:@onKey, onBlur:@finishEdit

相比之下 jsx 的 if-else 限制很多:http://facebook.github.io/react/tips/if-else-in-JSX.html

缺點

但是一直到最近我放棄了,所以我來說說 coffee style 的缺點。

無法使用在太複雜的 DOM 結構

其實可想而知,若DOM結構複雜的話,這樣做會搞死自己,雖然我們自以為對 javascript/coffee 非常熟稔,但人眼終究敵不過 template 的複雜度。這是一段我曾經寫的 component 的程式,坦白說這個結構沒有很複雜,但我看第二次的時候我自己都想殺了我自己,大家可以笑笑:

R.fieldset(className:"form-group #{ex_className}",
  R.legend( {className:"scheduler-border"}, item.name),
  R.div( {className:"table-responsive"},
    R.table( {className:"table table-hover"},
      R.tbody(null,
        for d_items, key in  item.dynamic_item
          console.log d_items
          R.tr(null,
            for i of  d_items
              d_item = d_items[i]
              if d_item.type is 'dropdown'
                R.td(null,  DropdownItem(item:d_item))
              else
                R.td(null,  TextItem( item: d_item))
            R.td(null,
              R.button( {type:"button",className:"btn btn-default btn-sm",
              onClick: do(key)=>
                => @removeItem(key)
              },
                R.i( {className:"glyphicon glyphicon-remove"})
              )
            )
          )
      )
    ),
    R.div(null,
      R.button( {
        type:"button"
        className:"btn btn-info btn-xs extend form-add"
        onClick: @addItem
      },
      R.i( {className:"glyphicon glyphicon-plus"})
      )
    )
  )
)
難以維護

由於 coffee 終究是 javascript,用 coffee 來寫基本上就只是在寫程式,一開始我對於可以使用 underscore 之類的 library 來寫 react component 感到很興奮,最後發現這根本沒辦法維護。比方說,我們可以這樣用

React.DOM.div null, foo.bar.map( (r)-> fruit[r]).join(', ')

看起來很簡單,而且寫起來很乾淨,但是當 template 摻雜著太多的邏輯,可就不好玩了,因為你很難控制團隊中大家會怎麼去使用這些東西,其實 template 應該還是回歸單純,只能接收 data,顯示 data。邏輯等等的東西應該都不要跟 template 有太多瓜葛。

結論

在經過一段時間的實做之後,我認為這些缺點非常致命,因此我不建議使用 coffee 來寫 react component,雖然說官方也提到 jsx 不是必要,但若是團隊合作的專案之中,建議還是乖乖的使用 jsx 來處理 template,盡量將邏輯與 template 分開。

下次會分享一些使用 jsx 的小技巧。

[expressjs] request proxy

api proxy

最近使用 expressjs 要將 api proxy 到另外一個 domain 的 api server 上。查了一下最簡單的做法是用 request 來處理。

var API_HOST = 'xxx.com';
app.use('/proxy', function(req, res) {
  var request_params, url;
  url = 'http://' + API_HOST + req.url;
  request_params = {
    followAllRedirects: true,  //redirect if 301~400
    body: JSON.stringify(req.body) //POST/PUT/DELETE data 
  };
  return req.pipe(request(url, request_params)).pipe(res);
});

看起來很簡單不過為了要解決 api server 會做 301/302 redirect 的動作,追了sourcecode才知道有這個 followAllRedirects 的設定,特此記錄一下。

reference:

[PhantomJS] Function.prototype.bind

PhantomJS 1.9.7 (Mac OS X) ERROR

使用 reactjs 來寫 test case 時,用 PhantomJS 執行時就遇到這個 error,奇怪的是用其他的 browser 來執行就沒有問題。

TypeError: 'undefined' is not a function (evaluating 'RegExp.prototype.test.bind(
      /^(data|aria)-[a-z_][a-z\d_.\-]*$/
    )')

查了一下追到這個 issue Function.prototype.bind is undefined,看起來是 PhantomJS 的內核沒有支援 Function.prototype.bind。於是再找到 MDN 的 Function.prototype.bind,在載入 reactJS 之前先將這個 polyfill 加進去就可以解決了。

karma.conf.js
files: [
    'test/polyfill/*.js',
    'app/bower_components/react/react-with-addons.js',
    '.tmp/test/spec/*.js'
]
phantom_bind_polyfill.js(2014/8/3 updated)

use react official polyfill

(function() {

var Ap = Array.prototype;
var slice = Ap.slice;
var Fp = Function.prototype;

if (!Fp.bind) {
  // PhantomJS doesn't support Function.prototype.bind natively, so
  // polyfill it whenever this module is required.
  Fp.bind = function(context) {
    var func = this;
    var args = slice.call(arguments, 1);

    function bound() {
      var invokedAsConstructor = func.prototype && (this instanceof func);
      return func.apply(
        // Ignore the context parameter when invoking the bound function
        // as a constructor. Note that this includes not only constructor
        // invocations using the new keyword but also calls to base class
        // constructors such as BaseClass.call(this, ...) or super(...).
        !invokedAsConstructor && context || this,
        args.concat(slice.call(arguments))
      );
    }

    // The bound function must share the .prototype of the unbound
    // function so that any object created by one constructor will count
    // as an instance of both constructors.
    bound.prototype = func.prototype;

    return bound;
  };
}

})();

[gulp] No need to use karma-browserify

天大的 bug

前幾天才寫了一篇 browserify and karma test 的環境設定,結果今天就踩到天大的 bug:Requiring files that require other files will cause failure.,問題是發生在你的 require dependency 太深的話就會發生 error。我自己遇到是在四層以上就會遇到一模一樣的 error message。

底下有人(gah-boh)留言:"I ended up not using karma-browserify. I tried karma-browserifast and ran into another issue with it. ",然後也提供了他的解法。

var gulp = require('gulp');
var source = require('vinyl-source-stream');
var browserify = require('browserify');
var karma = require('gulp-karma');
var reactify = require('reactify');
var glob = require('glob');

gulp.task('browserify-test', function() {
    var testFiles = glob.sync('./assets/tests/**/*.js');
    var bundleStream = browserify(testFiles).transform(reactify).bundle({debug: true});
    return bundleStream
        .pipe(source('bundle-tests.js'))
        .pipe(gulp.dest('assets'));
});

browserify your test case

我才發現根本就不需要用到 karma-browserify 來處理 (而且也不需要 gulp-karma(https://github.com/karma-runner/gulp-karma)),直接用 browserify 把所有的 test script 產生出來就好了。

以下是我的 task 設定

gulp.task "browserify:test", (callback)->
  bundleApp = map( (contents, filename)->
    fname = get_name(filename)
    browserify(
      entries: [filename]
      extensions: ['.coffee']
    )
    .bundle({debug: true})
    .on('error', $.util.log )
    .pipe(source("#{fname}.js"))
    .pipe gulp.dest(compiledPath + "/test/spec")
  )

  gulp.src('test/spec/**/*.coffee')
  .pipe(bundleApp)

然後 test task 照舊但是要在 browserify:test 做完之後在執行即可

gulp.task "test", ['browserify:test'], (callback)->
  appRoot = "#{__dirname}/../../"
  karmaCommand = appRoot + './node_modules/karma/bin/karma'
  karmaConfig = appRoot + 'test/karma.conf.js'
  exec("#{karmaCommand} start #{karmaConfig}", (err, stdout, stderr)->
    $.util.log stdout
    $.util.log stderr
    callback(err)
  )

結論

我一開始把 browerify 和 karma 的整合想得太複雜了,其實只要把 test case 都經過 browserify compile 之後就可以 run karma 了。我又繞了一大圈,真想殺了我自己。

browserify and karma test

[2014/07/10 update] No need to use karma-browserify

browserify

browserify 真的是個很棒的東西,可以用 require 輕易的把程式模組化,由於可以很輕易的拆成模組,就可以針對模組來寫 test case。之前在玩 AngularJS 時注意到 karma,可以真的run browser engine 來跑 unit test 甚至是 end to end test。原本我以為很簡單可以將 karma test 整進專案,但因為 project 用到 coffee,網路上找到的方式找不太到目前用到的需求(gulp + browserify + coffee + karma test)。所以研究了一下,給有需要的人做參考。

package.json

會用到的 library,比較特別的是 karma-browserify,這個是 karma 的 plugin,用在 karma 的 preprocessors,將 test 的程式做 browserify。

+    "karma": "^0.12.16",
+    "karma-browserify": "^0.2.1",
+    "karma-jasmine": "^0.1.5",
+    "karma-phantomjs-launcher": "^0.1.4",

gulp test task

task 沒什麼特別的,其實只是執行 karma start karma.conf.js,只是把執行的 script 用 gulp task 包起來而已。

gulp = require("gulp")
exec = exec = require('child_process').exec

$ = require("gulp-load-plugins")()

gulp.task "test", (callback)->
  karmaCommand = './node_modules/karma/bin/karma'
  karmaConfig = 'karma.conf.js'
  exec("#{karmaCommand} start #{karmaConfig}", (err, stdout, stderr)->
    $.util.log stdout
    $.util.log stderr
    callback(err)
  )

karma.conf

karma 的設定主要是參考 karma-browserify,也可以直接參考我的設定 karma.conf.js

unit-testing

都設定好之後就可以開始寫測試了,最棒的部分就是可以單獨測試某個 module。例如:

auth = require("../app/scripts/service/auth")
describe "service/auth", ->
  it "should be login", ->
    expect(auth.login()).toEqual('login')
  it "should be logout", ->
    expect(auth.logout()).toEqual('logout')

如此就可以把跟 UI 沒關係的 module 抓出來單獨做 unit-testing,而環境設定好之後我想 end to end testing 應該對 karma 來說都是可以相對簡單的事情了,然後要跑什麼 test report 應該就都可以了,喔耶!

也可以直接參考我的 repository:
https://github.com/blackbing/htccs-webapp/tree/develop

[node-webkit] video mp4 format support

node-webkit 不支援 mp4/mp3 format

最近在玩 node-webkit 遇到一個問題,就是 video 不支援 mp4,原本在瀏覽器開發的好好的,丟到 node-webkit 執行就會死掉。追了一下原來有這個 issue,而且還寫了 wiki Support mp3 and h264 in video and audio tag。大意是說因為 License 的關係,node-webkit 並不會把這些有專利的影音格式支援 build 進去,若您的應用程式需要支援這些有專利的影音格式,可以用以下的解決方式,但風險自負。

詳細看了一下 wiki,最簡單的方式是將 libffmpegsumo.so 取代至 build 出來的專案中。但一定要跟 node-webkit 的 chromium 版本相同,若你自己的 chrome 是最新版的並不保證支援。

於是我在 build 完再把 libffmpegsumo 複製過去取代,重新開啟之後發現影片出現了,但是沒有聲音,WTF。
而在 windows 上的症頭是程式無回應。WTF。
不過這也是 wiki 上寫的並不保證支援最新版本的 chrome。

WebM format

突然想到,這麼麻煩解 mp4 的問題幹嘛,就轉成支援的格式就好了,於是我突然想到可以用 WebM的格式,而且大部分瀏覽器都已經 support 了。所以我就先把 mp4 轉成 webm,沒想到最大的 benefit 就是檔案大小,從原本的 13M 降低成 2.8M,真是嚇死我了,因為當初想要用 node-webkit 包起來有個主要原因就是影片的檔案大小問題,想說可以跑在本機執行的話會比較順暢。不過 anyway,加上一個 source 之後就可以在 node-webkit 上順利執行了,也不用在 build process 中加入奇怪的取代 lib 的動作,非常的好。

<video>
    <source src="video.webm" type="video/webm">
    <source src="video.mp4" type="video/mp4">
</video>

小結

在想解決問題的方法時,不一定要執著在問題的本身,有時候回頭去思考問題或者轉個方向,搞不好就很快的迎刃而解了。想當初我為了要解決播放 mp4 的問題,花了很多時間搜尋 node-webkit mp4 的問題(目光含淚),但其實以我的需求根本不需要直接去解決這個問題。以上。