【问题标题】:Handle radio button form in Marionette js在 Marionette js 中处理单选按钮表单
【发布时间】:2014-06-25 02:16:33
【问题描述】:

我正在尝试在我的应用程序中构建一个视图,该视图将在模式对话框区域中弹出投票问题。例如,可能是这样的:

    What is your favorite color?

      >Red
      >Blue
      >Green
      >Yellow
      >Other

    Submit Vote

我了解到 Marionette js 不支持开箱即用的表单,建议您自行处理。

上面的那个结构,分支和叶子(问题和选项列表),向我建议 CompositeView。对吗?

如何触发 model.save() 来记录选择?一个 html 表单需要一个动作。我不清楚如何将表单操作连接到 model.save()。

我的草稿 ItemView 和 CompositeView 代码如下。我在球场上吗?应该如何调整?

var PollOptionItemView = Marionette.ItemView.extend({
    template: Handlebars.compile(
        '<input type="radio" name="group{{pollNum}}" value="{{option}}">{{option}}<br>'
    )
});


var PollOptionsListView = Marionette.CompositeView.extend({
    template: Handlebars.compile(

        //The question part
        '<div id="poll">' +
        '<div>{{question}}</div>' +
        '</div>' +

        //The list of options part
        '<form name="pollQuestion" action="? what goes here ?">' +
        '<div id="poll-options">' +
        '</div>' +
        '<input type="submit" value="Submit your vote">' +
        '</form>'
    ),

    itemView: PollOptionItemView,

    appendHtml: function (compositeView, itemView, index) {
        var childrenContainer = $(compositeView.$("#poll-options") || compositeView.el);
        var children = childrenContainer.children();
        if (children.size() === index) {
            childrenContainer.append(itemView.el);
        } else {
            childrenContainer.children().eq(index).before(itemView.el);
        }
    }
});

更多详情:

我的真正目标是动态构建投票问题,这意味着问题和选项在运行时是未知的,而是随后从 SQL 数据库中查询。如果您正在查看我的应用程序,我会通过 SignalR 在您的屏幕上发起投票。本质上,我是在告诉您的浏览器“嘿,从数据库中获取投票问题#1 的内容并显示它们”。我的想法是 CompositeViews 最适合这个,因为它们是数据驱动的。问题和相应的选项可以存储模型和集合,CompositeView 模板可以根据需要动态呈现它们。我有大部分都是有线的,看起来不错。我唯一的问题似乎是要渲染哪种模板的概念。表单?或者我的模板是否应该只是在屏幕上添加一些单选按钮并在其下方添加一个提交按钮,然后我编写一些 JavaScript 来尝试确定用户所做的选择?我根本不想使用表单,只使用主干框架来处理提交。这对我来说似乎很干净,但也许不可能或不明智?尚未确定。

【问题讨论】:

  • 如果您计划为选项使用主干集合,则只需要一个复合视图。关于表单部分,您可以像任何其他 ajax 表单一样处理它,例如stackoverflow.com/a/11855073/2347232。我会在我的视图中(在本例中为compositeView)收听表单上的点击事件,并使用前面示例中显示的方法。关键是使用ev.preventDefault(); 来阻止提交表单,以便您自己处理。如果您想详细说明小提琴示例,请说出来。
  • 是的,我确实计划使用 collection 作为选项。我这样做是因为每个投票问题的选项数量会有所不同,我需要一种通用方法来为每个问题动态创建正确数量的单选按钮。我对替代品持开放态度。在表单提交上,进行 ajax 提交似乎很奇怪。骨干不应该为此封装ajax吗?我完全使用表单的唯一原因是因为我不知道如何制作单选按钮组。我真的很想在没有表格的情况下这样做。只需将一些单选按钮组合在一起,然后自己处理“提交”点击。
  • 我正在查看这两个选项...需要一分钟来评估。我的目标实际上是动态构建未知长度的民意调查问题,并为每个问题从用户那里进行选择。可以即时添加不同长度的新投票问题,并且必须进行一般处理。我概述的方法似乎是一个很好的方法,但我现在质疑它。
  • 好吧,这样就更清楚了。在我采用的模型中,您希望将投票结果保存在哪里?既然你说表单是动态构建的,那么你打算如何将它存储在后端?
  • 所有好问题。是的,想将投票结果保存在模型中。然后,该模型可以提供“结果”视图。投票问题和选项存储在 SQL 数据库中。让我离开这个 cmets 区域并返回并在主帖子中添加更多细节。也许这会有所帮助。

标签: forms radio-button form-submit marionette


【解决方案1】:

我会使用以下方法:

  • 创建调查问题集合
  • 为每种类型的问题创建特殊的项目视图
  • 在您的 CompositeView 中,根据类型选择模型 itemView
  • 使用简单的验证来查看是否所有问题都已得到解答
  • 输出所有问题及其结果的数组。

有关示例实现,请参阅此小提琴:http://jsfiddle.net/Cardiff/QRdhT/
全屏:http://jsfiddle.net/Cardiff/QRdhT/embedded/result/

注意:

  • 在不回答所有问题的情况下尝试一下,以查看工作中的验证
  • 检查控制台是否成功以查看结果

代码

// Define data
var surveyData = [{
    id: 1,
    type: 'multiplechoice',
    question: 'What color do you like?',
    options: ["Red", "Green", "Insanely blue", "Yellow?"],
    result: null,
    validationmsg: "Please choose a color."
}, {
    id: 2,
    type: 'openquestion',
    question: 'What food do you like?',
    options: null,
    result: null,
    validationmsg: "Please explain what food you like."
}, {
    id: 3,
    type: 'checkbox',
    question: 'What movie genres do you prefer?',
    options: ["Comedy", "Action", "Awesome", "Adventure", "1D"],
    result: null,
    validationmsg: "Please choose at least one movie genre."
}];

// Setup models
var questionModel = Backbone.Model.extend({
    defaults: {
        type: null,
        question: "",
        options: null,
        result: null,
        validationmsg: "Please fill in this question."
    },
    validate: function () {
        // Check if a result has been set, if not, invalidate
        if (!this.get('result')) {
            return false;
        }
        return true;
    }
});

// Setup collection
var surveyCollection = Backbone.Collection.extend({
    model: questionModel
});
var surveyCollectionInstance = new surveyCollection(surveyData);
console.log(surveyCollectionInstance);

// Define the ItemViews
/// Base itemView
var baseSurveyItemView = Marionette.ItemView.extend({
    ui: {
        warningmsg: '.warningmsg',
        panel: '.panel'
    },
    events: {
        'change': 'storeResult'
    },
    modelEvents: {
        'showInvalidMessage': 'showInvalidMessage',
        'hideInvalidMessage': 'hideInvalidMessage'
    },
    showInvalidMessage: function() {
        // Show message
        this.ui.warningmsg.show();

        // Add warning class
        this.ui.panel.addClass('panel-warningborder');
    },
    hideInvalidMessage: function() {
        // Hide message
        this.ui.warningmsg.hide();

        // Remove warning class
        this.ui.panel.removeClass('panel-warningborder');   
    }
});

/// Specific views
var multipleChoiceItemView = baseSurveyItemView.extend({
    template: "#view-multiplechoice",
    storeResult: function() {
        var value = this.$el.find("input[type='radio']:checked").val();
        this.model.set('result', value);
    }
});

var openQuestionItemView = baseSurveyItemView.extend({
    template: "#view-openquestion",
    storeResult: function() {
        var value = this.$el.find("textarea").val();
        this.model.set('result', value);
    }
});

var checkBoxItemView = baseSurveyItemView.extend({
    template: "#view-checkbox",
    storeResult: function() {
        var value = $("input[type='checkbox']:checked").map(function(){
            return $(this).val();
        }).get();
        this.model.set('result', (_.isEmpty(value)) ? null : value);
    }
});

// Define a CompositeView
var surveyCompositeView = Marionette.CompositeView.extend({
    template: "#survey",
    ui: {
        submitbutton: '.btn-primary'  
    },
    events: {
        'click @ui.submitbutton': 'submitSurvey'  
    },
    itemViewContainer: ".questions",
    itemViews: {
        multiplechoice: multipleChoiceItemView,
        openquestion: openQuestionItemView,
        checkbox: checkBoxItemView
    },
    getItemView: function (item) {
        // Get the view key for this item
        var viewId = item.get('type');

        // Get all defined views for this CompositeView
        var itemViewObject = Marionette.getOption(this, "itemViews");

        // Get correct view using given key
        var itemView = itemViewObject[viewId];


        if (!itemView) {
            throwError("An `itemView` must be specified", "NoItemViewError");
        }
        return itemView;
    },
    submitSurvey: function() {
        // Check if there are errors
        var hasErrors = false;
        _.each(this.collection.models, function(m) {
            // Validate model
            var modelValid = m.validate();

            // If it's invalid, trigger event on model
            if (!modelValid) {
                m.trigger('showInvalidMessage');   
                hasErrors = true;
            }
            else {
                m.trigger('hideInvalidMessage'); 
            }
        });

        // Check to see if it has errors, if so, raise message, otherwise output.
        if (hasErrors) {
            alert('You haven\'t answered all questions yet, please check.');   
        }
        else {
            // No errors, parse results and log to console
            var surveyResult = _.map(this.collection.models, function(m) {
                return {
                    id: m.get('id'),
                    result: m.get('result')
                }
            });

            // Log to console
            alert('Success! Check your console for the results');
            console.log(surveyResult);

            // Close the survey view
            rm.get('container').close();
        }
    }
});

// Create a region
var rm = new Marionette.RegionManager();
rm.addRegion("container", "#container");

// Create instance of composite view
var movieCompViewInstance = new surveyCompositeView({
    collection: surveyCollectionInstance
});

// Show the survey
rm.get('container').show(movieCompViewInstance);

模板

<script type="text/html" id="survey">
    <div class="panel panel-primary"> 
        <div class="panel-heading"> 
            <h3 class="panel-title" > A cool survey regarding your life </h3>           
        </div>
        <div class="panel-body">
            <div class="questions"></div>
            <div class="submitbutton">
                <button type="button" class="btn btn-primary">Submit survey!</button>
            </div>
        </div>
    </div >
</script>

<script type="text/template" id="view-multiplechoice">
    <div class="panel panel-success"> 
        <div class="panel-heading"> 
            <h4 class="panel-title" > <%= question %> </h4>           
        </div>
        <div class="panel-body">
            <div class="warningmsg"><%= validationmsg %></div>
            <% _.each( options, function( option, index ){ %> 
                <div class="radio">
                  <label>
                    <input type="radio" name="optionsRadios" id="<%= index %>" value="<%= option %>"> <%= option %>
                  </label>
                </div>
            <% }); %>
        </div>
    </div>
</script>

<script type="text/template" id="view-openquestion">
<div class="panel panel-success"> 
        <div class="panel-heading"> 
            <h4 class="panel-title" > <%= question %> </h4>           
        </div>
        <div class="panel-body">
            <div class="warningmsg"><%= validationmsg %></div>
            <textarea class="form-control" rows="3"></textarea>
        </div>
    </div >
</script>

<script type="text/template" id="view-checkbox">
    <div class="panel panel-success"> 
        <div class="panel-heading"> 
            <h4 class="panel-title" > <%= question %> </h4>           
        </div>
        <div class="panel-body">
            <div class="warningmsg"><%= validationmsg %></div>
            <% _.each( options, function( option, index ){ %> 
                <div class="checkbox">
                  <label>
                    <input type="checkbox" value="<%= option %>"> <%= option %>
                  </label>
                </div>
            <% }); %>
        </div>
    </div>
</script>

<div id="container"></div>

更新:添加了车把示例
Jsfiddle 使用车把:http://jsfiddle.net/Cardiff/YrEP8/

【讨论】:

  • 天哪。这是一个非常彻底的例子和一个非常优雅的方法——比我走的路要多得多。除了我最初的模板困境之外,我还在研究它。在我的一般 Marionette 开发中,我不应该做一些我应该做的事情 - 验证方法、ItemView 扩展、getItemView、ui 哈希和一般组织。我会认真研究的。非常感谢。
  • 不客气。祝你的项目好运!请记住,这也只是一种方法,可以进行改进。但至少它表明这是可以做到的。
  • 我创建了另一个帖子 (stackoverflow.com/questions/23599011/…) 的附带问题。如果我的选项数据嵌套在我的 JSON 中更深的级别,是否对把手 #each 循环进行了调整以允许我访问它们?
  • 没关系。请忽略最后的评论。在我的代码中打错字。 “#each 选项”有效。只是拼错了:) Doh!
猜你喜欢
  • 2018-10-29
  • 2015-10-24
  • 2016-02-15
  • 2011-06-29
  • 2014-06-26
  • 1970-01-01
  • 2015-04-06
  • 2015-04-26
  • 2018-02-06
相关资源
最近更新 更多