I can't get the enable binding to work in Knockout JS. With the enabled property set to false, the button is not disabled and I can still click it.
see fiddle
<a class="btn btn-xl btn-primary"
href="#"
role="button"
data-bind="enable: enabled, click: clicked, visible: isVisible">
<i class="icon-only icon-ok bigger-130"></i>
</a>
var ViewModel = function(){
var self = this;
self.enabled = ko.observable(false);
self.isVisible = ko.observable(true);
self.clicked = function(){
alert('You clicked the button');
};
};
$(function(){
var model = new ViewModel();
ko.applyBindings(model);
})
Enable binding does not work with anything you want.
This is useful with form elements like input, select, and textarea
It also works with buttons. Like in my example http://jsfiddle.net/5CbnH/1/
But it does not work with your link. You are using twitter bootstrap and they enable/disable their "buttons" with css classes. So you have to use css binding like this:
data-bind="css: { yourClass: enabled }"
Check what class is responsible in bootstrap for showing your "button" and modify your code accordingly with css binding.
Right:
✅ enable✅ disable
Wrong:
❌ enabled❌ disabled
Make sure you use disable instead of disabled and enable instead of enabled.
<input type="text" data-bind="value: foo, enable: isEditing"/> YES!!
<input type="text" data-bind="value: foo, enabled: isEditing"/> NO!
Easy mistake to make :-)
For people who might find this in a search:
I had a problem getting the enable binding to work as well. My problem was trying to use a complex expression without referencing the observables like functions:
<input type="button" data-bind="enable:AreAllStepsVerified && IsFormEnabled, click:SubmitViewModel"/>
Should have been:
<input type="button" data-bind="enable:AreAllStepsVerified() && IsFormEnabled(), click:SubmitViewModel"/>
See: https://stackoverflow.com/a/15307588/4230970
What Salvador said in his answer.
You must understand that the enabled and disabled binding in knockout work by putting a disabled attribute on the target DOM element. Now if you look at the HTML documentation you'd notice that not all HTML element support this attribute.
Actually only form elements (e.g. <button>) do. <a> does not.
I got it to work by changing the anchor tag to a button, not really sure why this makes it work, but it works nonetheless.
Updated fiddle.
<button class="btn btn-xl btn-primary"
role="button"
data-bind="enable: enabled, click: clicked, visible: isVisible">
<i class="icon-only icon-ok bigger-130"></i>
</button>
Related
I am doing the following:
<a href="www.stackoverflow.com">
<button disabled="disabled" >ABC</button>
</a>
This works good but I get a HTML5 validation error that says "Element 'button' must not be nested within element 'a button'.
Can anyone give me advice on what I should do?
No, it isn't valid HTML5 according to the HTML5 Spec Document from W3C:
Content model: Transparent, but there must be no interactive content descendant.
The a element may be wrapped around entire paragraphs, lists, tables, and so forth, even entire sections, so long as there is no interactive content within (e.g. buttons or other links).
In other words, you can nest any elements inside an <a> except the following:
<a>
<audio> (if the controls attribute is present)
<button>
<details>
<embed>
<iframe>
<img> (if the usemap attribute is present)
<input> (if the type attribute is not in the hidden state)
<keygen>
<label>
<menu> (if the type attribute is in the toolbar state)
<object> (if the usemap attribute is present)
<select>
<textarea>
<video> (if the controls attribute is present)
If you are trying to have a button that links to somewhere, wrap that button inside a <form> tag as such:
<form style="display: inline" action="http://example.com/" method="get">
<button>Visit Website</button>
</form>
However, if your <button> tag is styled using CSS and doesn't look like the system's widget... Do yourself a favor, create a new class for your <a> tag and style it the same way.
If you're using Bootstrap 3, this works quite well
Primary link
Link
I've just jumped into the same issue and I solved it substituting 'button' tag to 'span' tag. In my case I'm using bootstrap. This is how it looks like:
<a href="#register">
<span class="btn btn-default btn-lg">
Subscribe
</span>
</a>
No.
The following solution relies on JavaScript.
<button type="button" onclick="location.href='http://www.stackoverflow.com'">ABC</button>
If the button is to be placed inside an existing <form> with method="post", then ensure the button has the attribute type="button" otherwise the button will submit the POST operation. In this way you can have a <form> that contains a mixture of GET and POST operation buttons.
It would be really weird if that was valid, and I would expect it to be invalid. What should it mean to have one clickable element inside of another clickable element? Which is it -- a button, or a link?
These days even if the spec doesn't allow it, it "seems" to still work to embed the button within a <a href...><button ...></a> tag, FWIW...
Another option is to use the onclick attribute of the button:
<button disabled="disabled" onClick="location.href='www.stackoverflow.com'" >ABC</button>
This works, however, the user won't see the link displayed on hover as they would if it were inside the element.
You can add a class to the button and put some script redirecting it.
I do it this way:
<button class='buttonClass'>button name</button>
<script>
$(".buttonClass').click(function(){
window.location.href = "http://stackoverflow.com";
});
</script>
why not..you can also embeded picture on button as well
<FORM method = "POST" action = "https://stackoverflow.com">
<button type="submit" name="Submit">
<img src="img/Att_hack.png" alt="Text">
</button>
</FORM>
Explanation and working solution here:
Howto: div with onclick inside another div with onclick javascript
by executing this script in your inner click handler:
if (!e) var e = window.event;
e.cancelBubble = true;
if (e.stopPropagation) e.stopPropagation();
It is illegal in HTML5 to embed a button element inside a link.
Better to use CSS on the default and :active (pressed) states:
body{background-color:#F0F0F0} /* JUST TO MAKE THE BORDER STAND OUT */
a.Button{padding:.1em .4em;color:#0000D0;background-color:#E0E0E0;font:normal 80% sans-serif;font-weight:700;border:2px #606060 solid;text-decoration:none}
a.Button:not(:active){border-left-color:#FFFFFF;border-top-color:#FFFFFF}
a.Button:active{border-right-color:#FFFFFF;border-bottom-color:#FFFFFF}
<p><a class="Button" href="www.stackoverflow.com">Click me<a>
Use formaction attribute inside the button
PS! It only works if your button type="submit"
<button type="submit" formaction="www.youraddress.com">Submit</button>
I am doing a project using vuejs latest version. In this project I want to get html5 attribute associating vue on click event.
my button code is
<a href="javascript:" class="btn btn-info btn-xs" #click="editModal"
data_id="{{$staff->id}}">
<i class="fa fa-pencil"></i>
</a>
and my js is
var staffModal = new Vue({
el: '#app',
methods: {
editModal: function(){
console.log(this.data_id);
}
}});
In my console I get undefined. How to get right value.
MouseEvent instance is passed as 1st parameter to click event handler. Use getAttribute function to access attribute. MouseEvent.target will point to <i> and MouseEvent.currentTarget to <a> (element that the event listener is attached to).
Change your editModal method to:
editModal: function(e) {
console.log(e.currentTarget.getAttribute('data_id'));
}
BTW: use - (dash) not _ (underscore) to create data attributes: correct is data-id not data_id
You have some things wrong in your code.
Let's start with HTML code.
When you need to interpolate an attribute, you must use v-bind. So you have two ways to do that. Using v-bind:attribute="expression" or the shorthand :attribute="expression". Using attribute="{{ expression }}" will definitely not work.
Another important thing, to use a custom attribute name, you should use data-custom-attribute-name instead of data_custom-attribute-name.
<div id="app">
<a
href="javascript:"
class="btn btn-info btn-xs"
#click="editModal"
:data-id="staff.id"
:data-my-amazing-custom-attribute="staff.name">
Click me!
</a>
</div>
Now, let's go to JS. From your question I couldn't know where the $staff variable comes from, so I made an adaptation to demonstrate.
new Vue({
el: '#app',
methods: {
editModal: function(event){
// Here you will be able to see all the
// custom attributes in camelCase
console.log(event.target.dataset);
console.log('ID:', event.target.dataset.id);
console.log('Name:', event.target.dataset.myAmazingCustomAttribute);
}
},
data () {
return {
staff: {
id: 'some id',
name: 'Name of my amazing custom attribute'
}
}
}
});
You can check a working version here: https://jsfiddle.net/6v3wyoh6/
Hope it helps!
Getting the id from a data attribute is fine, and will work, but my question would be, why? You're using Vue, so use Vue. You can pass the id directly.
<a class="btn btn-info btn-xs" #click="editModal({{$staff->id}})">
<i class="fa fa-pencil"></i>
</a>
And your Vue code becomes
methods: {
editModal: function(id){
console.log(id);
}
}
Now you don't need to worry about figuring out what the data attribute is. That's DOM manipulation that you should generally avoid when using Vue because it is unnecessary.
Note: I'm assuming here you are using laravel or something similar such that when {{$staff->id}} is rendered it is rendered as your id.
here the link to vue docs. Exactly the right way to emit a value to an eventhandler.
<button v-on:click="$emit('enlarge-text', 0.1)">
Enlarge text
</button>
The problem that we are having is closing the Foundation for apps popup when the 'give feedback' button is clicked.
<a class="align-center app-student-icon"
zf-popup-toggle="{{'popup-' + $index}}">
<span ng-bind="getMemberById(assessment.user).fullname"></span>
<i class="icon icon-pencil-no-fill"></i>
</a>
<zf-popup id="{{'popup-' + $index}}" class="padding text-center">
<h5>Question Feedback</h5>
<textarea name="name"
rows="4"
placeholder="Leave feedback here"
ng-model="assessment.test.tutorFeedback"></textarea>
<button type="submit"
class="button expand"
ng-click="closeAndSavePopup('popup-' + $index, assessment)">Give feedback</button>
</zf-popup>
The 'Give Feedback' button triggers the below scope function:
scope.closeAndSavePopup = function (popupId, object) {
saveFeedback(object);
setTimeout(function () {
FoundationApi.closeActiveElements();
});
};
We've dependency injected the foundationApi so we can access the module functions foundation has given us. We have used the example above as well as
FoundationApi.publish(popupId, 'close')
However when clicked the foundation popup doesn't seem to want to close. We have also used toggle instead of close to see if this would make a difference.
Has anyone else come into this problem and found a solution?
After looking in the foundation code we realised that this was a bug/poor documentation of our version of foundation for apps.
The answer is to wrap the 'close' in square brackets.
FoundationApi.publish(popupId, ['close'])
The reason being is because in the foundation javascript it is expecting a array for popups and pretty much anything else a string.. So stupid..
cheers
My Plunkr: http://plnkr.co/edit/4MkenJPczFbxy5aoillL?p=preview
<body ng-controller="mainCtrl as main">
<h1>Hello Plunker!</h1>
<p>Button should not be disabled:</p>
<div ng-init="main.btnDisabled = false">
<button ng-model="main.my_button"
ng-class="{ 'btn-success' : !tc.switching, 'btn-disabled' : tc.switching }"
disabled="main.btnDisabled"
type="button"
class="btn btn-info btn-sm switch-btn">My Button</button>
</div>
Angular
angular.module('app').controller('mainCtrl', function($scope) {
vm = this;
vm.btnDisabled = false;
});
I found this answer here, but it didn't work in my example.
The button is disabled because there is disabled attribute. This is enough for browser to know that element must be inactive. The value for disabled attribute doesn't matter, it can be anything.
This is exactly the reason why Angular provides ngDisabled directive, which adds disabled attibute when expression evaluates to true, and removes when it's false.
In your case you should use
<button ng-model="main.my_button"
ng-class="{ 'btn-success' : !tc.switching, 'btn-disabled' : tc.switching }"
ng-disabled="main.btnDisabled"
type="button"
class="btn btn-info btn-sm switch-btn">My Button</button>
There are a few problems that I see here.
First, change disabled to ng-disabled.
Second, when you click the button nothing will change/happen. Instead of putting that functionality into your ng-class, use something like ng-click to change the state.
This isn't contributing to your problem but make sure that you include $scope before passing it into your controller function.
Speaking of $scope, the plunker would be a bit easier to read if you put something on the scope instead of using a controller alias. No problem with that, it just might help you and other people debug your code.
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