使用knockoutjs将按钮类与选项控件同步 [英] Synchronizing a button class with an option control using knockoutjs

查看:172
本文介绍了使用knockoutjs将按钮类与选项控件同步的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

通常我可以找到一种让Knockout-js做我想做的事的方法。然而,在这种情况下,我有点挣扎,所以我在SO上向社区提出这个问题。



简介



我正在使用typescript,bootstrap,knockoutjs和nodejs后端编写一个HTML5网络应用程序。



在我的UI中,所有这些都是通过knockoutJS我有一组按钮,形成一个可选择的bootstrap 3按钮组。



这个合理的组,水平给我4个按钮,但允许行为按钮选项与一组选项按钮保持一致。



这种一致性很重要,因为一次只能选择一个按钮,所以当一个是单击,其余部分取消选择。



这是BS3中的默认组件,如下图所示:





正如您在图像,选择'已放弃'按钮,以实现这样一个'active'类必须添加到构成控件的内部radio元素周围的label元素的现有类列表中。以下HTML用于创建此图像:

 < div class =btn-group btn-group-justifieddata -toggle = 按钮 > 
< label class =btn btn-primary>
< input type =radioname =optionsid =option1已选中>已拒绝
< / label>
< label class =btn btn-primary active>
< input type =radioname =optionsid =option2>放弃
< / label>
< label class =btn btn-primary>
< input type =radioname =optionsid =option3>持有
< / label>
< label class =btn btn-primary>
< input type =radioname =optionsid =option3>标题已添加
< / label>
< / div>

所有这些都很有效,除了一个小缺陷,我正在使用淘汰JS来管理UI。



我正试图解决的问题



每个选项的选中状态都与a相关联应用于HTML的视图模型中的属性,因此被拒绝按钮上的内部选项具有正常的knockout-js检查绑定,如下所示:

 < input type =radioname =optionsid =option1checked data-bind =checked:reject>拒绝

每个选项都有自己的支持字段:

 拒绝
弃权
持有
addheader

和每个支持字段都是一个标准的布尔值,保持true / false,我无法弄清楚如何在封闭标签上添加/删除'active'类,以反映选择了哪些状态。



更多确切地说,我无法找到优雅地做到这一点的最好方法。



我试过的方法



什么我知道工作是为每个返回的标签添加一个简单的计算observable

 btn btn-primary active

当该选项设为true时,

 btn btn-primary

如果不是。



我知道这一点,因为在我的视图模型中,我有一个简单的函数:

  SenderDialogViewModel.prototype.isRejectSelected = function(){
if(this.reject == true){
returnbtn btn-primary active;
}
返回btn btn-primary;
};

然而,这种方法意味着4个函数,每个标志要测试一个,这使得很难添加新的以后的标志。



我希望能做的事情如下:

 < label class =btn btn-primarydata-bind =class:isSelected(reject)> 

作为示例。



其中我几乎可以对上面做一些修改:

  SenderDialogViewModel.prototype.isSelected = function(selectionVariable){
if(selectionVariable == true){
returnactive;
}
返回;
};

其中,选择变量可以是视图模型中可用的任何标志,传入。



这里的问题是,只有在第一次绘制UI时才会更新,后续更改标志,无法更新UI以反映给定状态。



为了尝试解决这个问题,我将函数更改为计算的observable,然后在绘制UI时接收到JS错误,声明计算的observable必须有'写'处理程序添加到它,因为我传入了一个参数。



如果我需要添加一个写处理程序,那就没关系,但我宁愿不这样做。 / p>

摘要



总而言之,有一些方法可以与其他选项同步更改班级列表,但大多数它们很混乱,我想要做的是创建一种方法,可以在添加新按钮时轻松扩展(这很重要,因为一些按钮集是动态生成的),而不是广告ding一个处理程序来单独检查并报告每个变量的状态,在一个函数调用中可以简单地添加到视图模型中并一次又一次地重复使用。

解决方案

好的......因为它总是发生,我发布这个,而不是我实际上弄清楚如何让它发挥作用。



解决方案一直盯着我,以淘汰js css绑定的形式。



引用knockout-js docs :


css绑定将一个或多个命名的CSS类添加或删除到关联的DOM元素。例如,如果它变为负值,则可以突出显示红色的某些值。


这是什么意思,因为 根据视图模型中变量的值,我可以将一个类应用于或移除到已存在的类集合中



所以,我的问题的答案,简单地变成:

 < div class =btn-group btn-group-justified数据肘节=按钮> 
< label class =btn btn-primarydata-bind =css:{active:reject}>
< input type =radioname =optionsid =option1已选中>已拒绝
< / label>
< label class =btn btn-primarydata-bind =css:{active:discard}>
< input type =radioname =optionsid =option2>放弃
< / label>
< label class =btn btn-primarydata-bind =css:{active:hold}>
< input type =radioname =optionsid =option3>持有
< / label>
< label class =btn btn-primarydata-bind =css:{active:addheader}>
< input type =radioname =optionsid =option3>标题已添加
< / label>
< / div>

除此之外,如果我现在只需将相应的选项/检查绑定添加到选项控件本身,然后,当点击按钮时,所有内容都应该根据需要正确更新。



关于自行解答的一点注意事项



我认为,有时候,只需要思考你的问题,而虚拟试图向别人描述它,就会引发大脑思考另一个方向。



肯定有帮助,当便士掉线时我输入了这个,但我继续,然后在适当的时候回答/更新,因为我发现这个问题会让其他人吵醒,希望这会服务作为对可能喜欢我的旅行者的一种教育,只是在星期天晚上的脑筋中受苦。



2014年6月30日星期一更新



事实证明,这比我最初预料的要复杂一些。当然,我解决了关于同步上面按钮的CSS问题的主要答案。但是......同步选项按钮也是一个相当大的挑战,这个更新是为了提供一个完整的端到端解决方案。



首先,你需要标记这样的HTML:

 < p>< strong>规则类型:< / strong>< / p> ; 
< div class =btn-group btn-group-justifieddata-toggle =buttons>
< label class =btn btn-primarydata-bind =css:{active:reject},点击:function(){changeType('reject');}>
< input type =radioname =ruletypevalue =rejectdata-bind =checked:selectedOptionString>拒绝
< / label>
< label class =btn btn-primarydata-bind =css:{active:discard},click:function(){changeType('discard');}>
< input type =radioname =ruletypevalue =discardvalue =truedata-bind =checked:selectedOptionString>丢弃
< / label>
< label class =btn btn-primarydata-bind =css:{active:hold},点击:function(){changeType('hold');}>
< input type =radioname =ruletypevalue =holddata-bind =checked:selectedOptionString>持有
< / label>
< label class =btn btn-primarydata-bind =css:{active:addheader},点击:function(){changeType('addheader');}>
< input type =radioname =ruletypevalue =addheaderdata-bind =checked:selectedOptionString>标题已添加
< / label>
< / div>

这里的KEY拿走如下:




  • 1)CSS规则必须指定类active并绑定到您所选择的选项显示true / false的独立选项标志。


  • 2)你必须在按钮上有点击处理程序,BS3(我发现)处理选项控件上按钮的点击,由于淘汰如何使这个HAS成为一个内联函数,传入一个参数,不要试图将它直接绑定到选项使用的计算的observable,它将不起作用。


  • 3)你必须将选项元素标记为如图所示,即它们必须具有相同的名称属性,值必须与你想要所选择的选项所描绘的一致,并且必须与你的按钮处理程序发送的字符串


  • 4)每个选项元素都不能绑定到一个简单的变量,你需要通过一个计算的observab传递它le,不仅仅是因为我正在处理它们,而且即使对于简单的单个布尔开关,它也使用true& false作为字符串,而不是你想象的布尔值。




一旦你标记了你的HTML ,然后你需要构建一个视图模型来支持它,在我的情况下,我实际上使用typescript然后编译为JS,我在这里粘贴的代码是由TS编译器生成的JS代码。



首先,您需要确保在视图模型上具有以下属性:

  this.reject = ko.observable(false); 
this.discard = ko.observable(false);
this.hold = ko.observable(false);
this.addheader = ko.observable(false);

(使用self,this,me ......或者用什么来定义你的淘汰赛模型)重要的是它们是简单的ko observable boolean's



你还需要一个具有写和读函数的计算observable:

  this.selectedOptionString = ko.computed({
read:function(){
if(this.reject())
返回拒绝;
if(this.discard())
返回discard;
if(this.hold())
返回hold;
if(this.addheader())
返回addheader;
},
写:function(value){
console.log(rejectstr: );
console.log(value);
if(value ==reject){
this.reject(true);
this.discard(false);
this.hold(false);
this.addheader(false);
}
if(value ==di scard){
this.reject(false);
this.discard(true);
this.hold(false);
this.addheader(false);
}
if(value ==hold){
this.reject(false);
this.discard(false);
this.hold(true);
this.addheader(false);
}
if(value ==addheader){
this.reject(false);
this.discard(false);
this.hold(false);
this.addheader(true);
}
}
},这个);

这可能会更优雅地完成,但基本上当一个组中的选项被激活时knockout,knockout接受'Value'属性中的任何内容并将其作为字符串发送到您的视图模型中。



如果您将此绑定到一个简单的observable,那么可观察将被设置为该属性的字符串值。然而,对我来说,因为我有一系列4个标志来设置控制UI上的各种状态,如果那么链接是合适的(一个开关或可能是一个查找数组或哈希表也可以工作)



observable的最终结果是一次布尔和一次只有一次设置,并且由于按钮中的CSS与此标志相关联,然后活动类被应用于给定的按钮,其设置为true。



对于读取,您需要将标志状态转换回字符串以进行敲除以与其知道的值进行比较关于,所以读取反过来。



对于按钮单击处理程序,你必须如标记中所示内联这样做,这是beacuse knockout保留一些参数自动的东西,如元素名称,事件信息和其他,我们在这里都不需要,所以最简单的方法是内联它。



然而,在衬里它意味着你的不与物业捆绑等等你不能将它直接绑定到选项控件使用的计算observable。



而你需要做的是在视图模型中添加一个小的存根函数,如下所示:

  SenderDialogViewModel.prototype.changeType = function(newType){
this.selectedOptionString(newType);
};

这不需要以任何方式观察,因为只有当你的按钮被按下时它才会被调用点击。



如果你想看到完整的解决方案,我将会在我的git-hub页面上免费供人们使用这个应用程序,但它还没有结束。



希望上面所有内容对一些人来说都是有用的,我不得不承认,结果却是一点点了比我预期的挑战。



Shawty


usually I can figure out a way to make Knockout-js do what I want. In this case however, i'm struggling a little, so I'm opening the question up to the community here on SO.

Introduction

I'm writing an HTML5 web app using typescript, bootstrap, knockoutjs and a nodejs backend.

In my UI which is all controlled via knockoutJS I have a set of buttons, formed as a bootstrap 3 button group of select-able options.

This justified group, gives me 4 buttons horizontally, but allows the behaviour of the button selections to remain consistant with a group of option buttons.

That consistancy is important, beacuse ONLY one button at a time can ever be selected, so when one is clicked, the rest deselect.

This is a default component in BS3, as the following image shows:

As you can see in the image, the 'Discarded' button is selected, to achieve this a class of 'active' must be added to the existing class list of the label element surrounding the inner radio element that makes up the control. The following HTML is used to create this image:

<div class="btn-group btn-group-justified" data-toggle="buttons">
  <label class="btn btn-primary">
    <input type="radio" name="options" id="option1" checked >Rejected
  </label>
  <label class="btn btn-primary active">
    <input type="radio" name="options" id="option2">Discarded
  </label>
  <label class="btn btn-primary">
    <input type="radio" name="options" id="option3">Held
  </label>
  <label class="btn btn-primary">
    <input type="radio" name="options" id="option3">Header Added
  </label>
</div>

All this works great except for one small flaw, I'm using knockout JS to manage the UI.

The Problem I'm Trying to Solve

The checked state of each of the options is tied to a property inside the view model applied to the HTML, so the inner option on the rejected button for example has a normal knockout-js checked binding added to it as follows:

<input type="radio" name="options" id="option1" checked data-bind="checked: reject">Rejected

Each of the options, each have their own backing field:

reject
discard
hold
addheader

and each of those backing fields are a standard boolean value holding true/false, what I can't figure out is how to add/remove the 'active' class on the enclosing label, to reflect which of these states has been selected.

To be more precise, I cant figure out the best way to do it elegantly.

Approaches I've tried

what I know works is to add a simple computed observable to each label that returns

"btn btn-primary active"

when that option is set to true, and

"btn btn-primary"

when it is not.

I know this, because in my view model, I had a simple function:

SenderDialogViewModel.prototype.isRejectSelected = function () {
        if (this.reject == true) {
            return "btn btn-primary active";
        }
        return "btn btn-primary";
    };

However, this approach means 4 functions, one for each flag to test, making it difficult to add new flags at a later date.

What I'd like to be able to do, is something like the following:

<label class="btn btn-primary" data-bind="class: isSelected(reject)">

as an example.

Which I almost got to work with a slight modification to the above:

SenderDialogViewModel.prototype.isSelected = function (selectionVariable) {
        if (selectionVariable == true) {
            return "active";
        }
        return "";
    };

Where selection variable could be any of the flags available in the view model, passed in.

The problem here was, that this ONLY updated the first time the UI was drawn, subsequent changes to the flags, failed to update the UI to reflect the given status.

To try and resolve this, I changed the function to a computed observable, only to then receive a JS error when the UI was drawn, stating that the computed observable had to have a 'write' handler added to it, because I was passing a parameter in.

If I need to add a write handler, then that's fine, but I'd rather not.

Summary

So in summary, there are ways of changing the class list in sync with other options, but most of them are messy, what I'm trying to do is create a way that's easily expanded as new buttons are added (This is important as some button sets are dynamically generated), rather than adding a handler to individually check and report the status on each and every variable there, in one function call that can be added simply into the view-model and re-used again and again.

解决方案

Ok... and as it ALWAYS happens, no sooner do I post this, than I actually figure out how to make it work.

The solution was staring me in the face all along, in the form of the knockout js css binding.

To quote the knockout-js docs:

The css binding adds or removes one or more named CSS classes to the associated DOM element. This is useful, for example, to highlight some value in red if it becomes negative.

What this is saying, as "I can apply or remove a single class, to the collection of classes already present, based on the value of a variable in my view model"

So, the answer to my problem, quite simply becomes:

<div class="btn-group btn-group-justified" data-toggle="buttons">
  <label class="btn btn-primary" data-bind="css: { active: reject }">
    <input type="radio" name="options" id="option1" checked >Rejected
  </label>
  <label class="btn btn-primary" data-bind="css: { active: discard }">
    <input type="radio" name="options" id="option2">Discarded
  </label>
  <label class="btn btn-primary" data-bind="css: { active: hold }">
    <input type="radio" name="options" id="option3">Held
  </label>
  <label class="btn btn-primary" data-bind="css: { active: addheader }">
    <input type="radio" name="options" id="option3">Header Added
  </label>
</div>

Along with that, if I now just add the appropriate option/checked bindings to the option controls themselves, then everything should update correctly as needed when the buttons are clicked.

A little side note on working your own answer out

I think sometimes, the exercise of just having to think through your problem, while 'virtually' trying to describe it to others, triggers the brain to think in a different direction.

It certainly helped, that I was typing this when the penny dropped, but I proceeded and then answered/updated as appropriate, because it occurs to me that this is a question that will trip others up too, hopefully this will serve as an education to fellow travelers who might like me just be suffering from a sunday evening brainfart.

Update Monday 30-June 2014

It turns out, this was a little more tricky than I first anticipated. Sure I solved the main answer to my question about syncing the CSS for the button above. BUT... syncing the Option buttons also turned out to be quite a challenge, this update is to present a full end to end solution.

First, you need to mark up your HTML like this:

<p><strong>Rule type:</strong></p>
<div class="btn-group btn-group-justified" data-toggle="buttons">
  <label class="btn btn-primary" data-bind="css: { active: reject }, click: function(){ changeType('reject'); }">
    <input type="radio" name="ruletype" value="reject" data-bind="checked: selectedOptionString" >Rejected
  </label>
  <label class="btn btn-primary" data-bind="css: { active: discard }, click: function(){ changeType('discard'); }">
    <input type="radio" name="ruletype" value="discard" value="true" data-bind="checked: selectedOptionString">Discarded
  </label>
  <label class="btn btn-primary" data-bind="css: { active: hold }, click: function(){ changeType('hold'); }">
    <input type="radio" name="ruletype" value="hold" data-bind="checked: selectedOptionString">Held
  </label>
  <label class="btn btn-primary" data-bind="css: { active: addheader }, click: function(){ changeType('addheader'); }">
    <input type="radio" name="ruletype" value="addheader" data-bind="checked: selectedOptionString">Header Added
  </label>
</div>

The KEY take away's here are as follows:

  • 1) The CSS rule must specify the class 'active' and be tied to your independent option flag that shows true/false for that option being selected.

  • 2) You MUST have the click handler on the button, BS3 (as I found out) processes the click on the button NOT on the option control, due to how knockout works this HAS to be an inline function, passing in a single parameter, DO NOT be tempted to tie it directly to the computed observable used by the option, it won't work.

  • 3) You must mark the option elements up as shown, that is they must ALL have the same name attribute, the value MUST match what you want that selected option to portray, and must match the strings your button handler is sending

  • 4) Each option element cannot be bound to a simple variable, you need to pass it through a computed observable, not just because of how I'm handling them, but even for simple single Boolean switches it uses "true" & "false" as strings and not as Boolean's as you might expect it.

Once you've marked up your HTML, you then need to build a view model to support it all, in my case I actually did this using typescript then compiled to JS, the code I'm pasting in here is the JS code produced by the TS compiler.

first and foremost you need to make sure you have the following properties on your view model:

this.reject = ko.observable(false);
this.discard = ko.observable(false);
this.hold = ko.observable(false);
this.addheader = ko.observable(false);

(use self, this, me... or what ever it is you use to define your knockout models) the important thing is that they are simple ko observable boolean's

You also need a computed observable that has both a write and a read function:

this.selectedOptionString = ko.computed({
    read: function () {
        if (this.reject())
            return "reject";
        if (this.discard())
            return "discard";
        if (this.hold())
            return "hold";
        if (this.addheader())
            return "addheader";
    },
    write: function (value) {
        console.log("rejectstr:");
        console.log(value);
        if (value == "reject") {
            this.reject(true);
            this.discard(false);
            this.hold(false);
            this.addheader(false);
        }
        if (value == "discard") {
            this.reject(false);
            this.discard(true);
            this.hold(false);
            this.addheader(false);
        }
        if (value == "hold") {
            this.reject(false);
            this.discard(false);
            this.hold(true);
            this.addheader(false);
        }
        if (value == "addheader") {
            this.reject(false);
            this.discard(false);
            this.hold(false);
            this.addheader(true);
        }
    }
}, this);

This could probably be done a lot more elegantly, but essentially when an option in a group is activated under knockout, knockout takes whatever is in the 'Value' attribute and sends that as a string into your view model.

If you tied this to a simple observable, then that observable would get set to the string value of that attribute. For me however, because I have a series of 4 flags to set that control various states on the UI, a chained if then was appropriate (a switch or possibly a lookup array or hashtable would have worked just as well)

The ultimate outcome of the observable is that one boolean and one only ever be set at a time, and beacuse the CSS in the button is tied to this flag, then the active class gets applied to the given button for which ever is set to true.

For a read, you need to translate your flag state back to a string for knockout to compare to the values it knows about, so the read does the reverse.

For the button click handler, you have to do this inline as shown in the markup, this is beacuse knockout reserves some parameters for automatic things like element name, event info and other's, none of which we need here, so the simplest way is to inline it.

However, in lining it means your not tying to a property and so you can't tie it directly to the computed observable used by the option controls.

Instead what you need to do is add a small stub function to your view model as follows:

SenderDialogViewModel.prototype.changeType = function (newType) {
    this.selectedOptionString(newType);
};

This does not need to be observable in any way as it only gets called one way when your button is clicked.

If you want to see the full solution, I'll be making the app this is part of available on my git-hub page free for people to use, but it's not finished yet.

Hope everything above however turns out to be useful for some folk, I have to admit, it turned out to be a little bit more of a challenge than I expected.

Shawty

这篇关于使用knockoutjs将按钮类与选项控件同步的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆