使用Stapes创建一个基于MVC的数据列表页面时,页面的MVC逻辑比较清晰——所有的数据条目是一个 Model,列表的展示是对应的 ViewController 则将视图的事件转化为对数据模型的操作。

对于列表中的每一条数据,由于其是一个独立的数据,因此可以建立一个数据模型用来存储,点击之后弹出模态框,对于这个模态框的设计,如何能保证清晰的逻辑呢?

假设列表的 MVC 实现代码如下:

/* List Model */
var DataModel = Stapes.subclass({
    constructor : function (cfg) {
        // initialize...
    }
});
DataModel.proto({
    updateData : function (params) {
        // ajax request
    }
});

/* List View */
var DataView = Stapes.subclass({
    constructor : function (cfg) {
        this.model = cfg.model || null;
        this.$body = cfg.body || $('body');
        
        this.render();
        this.bindEvents();
    }
});
DataView.proto({
    render : function () {},

    bindEvents : function () {
        this.$body.on('click', 'button', function () {
            var modal = new SomeModal({});
        });
    }
});

/* List Controller */
var DataController = Stapes.subclass({
    constructor : function (cfg) {
        this.model = cfg.model || null;
        this.view = cfg.view || null;
        if (!this.model || !this.view) {
            throw ('DataController init failed!');
        }

        bindEvents();
    }
});
DataController.proto({
    // bind model events
    this.model.on({
        update : function (params) {
            this.model.updateData(params);
        }
    }, this);
    
    // bind view events
    this.view.on({
        update : function (params) {
            this.model.emit('update', params);
        }
    }, this);
});

仅供查看数据的模态框

仅供查看数据的模态框是比较容易处理的,这时模态框就是一个单纯的 View,其绑定的数据模型就是列表中的那一条数据。

var InfoModal = Stapes.subclass({
    constructor : function (cfg) {
        this.model = cfg.model || null;
    }
});

新增或修改数据的模态框

对于新增或者修改的模态框,情况就比较复杂了,因为用户可以在模态框中对用户的数据进行操作。

根据 MVC 的思想,我们要尽量避免在 View 中对数据模型进行直接的操作,View 应该通过发出事件的方式让 Controller 去捕捉并操作 Model,但是用一个完整的 MVC 去实现一个简单的模态框未免太过周折,并且两个 Controller 之间的通讯问题会把逻辑绕得更加复杂。

如何写出一个逻辑清晰的模态框组件,总结了有下面几个思路:

  • 直接操作数据模型
  • 提供回调
  • 绑定父视图

直接操作数据模型

说是直接操作 Model,但也是通过事件机制与 Model 进行通讯的,但是由于模态框没有自己的 Controller,只能够直接发出 Model 上的事件,因此这种事件机制与直接操作 Model 并无太大的区别。

另一种情况是,我们需要在模态框中直接操作另一组数据。

提供回调

我们可以为调用该模态框的视图提供一个回调,将用户在模态框中对数据的修改以参数的方式传回。

用这种方式实现的模态框逻辑很简单,只需要将 Model 中的数据表现出来,然后再将用户的输入传回就好了,不需要关心对 Model 的具体操作与结果。

var EditModal = Stapes.subclass({
    constructor : function (cfg) {
        this.model = cfg.model || null;
        this.cbFn = cfg.cbFn || function () {};
        
        this.$body = null;  // get initialized in `render()`

        this.render();
        this.bindEvents();
    }
});
EditModal.proto({
    bindEvents : function () {
        var self = this;
        self.$body.on('click', '.btn-ok', function () {
            var params = self.getPostParams();
            self.cbFn.call({}, params);
        });
    }
});

这时,在列表视图中,可以这样新建一个模态框:

var self = this;    // `this` points to the list view
var modal = new EditModal({
    model : dataModel,
    cbFn : function (params) {
        self.emit('update', params);
    }
});

绑定父视图

有时在模态框中的操作过于复杂,需要提供多个回调,这样就可以将父视图绑定到这个模态框上,这个模态框就相当于其父元素的一个组成部分,其可以触发父元素上的事件。

var EditModal = Stapes.subclass({
    constructor : function (cfg) {
        this.model = cfg.model || null;
        this.parentView = cfg.parentView || null;
    
        this.$body = null;  // get initialize in `render()`

        this.render();
        this.bindEvents();
    }
});
EditModal.proto({
    bindEvents : function () {
        var self = this;
        self.$body.on('click', '.btn-ok', function () {
            var params = self.getPostParams();
            self.parentView.emit('update', params);
        });
    }
});

更多

  • 使用 ember.js 创建一个模态框的讨论:
    http://discuss.emberjs.com/t/modal-views-can-we-agree-on-a-best-practice/707/5