T3: 構建大型網站的 JavaScript 框架

T3.js

T3 是一個 JavaScript UI Framework,主要的功能是讓程式碼更結構化。如果網站內的程式碼很雜亂的話,很適合用來整理散落在各處的程式碼(尤其是針對年紀大需要翻新的大型網站)。而整理方式就是將程式碼分成幾個部份:Application、Module、Serveice、Behavior 等來處理。頁面 UI 整理成 Module,Module 彼此共用的方法或事件整理成 Behavior,而非 UI 邏輯且多個 Module 會共用的部份(例如:與 Server 端溝通取資料)則抽出來成為 Service。

Module

將頁面功能切成模組(Module)來運作,如果頁面無法切成多個模組就將整個頁面視為一個模組。模組的事件有以下幾種:Message Handling、Behavior、Event Handlers(例如:onclick)。如下:頁面上的模組「test-module」,可能需要針對其他模組的反應來做出回應,因此有 Message Handling;不同模組但有重覆的事情要做,則可用 Behavior;模組內的事件處理可使用 onclick 等;非 UI 邏輯的部份丟給 Service 即可。

HTML

<button data-module="test-module">Test</button>

JS

Box.Application.addModule('test-module', function(context) {
  return {
    messages: ['statechanged', 'statecompleted'],
    onmessage: function(name, data) {
      switch(name) {
        case 'statechanged':
          console.log('statechanged');
          break;
        case 'searchcomplete':
          console.log('statecompleted: ' + data.numResults + ' tasks completed.');
          break;
      }
    },
    behaviors: ['getInfo'],
    init: function(){
      console.log('module init');
    },
    onclick: function(event, element, elementType){
      console.log('click');
      context.getService('connectSomething').connect();
    }
  }
});

Service

Module 與 Behavior 負責處理 UI,而非 UI 的部份則交給 Service。Service 可當成一個共用的介面,讓不同的 Module 和 Behavior 來共同呼叫。例如:使用 ajax 取資料。如下範例,「getInfo」這個 Service 負責與 Server 端溝通取資料,我們呼叫這個 Service 的 method「getSearchResultByKeyword」,並將結果回傳給 Behavior「getSearchResult」。

Box.Application.addService('getInfo', function(application) {
  return {
    getSearchResultByKeyword: function(query) {
      $.ajax({
        url: '/getSearchResult',
        type: 'post',
        data: {
          query: query
        },
        dataType: 'json',
        success: function (response) {
          return response.data;
        },
        error: function (xhr) {
          alert('噢噢!發生錯誤了!請重新再試一次~');
        }
      });
    }
  };
});

Box.Application.addBehavior('getSearchResult', function(context) {
  var query = "日式料理";
  return {
    init: function() {
      var data = context.getService('getInfo').getSearchResultByKeyword(query);
      console.log(data);
    }
  };
});

Box.Application.init();

Behavior

不同模組但有重覆的事情要做,則可在模組中使用 Behavior。

Box.Application.addModule('module-test-1', function(context) {
  return {
    behaviors: ['element-button'],
    init: function(){
      console.log('init');
    }
  }
});

Box.Application.addModule('module-test-2', function(context) {
  return {
    behaviors: ['element-button'],
    init: function(){
      console.log('init');
    }
  }
});

Box.Application.addBehavior('element-button', function(context) {
  return {
    init: function() {
      console.log('add behavior');
      context.getService('connectSomething').connect();
    }
  };
});

Box.Application.addService('connectSomething', function(application) {
  return {
    connect: function() {
      console.log('connect something...');
    }
  };
});

Box.Application.init();

DOMEventDelegate

委派,範例如下。

HTML

<button data-module="module-domeventdelegate">Test DOMEventDelegate</button>

JS

Box.Application.addModule('module-domeventdelegate', function(context) {
  var element = context.getElement();
  var delegate = new Box.DOMEventDelegate(element, {
    onclick: function(event) {
      console.log('DOMEventDelegate: ' + event.type);
    }
  });

  return {
    init: function(){
      var element = context.getElement();
      delegate.attachEvents(); //DOMEventDelegate
    },
    onclick: function(event, element, elementType){
      console.log('click!');
    }
  }
});

Box.Application.init();

Context

broadcast

模組訂閱事件,事件發生時會通知模組。可用在網站的訊息中心等。

HTML

<div data-module="module-broadcast-1">1</div>
<div data-module="module-broadcast-2">2</div>

JS

Box.Application.addModule('module-broadcast-1', function(context) {
  return {
    messages: ['broadcast-a', 'broadcast-b'],
    onmessage: function(name, data) {
      switch(name) {
        case 'broadcast-a':
          console.log('broadcast-a');
          Box.Application.broadcast('broadcast-b');
          break;
        case 'broadcast-b':
          console.log('broadcast-b');
          break;
      }
    }
  }
});

Box.Application.addModule('module-broadcast-2', function(context) {
  return {
    messages: ['broadcast-a', 'broadcast-c'],
    onmessage: function(name, data) {
      switch(name) {
        case 'broadcast-a':
          console.log('broadcast-a');
          break;
        case 'broadcast-c':
          console.log('broadcast-c');
          break;
      }
    },
  }
});

Box.Application.init();
Box.Application.broadcast('broadcast-a');
Box.Application.broadcast('broadcast-c');

getGlobal

取得 window 物件。

HTML

<div data-module="module-test">test</div>

JS

Box.Application.addModule('module-test', function(context) {
  return {
    init: function(){
      var navigator = context.getGlobal('navigator');
      console.log(window.navigator.userAgent === navigator.userAgent);
    }
  }
});

Box.Application.init();

getGlobalConfig

取得 Box.Application.init 中的設定物件。

HTML

<div data-module="module-test">test</div>

JS

Box.Application.addModule('module-test', function(context) {
  return {
    init: function(){
      console.log(context.getGlobalConfig('userName'));
    }
  }
});

Box.Application.init({
    userName: 'Bob'
});

以上只列出目前比較常用的部份,T3 的文件寫得很詳細,有興趣的話可以看一下。我也將之前參加駭客松的作品吃什麼,どっち部份改版使用 T3 實作。以上的程式碼都會放在 Github 上(含簡單的 TodoList),如果有什麼建議或想法都歡迎聊聊。


這篇文章的原始位置在這裡-T3 - 構建大型網站的 JavaScript 框架

由於部落格搬遷至此,因此在這裡放了一份,以便閱讀;部份文章片段也做了些許修改,以期提供更好的內容。
標籤: javascript
comments powered by Disqus