I'm looking for a method to bind many different keys to different actions/functions in my viewmodel.
I've found this example where a binding handler is used to bind actions to the enter-key.
But how do I modify this handler to also support a supplied key-code? I'd like to be able to use the same handler for all kinds of keys and preferably also combined with modifier keys.
ko.bindingHandlers.executeOnEnter = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var allBindings = allBindingsAccessor();
$(element).keypress(function (event) {
var keyCode = (event.which ? event.which : event.keyCode);
if (keyCode === 13) {
allBindings.executeOnEnter.call(viewModel);
return false;
}
return true;
});
}
};
You could do something like this:
ko.bindingHandlers.actionKey = {
init: function(element, valueAccessor, allBindings, data) {
var handler = function(data, event) {
var keys = ko.utils.unwrapObservable(allBindings().keys) || [13]; //default to Enter Key
if (!Array.isArray(keys))
keys = [keys];
if (keys.indexOf(event.keyCode) > -1) {
valueAccessor().call(data, data, event);
};
};
var newValueAccessor = function() {
return { keyup: handler };
};
ko.bindingHandlers.event.init(element, newValueAccessor, allBindings, data);
}
};
and you can use this binding in like this:
Observable Keys: <input data-bind="actionKey: action, keys: keyCodes" /><br/>
Inline Keys: <input data-bind="actionKey: action, keys: [33, 34]" /><br/>
Inline Key: <input data-bind="actionKey: action, keys: 33" /><br/>
Default Keys: <input data-bind="actionKey: action" /><br/>
Here is a fiddle demonstrating this binding.
Related
I created a computed observable that works well formatting phone numbers. It will take a string of numbers and strip it down to this format xxx-xxx-xxxx when focus is off the form field. then I a have a controller action stripping it down to format xxxxxxxxxx before it persists to the database. Then my computed observable re-formats it to the xxx-xxx-xxxx format.
I now want the to create a reusable custom binding handler that can be implemented across our application. The problem is that I cannot get it to do the last part where it re-formats it back in the form field. So, the issue is when I click update, the form field displays the number as xxxxxxxxxx (the same way as it is in the DB) Does anyone know what I need to change to make by custom binding work like my current computed observable?
Observable:
self.Phone = ko.observable(model.MainPhone ? model.MainPhone : "").extend({ maxLength: 20, minLength: 10 });
Computed Observable working correctly:
self.PhoneFormat = ko.computed(function () {
var phoneFormatting = self.Phone()
.replace(/\D+/g, "")
.replace(/^[01]/, "")
.replace(/(\d{3})(\d{3})(\d{4})/, "$1-$2-$3")
.substring(0, 12);
return self.Phone() ? self.Phone(phoneFormatting) : "";
}, self);
Custom binding not working correctly:
ko.bindingHandlers.formatPhone = {
init: function (element, valueAccessor, allBindings) {
var source = valueAccessor();
var formatter = function () {
return ko.computed({
read: function() { return source(); },
write: function(newValue) {
source(newValue.replace(/\D+/g, "")
.replace(/^[01]/, "")
.replace(/(\d{3})(\d{3})(\d{4})/, "$1-$2-$3")
.substring(0, 12));
}
});
};
ko.bindingHandlers.value.init(element, formatter, allBindings);
},
update: function(element, valueAccessor, allBindings) {
var source = valueAccessor();
var formatter = function() {
return ko.computed({
read: function() { return source(); },
write: function(newValue) {
source(newValue.replace(/\D+/g, "")
.replace(/^[01]/, "")
.replace(/(\d{3})(\d{3})(\d{4})/, "$1-$2-$3")
.substring(0, 12));
}
});
};
ko.bindingHandlers.value.update(element, formatter, allBindings);
}
};
It feels like the read and write should be swapped around...
I'd say you want your "source" data to be just the numbers. The formatted data is mainly for display purposes.
I'd expect a computed layer added in your binding, that does two things:
When writing, remove the formating before putting it in the source
When reading, add in the correct format
I'm not sure if I broke stuff that you had working previously, but it could be something like:
ko.bindingHandlers.formatPhone = {
init: function (element, valueAccessor, allBindings) {
var source = valueAccessor();
var formatter = ko.computed({
write: function(newValue) {
source(newValue.split("-").join(""));
},
read: function() {
return source().replace(/\D+/g, "")
.replace(/^[01]/, "")
.replace(/(\d{3})(\d{3})(\d{4})/, "$1-$2-$3")
.substring(0, 12);
}
});
ko.bindingHandlers.value.init(element, function() { return formatter; }, allBindings);
}
};
var vm = { phoneNr: ko.observable("3456546512") };
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
Via custom binding:
<input data-bind="formatPhone: phoneNr">
<br/>
Mimic external update:
<input data-bind="textInput: phoneNr">
I am not sure why this works but the custom binding did not. :(
HTML:
<input data-bind="value: Phone" />
KO Observable:
self.Phone = ko.observable(model.Phone ? model.Phone : "").trimmed();
KO Subscribable:
ko.subscribable.fn.trimmed = function () {
return ko.computed({
read: function () {
return this().replace(/\D+/g, "")
.replace(/^[01]/, "")
.replace(/(\d{3})(\d{3})(\d{4})/, "$1-$2-$3")
.substring(0, 12);
},
write: function (value) {
this(value.replace(/\D+/g, "")
.replace(/^[01]/, "")
.replace(/(\d{3})(\d{3})(\d{4})/, "$1-$2-$3")
.substring(0, 12));
this.valueHasMutated();
},
owner: this
}).extend({ notify: 'always' });
};
i am trying to submit the text in a textarea with the enterkey. thats working fine but the enterkey is always adding a new line before submitting the data...
i know that i have to use event.preventDefault() but it is not working for me.
ko.bindingHandlers.enterKey = {
init: function (element, valueAccessor, allBindingsAccessor, data) {
var wrappedHandler, newValueAccessor;
// wrap the handler with a check for the enter key
wrappedHandler = function (data, event) {
if (event.keyCode === ENTER_KEY && !event.shiftKey) {
event.target.blur();
valueAccessor().call(this, data, event);
}
};
// create a valueAccessor with the options that we would want to pass to the event binding
newValueAccessor = function () {
return {
keyup: wrappedHandler
};
};
// call the real event binding's init function
ko.bindingHandlers.event.init(element, newValueAccessor, allBindingsAccessor, data);
}
};
You should call event.preventDefault on keypress event.
For example, code below prevents enter key pressing
ko.bindingHandlers.enterKey = {
init: function (element, valueAccessor, allBindingsAccessor, data) {
$(element).keypress(function(e) {
(e.keyCode == 13) {
e.preventDefault();
// do something else
}
})
}
}
I'm trying to have an input element call a function when I press enter and use the value in the element:
<input id="searchQuery" placeholder="Search..." type="text" data-bind="value: searchQuery, valueUpdate: 'onkeydown', event: { keydown: searchQueryEntered }"/>
The function:
searchQueryEntered = function (value, event) {
if (event.keyCode == 13)
...do some stuff...
},
My function does get called, but searchQuery is never updated! I can't even type anything into the input. Somehow it's getting thrown out. What I'm trying to do seems pretty simple to me but I haven't been able to get it right.
To fix your input you need to return true from your keydown handler:
searchQueryEntered = function (value, event) {
if (event.keyCode == 13) {
...do some stuff...
}
return true;
}
Then you should use valueUpdate: 'afterkeydown' to make your model be updated after each keydown.
Check out this working example: http://jsfiddle.net/tabalinas/h4u8E/
Register a bindinghandler for the enter key
ko.bindingHandlers.onEnterKey = {
init: function(element, valueAccessor, allBindings, vm) {
ko.utils.registerEventHandler(element, "keyup", function(event) {
if (event.keyCode === 13) {
ko.utils.triggerEvent(element, "change");
valueAccessor().call(vm, vm);
}
return true;
});
}
};
then you can bind as follow onEnterKey: searchQueryEntered
see fiddle below
http://jsfiddle.net/NccZ8/2/
Currently I am unable to fire off a binding event that is dependent on the results of another binding event in knockout.
In the example below, provide a value in the 'available' input, when the 'condition1' input is filled with a value such as 22 the 'available' input should be cleared and disabled, all of this is done in the skip logic binding. This is happening properly.
However, the problem lies with the execution of the skiplogic binding on the chain1 input element. This isn't even being fired after the 'available' input is cleared of its value. How can I get it so the results of one binding fires off another binding?
Here is the js fiddle version of the code below: http://jsfiddle.net/gYNb8/2/
Here is the form I am using to test the concept out on:
<script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<span>Condition 1</span>
<input id="condition1" data-bind="value: condition1" />
<br/>
<span>Condition 2</span>
<input id="condition2" data-bind="value: condition2" />
<br/>
<span>Available?</span>
<input id="available" data-bind="value: available, skipLogic: condition1, skipLogic: condition2" />
<br/>
<span>Chain1</span>
<input id="chain1" data-bind="value: chain1, skiplogic: available" />
Here is the javascript:
// Overall viewmodel for this screen, along with initial state
function ReservationsViewModel() {
var self = this;
self.condition1 = ko.observable();
self.condition2 = ko.observable();
self.available = ko.observable();
self.chain1 = ko.observable();
}
//Here are the conditions which govern whether an element should be enabled or not
var elementConditions = {
'available': [{
'Condition': 'condition1() > 0',
'Type': 'evaluation'
}, {
'Condition': 'condition2() > 0',
'Type': 'evaluation'
}],
'chain1': [{
'Condition': 'available',
'Type': 'empty'
}]
};
ko.bindingHandlers.skipLogic = {
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var id = $(element).attr("id");
var conditions = elementConditions[id];
var isSkipped = false;
var conditionMet = false;
for (var i = 0; i < conditions.length; i++) {
conditionMet = false;
if (conditions[i].Type == "evaluation") {
conditionMet = eval('viewModel.' + conditions[i].Condition);
} else if (conditions[i].Type == "empty") {
if ($('#' + conditions[i].Condition).val().length == 0) {
conditionMet = true;
}
} else if (conditions[i].Type == "notempty") {
if ($('#' + conditions[i].Condition).val().length > 0) {
conditionMet = true;
}
}
if (conditionMet == true) {
isSkipped = true;
}
}
if (isSkipped) {
eval("viewModel." + id + "('');");
$(element).attr("disabled", "disabled");
} else {
if (elementSkipped[id] > 0) {
$(element).attr("disabled", "disabled");
} else {
$(element).removeAttr("disabled");
}
}
}
};
ko.applyBindings(new ReservationsViewModel());
Instead of trying to keep the conditions separately, can you use Boolean logic to string them together in the binding? this way you don't need to keep track of each bindings state. I have built the following binding:
ko.bindingHandlers.skipLogic = {
init: function(element, valueAccessor) {
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var valueOfCondition = ko.unwrap(valueAccessor());
var jqElement = $(element);
//update if the field is disabled if more than one condition is met
if(valueOfCondition){
jqElement.prop('disabled', true);
}
else{
jqElement.prop('disabled', false);
}
}
};
a working example is here: http://jsfiddle.net/M7vUV/3/
The update function for a binding will be executed when the element is first bound (after the init function) and then will run again whenever any of its dependencies change. You create dependencies by accessing observables within the function (like inside of a computed, because a computed is actually used to facilitate the binding updates).
So, you would want to ensure that you are accessing the retrieving whatever you passed to the binding by calling valueAccessor(), then if the value is observable you would want to call it as a function to retrieve the value. Otherwise, if you unsure whether you have been passed an observable you can call ko.unwrap (before 2.3 this was ko.utils.unwrapObservable - post 2.3 either can be used).
Additionally, you could access values passed to other bindings by using the allBindingsAccessor argument (3rd argument) or accessing values directly off of the data (4th arg) or context (5th arg).
It will not work to pass multiple bindings with the same name of the same element. You might want to consider structuring it differently like passing an array data-bind="skipLogic: [one, two]" and then accessing the values off of each.
i have an html textbox with onkeypress event to send message like below
<input type="text" data-bind="attr:{id: 'txtDim' + $data.userID, onkeypress: $root.sendMsg('#txtDim' + $data.userID, $data)}" />
I have written javascript function to to send message while preesing enter button like below:
self.sendMsg = function (id, data) {
$(id).keydown(function (e) {
if (e.which == 13) {
//method called to send message
//self.SendDIM(data);
}
});
};
In my case i have to press enter button 2 times to send the message.
1: keypress call self.sendMsg
2: keypress call self.SendDIM
But i need to send message on one keypress only. It can be done in plain javascript only. But i need viewmodel data, so applied in data-bind. So not working fine.
The reason you need to press enter twice is that your sendMsg method is only attaching a handler to the keydown event. This handler is then invoked on the second button press.
A better approach would be to write a custom binding handler that attaches the event handler and passes the view model through:
ko.bindingHandlers.returnAction = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
var value = ko.utils.unwrapObservable(valueAccessor());
$(element).keydown(function(e) {
if (e.which === 13) {
value(viewModel);
}
});
}
};
You can see a running example here
I have added keypress event like below to textbox
<input type="text" data-bind="attr:{id: 'txtGoalsheetNote' + $data.userID}, event:{keypress: $root.SendMsg}" />
And in javascript i have added the following method by keeping event as a parameter
self.SendMsg= function (data, event) {
try {
if (event.which == 13) {
//call method here
return false;
}
return true;
}
catch (e)
{ }
}
Then its work.