【问题标题】:Javascript MVC: reset view to default stateJavascript MVC:将视图重置为默认状态
【发布时间】:2019-04-30 14:55:02
【问题描述】:

我有一个视图可以更改样式并显示/隐藏元素以响应用户操作。如何将视图返回到其默认状态?

例如:

class Model {
    constructor() {}
}

class Controller {
    constructor(model) {
        this.model = model;
    }

    respondToButton() {
        $('#context-box').css('color', 'red');
        alert("Cannot do baz.");
    }

    respondToSelect(e) {
        if ($(e.target).val() == "foo") {
            $('#context-area').hide();
        } else {
            $('#context-area').show();
        }
    }
}

class View {
    constructor(controller) {
        this.controller = controller;
        this.bindEvents();
    }

    bindEvents() {
        $('#button').on('click', this.controller.respondToButton);
        $('#select').on('change', this.controller.respondToSelect);
    }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>
<html>
<head></head>
<body>
    <select id="select">
        <option value="foo">Foo</option>
        <option value="bar">Bar</option>
    </select>

    <div hidden id="context-area">
        <span id="context-box">Baz</span>
        <button id="button">Do Baz?</button>
    </div>
</body>

<script>
    $(document).ready(function() {
        var model = new Model();
        var controller = new Controller(model);
        var view = new View(controller);
    });
</script>
</html>

一旦 context-box 被修改,即使外部上下文发生变化,它也会保持这种状态。我希望它恢复到原来的“加载”状态。

一种选择是只重新加载页面,但重新加载页面以响应选择更改感觉不是很好,可能需要“您确定吗?”卸载事件。

对于这个小例子,我可以在选择更改事件中重置 css,但是随着 context-area 变得更加复杂,要重置的元素数量会增加。这也让未来的更改令人沮丧,因为每次更改都需要相应的重置。

我目前使用的选项是在页面加载时克隆 context-area,然后删除和恢复节点以响应上下文变化。这工作正常,但我发现自己删除和恢复节点以响应每个模型更改,以防万一。

有没有更好的方法来处理这个问题?这在 MVC 框架中是如何完成的(我还没学过)?

【问题讨论】:

  • 您的示例中的哪个事件需要重置状态?
  • respondToSelect()

标签: javascript model-view-controller


【解决方案1】:

关于 MVC 有几种思想流派,所以接下来的内容将是一个见仁见智的问题。我将反转视图和控制器之间的连接,以便只有控制器知道其他两个组件。可能模型也应该知道视图,但是由于您的示例没有用例,因此我将忽略它。

我也觉得jQuery应该只用在View中,而不是Controller中:jQuery的作用是(轻松)与DOM交互,这就是View的作用。

对于您的特定问题,我将在 View 类中实现一个方法 reset,其他组件可以在适当时调用该方法。 View 类也会在构造函数中调用它自己:

class Model {
    constructor() {}
}

class Controller {
    constructor(model, view) {
        this.model = model;
        this.view = view;
        view.onButton(this.processAction.bind(this));
        view.onSelect(this.processChoice.bind(this));
    }

    processAction() {
        this.view.highlight = true;
        this.view.message("Cannot do baz.");
    }

    processChoice(value) {
        if (value === "foo") {
            this.view.reset();
        } else {
            this.view.showContext = true;
        }
    }
}

class View {
    constructor() {
        this.reset();
    }
    
    reset() {
        this.highlight = false;
        this.showContext = false;
    }
    
    onButton(listener) {
        $('#button').on('click', listener);
    }
    
    onSelect(listener) {
        $('#select').on('change', function (e) {
            listener($(e.target).val());
        });
    }
    
    set highlight(highlighted) {
        $('#context-box').css('color', highlighted ? 'red' : 'initial');
    }
    
    set showContext(visibility) {
        $('#context-area').toggle(visibility);
    }
    
    message(msg) {
        alert(msg);
    }
}

$(function() {
    new Controller(new Model(), new View());
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<select id="select">
    <option value="foo">Foo</option>
    <option value="bar">Bar</option>
</select>

<div hidden id="context-area">
    <span id="context-box">Baz</span>
    <button id="button">Do Baz?</button>
</div>

更进一步,View 应该真正负责初始 HTML,这意味着您将从一个空页面开始,然后 View 实例将填充它:

class Model {
    constructor() {}
}

class Controller {
    constructor(model, view) {
        this.model = model;
        this.view = view;
        view.onButton(this.processAction.bind(this));
        view.onSelect(this.processChoice.bind(this));
    }

    processAction() {
        this.view.highlight = true;
        this.view.message("Cannot do baz.");
    }

    processChoice(value) {
        if (value === "foo") {
            this.view.reset();
        } else {
            this.view.showContext = true;
        }
    }
}

class View {
    constructor() {
        this.reset();
    }
    
    reset() {
        $(() => $("body").empty().append(
            this.$select = $("<select>").append(
                $("<option>").val("foo").text("Foo"),            
                $("<option>").val("bar").text("Bar")
            ),
            this.$contextArea = $("<div>").append(
                 this.$contextBox = $("<span>").text("Baz"),
                 this.$button = $("<button>").text("Do Baz?")
            ).hide()
        ));
    }
    
    onButton(listener) {
        $(() => $(document).on("click", e => 
            this.$button.is(e.target) && listener()
        ));
    }
    
    onSelect(listener) {
        $(() => $(document).on("change", e => 
            this.$select.is(e.target) && listener(this.$select.val())
        ));
    }
    
    set highlight(highlighted) {
        $(() => this.$contextBox.css('color', highlighted ? 'red' : 'initial'));
    }
    
    set showContext(visibility) {
        $(() => this.$contextArea.toggle(visibility));
    }
    
    message(msg) {
        alert(msg);
    }
}

new Controller(new Model(), new View());
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"&gt;&lt;/script&gt;

在服务器驱动的 Web 应用程序中,MVC 组件将存在于服务器端,您将使用此类服务​​器技术提供的生成 HTML 的功能。

【讨论】:

  • 关于 V-C 连接和使用 jQuery 的优点。在我的真实项目中,我将视图设置为模型的观察者,因此它在这个意义上知道视图。我确实尝试过扩展“只重置每次更改”选项以使用专用的重置功能,这当然是一种改进,但仍然需要每次更改都有相应的重置行。为什么视图调用会自行重置?这不是多余的吗,因为在创建视图时它已经处于重置状态?
  • 好吧,老实说,您已经使用hidden 在 HTML 中进行了一些初始化。这种事情真的应该由 View 在初始化时完成。在更极端的解释中,首先应该是 View 生成 HTML。因此,您将从 body 中的任何 HTML 元素开始。这将在构造视图时填充。所以在那种情况下,在构造函数中调用reset 真的很有意义。 reset 实际上可以清空 body 标记并重新填充它。
  • 我添加了我上面所说的实现。
【解决方案2】:

我猜经典的做法是发送 ajax 请求以再次获取页面并使用您将获得的 html 作为响应仅刷新您想要的部分。

但是您在加载时保存节点然后在需要时恢复它的想法似乎是最快的!

编辑:对于 Ajax 请求,我正在考虑类似的事情:

function resetDiv() {
  let myRequest = new XMLHttpRequest();
  myRequest.open('GET', "YOUR DEFAULT PAGE PATH");
  myRequest.responseType = "document";
  myRequest.onreadystatechange = function () { 
      if (myRequest.readyState === 4) {
      	  let doc = myRequest.response;
          document.getElementById('context-area').innerHTML = doc.getElementById('context-area').innerHTML;
      }
  };
  myRequest.send();
}

function showContext(element) {
	if(element.value == "bar") {
  	$('#context-area').show();
  } else {
  	$('#context-area').hide();
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<select id="select" onchange="showContext(this);">
    <option value="foo">Foo</option>
    <option value="bar">Bar</option>
</select>
<div hidden id="context-area">
    <span id="context-box">Baz</span>
</div>
<button id="reset" onclick="resetDiv();">
reset
</button>

【讨论】:

  • 你能扩展只刷新我想要的部分吗?如何从检索到的 html 中提取示例中的上下文区域节点?我可以将 html 加载到 DOM 对象中,然后解析出我想要的节点,但这似乎只是将节点本身存储在 ajax 的额外开销中。
猜你喜欢
  • 2012-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多