/ Famo.us

[Famo.us] View 核心說明

[UPDATE] Famo.us 官方已捨棄此模組,詳情請看官方 Github

見山不是山,大概就是在說這個 View 模組。


View 核心

核心模組 core/View 是用於提供一個或是多個 RenderNode容器給框架引擎使用,它本身在輸出的時候並不會產生 DOM 實例,所以你在畫面上其實是看不到 View 這個模組的。

先解釋一下 RenderNode 這個東西,

  • 整個框架系統中,只要是產出與畫面有關的模組,都算是一種 RenderNode
  • 核心 core/RenderNode 只包含簡單的方法,一切由其他元件自行擴展
  • 核心 core/View 就是由 RenderNode 擴展而來

View 的擴展包含有,

  1. EventHandler
  2. OptionsManager
  3. RanderNode

那麼,哪一些東西算是 RenderNode 的容器或是會產生 RenderNode 的呢?

以下物件算是 RenderNode 的容器,

  • core/View, core/ViewSequence, views/* 核心與其模組
  • core/Surface, surfaces/* 核心與其模組
  • wigets/* 模組
  • core/RenderNode 自身或是自行延展的模組

以下物件會產生 RenderNode

  • core/Context
  • core/Engine
  • core/RenderNode
  • core/Scene
  • core/View
  • views/Flipper
  • views/GridLayout
  • views/HeaderFooterLayout
  • views/Lightbox
  • views/RenderController

**請注意!**上述物件所帶有的 RenderNode 並不是在相同的物件屬性上,舉例來說,你在 core/View 中能透過 _node 取得 RenderNode 物件(核心原件大部份都使用 _node),但是在 views/Flipper 裡,frontNodebackNode 才是 RenderNode

所以,如果把 RenderNode 放進不是 RenderNode 的容器裡面會怎樣?

報告學長,沒有畫面

View 核心操作解說

先來看個例子,

define(function(require, exports, module) {
  var Engine = require('famous/core/Engine');
  var Surface = require('famous/core/Surface');
  var View = require('famous/core/View');

  var mainContext = Engine.createContext();

  var view = new View();

  var surf = new Surface({
    size: [200, 200],
    content: "test",
    properties: {
      backgroundColor: "red"
    }
  });

  view.add(surf);

  mainContext.add(view);
});

上述這個例子,你有沒有加入 View 輸出的結果都會一樣。原因在於,EnginecreateContext 所提供的 Context 模組,自身就會提供 RenderNode,所以當然有沒有加上 View 差異就不大了。

既然有沒有 View 好像都無差別,那麼使用時機到底在哪裡?

  1. 當模組必須也只接受 RenderNode 或其容器的時候
  2. 使用 views/* 其下所有模組的時候
  3. 轉換、包裝或是擴充模組
  4. 自行開發的模組

上述頭兩項應該沒有異議,第三點提出來說明一下。或許你會問,包裝子元件我不也可以使用 ContainerSurface 嗎?其實可以(因為 ContainerSurface 也是 RenderNode(僅止於擴展方式不同

舉例來說,如果需要一個捲軸物件,

define(function(require, exports, module) {
  var Engine = require('famous/core/Engine');
  var Surface = require('famous/core/Surface');
  var View = require('famous/core/View');

  var ContainerSurface = require('famous/surfaces/ContainerSurface');
  var ScrollView = require('famous/views/ScrollView');

  var mainContext = Engine.createContext();

  var views = [];

  var view = new ScrollView();
  view.sequenceFrom(views);

  for (var i = 0; i < 50; i++) {
    var _container = _createContainer(view, i);

    views.push(_container);
  }

  function _createContainer(view, i) {
    var surface, view = view, i = i;

    surface = new Surface({
      size: [100, 100],
      content: 'box '+ i,
      properties: {
        backgroundColor: "red"
      }
    });

    surface.pipe(view);

    return surface;
  }

  mainContext.add(view);
});

我們就單獨看 _createContainer 這個 function,如果改寫成這樣,

function _createContainer(view, i) {
  var surface, _view, view = view, i = i;

  surface = new Surface({
    size: [100, 100],
    content: 'box '+ i,
    properties: {
      backgroundColor: "red"
    }
  });

  surface.pipe(view);

  _view = new View();

  _view.add(surface);

  return _view;
}

他還是可以運作,而且輸出的 HTML 與上述第一個例子完全一樣。或者,我們換成 ContainerSurface

function _createContainer(view, i) {
  var surface, container, view = view, i = i;

  surface = new Surface({
    size: [100, 100],
    content: 'box '+ i,
    properties: {
      backgroundColor: "red"
    }
  });

  container = new ContainerSurface({
    size: [100, 100]
  });

  container.pipe(view);

  container.add(surface);

  return container;
}

畫面結果會跟上述兩個例子完全一樣,但是 HTML 會有不同(請參考 #1 所述關於 ContainerSurface 的部分

那什麼情況不行,例如,你把 View 扔給 Surface(**注意!**扔給 ContainerSurface 是合法的

  // 這樣做,你的畫面會是空的(而且也不會有錯誤訊息
  var view = new View();
  var surface = new Surface();

  surface.add(view);
  mainContext.add(surface);

擴展應用

由於 View 自身有 EventHandler 模組,所以在自行開發模組的時候,通常可以擴展這個核心來使用。而由於 View 本身與畫面呈現又有高度相關性,所以直接擴展使用其實是很方便的做法。

以下取自我的 500px 範例其中一段,

  // 上略

  function PhotosView() {
    View.apply(this, arguments);

    _createContent.call(this);
    this.createPhotoRowView = createPhotoRowView;
  }

  // 中略
  container.add(this.scrollView);
  this._add(mod).add(container);

  // 下略

自行擴展出一個 PhotosView 模組,自己把 container 加入,最後返回給 Engine 去做最後輸出。View 最大的用意在於,讓開發者自行去擴展所需要的輸出模組,或是做出更複雜(或龐大)的元件控制,最後收斂在 View 這個容器裡面,再輸出給 Engine 處理。