跳到主要內容

ExtJS MVC

ExtJS MVC

Architecture

  • Model is persistent in server side.
  • Store is cache of model thru its proxy.
  • Controller connect View and data in Store.
  • Controller responds users’ operations over View.
  • View presents binded data and input widgets to users.

Application Files

Root Second Third Forth / Fifth Fifth
MyApp /
+ ext-4 /<extjs_code>
+ index.html
+ app.js
+ app /
+ + controller /Main.js
+ + model /Main.js
+ + store /Main.js
+ + view /
+ + + Viewport.js
+ + + main /
+ + + + Act1.js
+ + + + Act2.js
+ data /
+ + main.json
+ + updatemain.json

index.html

<html>
<head>
    <title>Account Manager</title>
    <!-- Load ExtJS style sheet and scripts -->
    <link rel="stylesheet" type="text/css" href="ext-4/resources/css/ext-all.css">

    <script type="text/javascript" src="ext-4/ext-debug.js"></script>

    <script type="text/javascript" src="app.js"></script>
</head>
<body></body>
</html>

/app.js

Ext.application({
  requires: ['Ext.container.Viewport'],
  // The following name is registered to Ext.namespace
  name: 'Hello',
  appFolder: 'app',
  // specify controllers
  controllers: ['Main'],
  launch: function() {
    Ext.create('Ext.container.Viewport', {
      layout: 'fit',
      items: [{
        // Defined in view/main/Act1.js
        xtype: 'mainAct1', 
      }]
    });
    console.log('app is lunched');
  }
});

Controller

/app/controller/Main.js

Ext.define('Hello.controller.Main', {
  extend: 'Ext.app.Controller',
  stores: ['Main'],
  models: ['Main'],
  views: ['main.Act1', 'main.Act2'],
  init: function() {
    // Bind listeners by this.control
    this.control({
      // `select' components from view
      'viewport > panel': {
        // Register a listener to selected components
        render: this.onPanelRendered
      },
      // mainAct1 is defined in view/main/Act1.js
      'mainAct1' : {
        itemdblclick: this.doAct2
      },
      // see view/main/Act2.js
      'mainAct2 button[action=save]': {
        click: this.updateModel
      }
    });
    console.log('controller/Main was initialized');
  },
  onPanelRendered: function() {
    console.log('The panel was rendered');
  },
  // open dialog when item is dbclicked
  doAct2: function(grid, record) {
    // record format is defined in view/main/Act1.js (store config)
    console.log('Double clicked on ' + record.get('name'));
    var view = Ext.widget('mainAct2');
    // `down' is a ComponentQuery method
    view.down('form').loadRecord(record);
  },
  updateModel: function(button) {
    var win = button.up('window'),  // Act2 window
        form = win.down('form'),    // Act2 form
        record = form.getRecord(),  // Ref to record
        values = form.getValues();  // New values
    record.set(values);
    win.close();
    this.getMainStore().sync();
  }
});

Note the execution order:

Log Location
controller/Main was initialized Main.js?_dc=1399966476315:9
The panel was rendered Main.js?_dc=1399966476315:12
app is lunched app.js:15

Views

/app/view/main/Act1.js

Ext.define('Hello.view.main.Act1' ,{
  extend: 'Ext.grid.Panel',
  // Make this view of xtype mainAct1
  alias: 'widget.mainAct1',
  title: 'Act 1',
  // Use StoreMain to replace inline store
  store: 'Main',
  initComponent: function() {
    /* inline store config
    this.store = {
      // following go to /app/model/Main.js
      fields: ['name', 'message'], 
      // following go to /data/main.json
      data  : [
        {name: 'Ed',    message: 'Hello Tommy!'},
        {name: 'Tommy', message: 'Hello Ed!'}
      ]
    };
    */
    // columns rendered by Ext.grid
    this.columns = [
      {header: 'Name',  dataIndex: 'name',  flex: 1},
      {header: 'Message', dataIndex: 'message', flex: 1}
    ];

    this.callParent(arguments);
  }
});

/app/view/main/Act2.js

Ext.define('Hello.view.main.Act2', {
  extend: 'Ext.window.Window',
  alias: 'widget.mainAct2',
  title: 'Act2',
  layout: 'fit',
  autoShow: true,
  initComponent: function() {
    this.items = [{
      xtype: 'form',
      // form fields
      items: [{
        xtype: 'textfield',
        name : 'name',
        fieldLabel: 'Name'
      }, {
        xtype: 'textfield',
        name : 'message',
        fieldLabel: 'Message'
      }]
    }];
    this.buttons = [{
      // will be selected by controller/Main via
      // 'mainAct2 button[action=save]'
      text: 'Save',
      action: 'save'
    }, {
      text: 'Cancel',
      scope: this,
      handler: this.close
    }];
    this.callParent(arguments);
  }
});

Model

/app/model/Main.js

Ext.define('Hello.model.Main', {
  extend: 'Ext.data.Model',
  fields: ['name', 'message']
});

Note
We can add `validations’ to validate values of fields.

Store and Proxy

/app/store/Main.js

Ext.define('Hello.store.Main',{
  extend: 'Ext.data.Store',
  model: 'Hello.model.Main',
  // enforce proxy to refresh data immediately
  autoLoad: true,
  proxy: {
    type: 'ajax',
    // bind api(URI) for read/update actions
    api: {
      read: 'data/main.json',
      update: 'data/updatemain.json'
    },
    // specify response handler
    reader: {
      type: 'json',
      root: 'main',
      successProperty: 'success'
    }
  }
});

Data

/data/main.json

{
  // successProperty defined in store proxy
  "success": true, 
  // data
  "main": [
    {"id": 1, "name": 'Ed',    "message": "Hello Tommy!"},
    {"id": 2, "name": 'Tommy', "message": "Hello Ed!"}
  ]
}

Note
Happened to type “main” as “maint” and ExtJS reported nothing and left blank grid. Should I register a error handler or something to get the error?

/data/updatemain.json

{"success" : true }

Architecture & Flow

Draw flow for editing only (not include cancel, render, etc).

Some Observation

PRO

  • They made GUI programming like answering fill-in-blank question.
  • Rich set of ready to use component (maybe not that apparent in this note).

Confusion

  • Names of constructors are inconsistent. e.g. controller uses init() and view uses initComponent() instead.
  • Inheritance model is intrusive. e.g. explicit invoke this.callParent(arguments);
  • Ambiguous methods.
    • Example1 form.getRecord() for retrieving original data reference, and form.getValues() for getting new (user input) data.
    • Example2 controller.getMainStore().sync() is sync form record to store. It would be celar if they named it commit().
  • Potentially performance problem - MVC are all done in JS.
  • Model only define schema, huge amount of work reside in Store(need more investgation).

Written with StackEdit.

留言

這個網誌中的熱門文章

得利油漆色卡編碼方式

得利油漆色卡編碼方式 類似 Munsell 色彩系統 ,編碼方式為 HUE LRV/CHROMA 例如 10GY 61/449 ( 色卡 ) 編碼數值 描述 10GY hue ,色輪上從 Y(ellow) 到 G(reen) 區分為 0 ~ 99 ,數值越小越靠近 Y,越大越靠近 G 61 LRV (Light Reflectance Value) 塗料反射光源的比率,數值從 0% ~ 100% ,越高越亮,反之越暗,也可理解為明度 449 chroma 可理解為彩度,數值沒有上限,越高顏色純度 (濃度) 越高 取決於測量儀器,對應至 RGB 並不保證視覺感受相同。 參考資料: 色卡對照網站 e-paint.co.uk Written with StackEdit .

UTF8 與 Unicode 的轉換 (C++)

UTF8 與 Unicode 的轉換 (C++) 先釐清一下這兩者的性質 Unicode: 為世界上所有的文字系統制訂的標準,基本上就是給每個字(letter)一個編號 UTF-8: 為 unicode 的編號制定一個數位編碼方法 UTF-8 是一個長度介於 1~6 byte 的編碼,將 unicode 編號 (code point) 分為六個區間如下表 1 Bits First code point Last code point Bytes Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 Byte 6 7 U+0000 U+007F 1 0xxxxxxx 11 U+0080 U+07FF 2 110xxxxx 10xxxxxx 16 U+0800 U+FFFF 3 1110xxxx 10xxxxxx 10xxxxxx 21 U+10000 U+1FFFFF 4 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 26 U+200000 U+3FFFFFF 5 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 31 U+4000000 U+7FFFFFFF 6 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 觀察上面的表應該可以發現 除了 7 bits 的區間外,第一個 byte 開頭連續 1 的個數就是長度,例如 110XXXXX 就是 2 byte 長,而 1110xxxx 就是 3 byte 除了第一個 byte 外,之後的 byte 前兩個 bit 一定是 10 開頭,這樣的好處在於確立了編碼的 self-synchronizeing,意即當編碼為多個 byte 時,任取一個 byte 無法正常解碼。 Note 第一點中的例外 (7 bits) 是為了與 ASCII 的相容性,而第二點會影響到 code point 至 UTF-8 的轉換。 為了與 UTF-16 的相容性,在 R

C++17 新功能 try_emplace

C++17 新功能 try_emplace 回顧 emplace 大家的好朋友 Standard Template Library (STL) 容器提供如 push_back , insert 等介面,讓我們塞東西進去; C++11 之後,新增了 emplace 系列的介面,如 std::vector::emplace_back , std::map::emplace 等,差異在於 emplace 是在容器內 in-place 直接建構新元素,而不像 push_back 在傳遞參數前建構,下面用實例來說明: struct Value { // ctor1 Value ( int size ) : array ( new char [ size ] ) , size ( size ) { printf ( "ctor1: %d\n" , size ) ; } // ctor2 Value ( const Value & v ) : array ( new char [ v . size ] ) , size ( v . size ) { printf ( "ctor2: %d\n" , size ) ; memcpy ( array . get ( ) , v . array . get ( ) , size ) ; } private : std :: unique_ptr < char [ ] > array ; int size = 0 ; } ; struct Value 定義了自訂建構子 (ctor1),以指定大小 size 配置陣列,複製建構子 (ctor2) 則會配置與來源相同大小及內容的陣列,為了方便觀察加了一些 printf 。當我們如下使用 std::vector::push_back 時 std :: vector < Value > v ; v . push_back ( Value ( 2048 ) ) ; 首先 Value 會先呼叫 ctor1,傳給 push_ba