How to get knockouts sibling data-bind properties - javascript

UPDATED: See jsfiddle link below
UPDATED Again - See Update 2
I have the following HTML:
<button type="button" data-who="Appellant" data-bind="click: showLetter, hasFlag: { value: DeterminationLettersGenerated, flag: Enum_AppealParties.Appellee, enableIfTrue: true }">View</button>
Within the showLetter function I would like to do something like this:
self.showLetter = function (model, event) {
var flagValue = $(event.target).data("bind").flag;
...
}
And by sibling, I mean siblings to the actual click event that is bound. I just need to get whatever will get me Enum_AppealParties.Appellee.
I have tried numerous combinations of ko.toJS, ko.toJSON, $.parseJSON and JSON.stringify. They always return me a string of the following with quotes or escaped quotes around it:
click: showLetter, hasFlag: { value: DeterminationLettersGenerated, flag: Enum_AppealParties.Appellee, enableIfTrue: true }
What I NEED is the above string converted to JSON so at worst I would need to do the following in code:
self.showLetter = function (model, event) {
var magicObject = SomeAwesomeAnserHere();
var flagValue = magicValue.hasFlag.flag;
...
}
UPDATE:
Re the request to see a repo of it, check out this link Fiddle
Just click on the View button within and some Alert messages will appear. The one that says "Should say Object" says it is a string. Not sure if the combinations I mention above are the way to go or what. Just want to get to each piece of the data-bind elements.
UPDATE 2:
I know KO has to be doing what I am trying to accomplish, right? So after some digging around in the KO code, I see where it is turning the data-bind string into a usable object (in this case a function.) I am close to getting it to be useful within my own bindings/functions. This does not work 100% yet. But perhaps with someone smarter than me tinkering with it...
This code is within a KO.click event like the self.showLetter above:
var rewrittenBindings = ko.expressionRewriting.preProcessBindings($(event.target).data("bind"), null);
var functionBody = "with($context){with($data||{}){return{" + rewrittenBindings + "}}}";
var almost = new Function("$context", "$element", functionBody);

To access sibling bindings, you need to define a custom binding. Defining such a binding that simply wraps the click binding is pretty simple:
ko.bindingHandlers.clickFlag = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
ko.applyBindingAccessorsToNode(element, {
click: function() {
return function(model, event) {
valueAccessor().call(this, model, event, allBindings.get('hasFlag'));
}
}
}, bindingContext);
}
}
http://jsfiddle.net/mbest/9mkw067h/85/

Why not just append it to the click handler?
<button type="button" data-who="Appellant" data-bind="click: function() {showLetter($data,Enum_AppealParties.Appellee);}">View</button>
http://jsfiddle.net/9mkw067h/86/
I agree with the previous posters, though. This should be part of the model.

In this this similar-ish question, which ultimately fizzled out without a good answer:
Knockout how to get data-bind keys and value observables using element?
it became fairly clear the only way to access this info was via parsing the data-bind attribute. Here's an updated version of your fiddle showing how to parse a nested bind statement to get what you need:
http://jsfiddle.net/9mkw067h/83/
This is the code that does the parse:
self.showLetter = function (model, event) {
var binding_info = {}
var binding_attr = $(event.target).attr("data-bind")
var indent = false, indent_key = "";
$(binding_attr.split(",")).each(
function(idx, binding) {
var parts = binding.split(":")
var key = parts[0].trim()
var val = parts[1].trim()
if (val.indexOf("{") != -1) {
binding_info[key] = {}
indent = true
indent_key = key
}
if (indent == true) {
binding_info[indent_key][key] = val.replace("{", "").replace("}", "").trim()
}
else {
binding_info[key] = val
}
if (val.indexOf("}") != -1) {
indent = false
indent_key = ""
}
}
)
console.log(binding_info.hasFlag.flag)
}
At the end of that, binding_info has what you're after.
Update:
The linked question above is slightly different, in that it starts from another view model and a given DOM element, and it says, can I get the bindings for that DOM element? It rules out a custom binding. However, in this instance, custom bindings are already in use, so Michael Best's post below provides a neater answer without custom parsing code and proves my assertion incorrect that custom parsing is the only way to do it!

Related

Knockout: Rules for custom binding triggers firing update method

Can someone tell me why this works?
<div id="test">
<label data-bind="dateText: TheDate"></label>
</div>
ko.bindingHandlers.dateText = {
update:
function(el, f_valueaccessor, allbindings, viewmodel, bindingcontext)
{
// Get current value of supplied value
var val = ko.unwrap(f_valueaccessor());
console.log('dt is ' + (typeof(val.dt) == 'function' ? '' : 'NOT ') + 'observable');
var dt = ko.unwrap(val.dt);
$(el).html(dt ? moment(dt).format('DD/MM/YYYY') : '');
}
};
function Model(data, mapping)
{
var _this = this;
if(data)
ko.mapping.fromJS(data, mapping, _this);
this.TheDate = this.TheDate || { dt: ko.observable() }
}
var model = new Model({ TheDate: { dt: null } });
ko.applyBindings(model);
model.TheDate.dt('2001-01-01T01:01:01');
Fiddle: http://jsfiddle.net/qkLzc5u5/2
The reason I ask is that I am having problems making this custom binding work in a real application - there, it displays any initial date fine but updates are not reflected in the text - however the IsDated computed does update, so the label is displayed; just blank.
I thought that the problem was that the binding is attached to TheDate, which is not an observable itself; only dt is.
However, the code above will in fact fire the update, so the date 01/01/2001 is shown, whereas I was expecting nothing to be displayed due to the null initial value.
Now this is actually exactly the behaviour I want, so I don't have a problem with this code - I am hoping to get a bit more of an insight into how the custom bindings know when to fire so I can debug my real world code better.
(Note that the object model for passing dates is fixed by a web service that I cannot change)

In a custom widget, inside a validation handler of addMethod(), I have a wrong context (this)

I'm creating a custom combobox which uses jQuery validator.
At first they all are gray except the first (it means Country). When I choose 'Slovenská republika' the second combobox is enabled.
They all are instances of a a custom autocomplete combobox widget.
To enable the validation I use this code (which is called within _create: function(){..})
There you can find $.validator.addClassRules(); and $.validator.addMethod(). I also added the appropriate class so it really does something.
_registerCustomValidator: function(){
var uniqueName = this._getUniqueInstanceNameFromThisID(this.id);
var that = this;
console.log(this.id);//this prints 5 unique ids when the page is being loaded
$.validator.addMethod(uniqueName, function(value,element){
if(!that.options.allowOtherValue){
return that.valid;
}
console.log(that.id);//this always prints the ID of the last combobox StreetName
return true;
}, "Error message.");
var o = JSON.parse('{"'+uniqueName+'":"true"}');
$.validator.addClassRules("select-validator", o);
}
//this.id is my own property that I set in _create
Problem: When I change the value of any instance of combobox, it always prints the ID of the last instance StreetName, but it should belong to the one that has been changed.
I thought it might be because of registering $.validator.addMethod("someName",handler) using such a fixed string, so now I pass a uniqueName, but the problem remains.
Therefore the validation of all instances is based on the property allowOtherValue of the last instance.
I don't understand why it behaves so. Does anyone see what might be the problem?
EDIT:
see my comments in the following code
_registerCustomValidator is a custom function within a widget factory.
//somewhere a global var
var InstanceRegistry = [undefined];
//inside a widget factory
_registerCustomValidator: function(){
var i=0;
while(InstanceRegistry[i] !== undefined) ++i;
InstanceRegistry[i] = this.id;
InstanceRegistry[i+1] = undefined;
var ID = i; //here ID,i,InstanceRegistry are correct
$.validator.addMethod(uniqueName, function(value,element){
//here InstanceRegistry contains different values at different positions, so its correct
console.log("ID=="+ID);//ID is always 5 like keeping only the last assiged value.
var that = InstanceRegistry[ID];
if(!that.options.allowOtherValue){
return that.valid;
}
return true;
}, "Error message");
var o = JSON.parse('{"'+uniqueName+'":"true"}');
$.validator.addClassRules("select-validator", o);
}
It looks like a sneaky combination of closure logic and reference logic. The callback in $.validator.addMethod is enclosing a reference to this which will equal the last value of this when $.validator.addMethod. (Or something like that?)
Glancing at your code, it's not clear to me what this is in this context. So I can't really offer a concrete solution. But one solution might be to create some kind of global registry for your thises. Then you could do something along the lines of:
_registerCustomValidator: function(){
var uniqueName = this._getUniqueInstanceNameFromThisID(this.id);
$.validator.addMethod(uniqueName, function(value,element) {
var instance = InstanceRegistry[uniqueName];
if(! instance.options.allowOtherValue){
return instance.valid;
}
return true;
}, "Error message.");
var o = JSON.parse('{"'+uniqueName+'":"true"}');
$.validator.addClassRules("select-validator", o);
}
The registry could be keyed to uniqueName or id, just so long as it is a value getting enclosed in your callback.

In Javascript, is there a way to evaluate a variable that may or may not be a function

I have a complex javascript variable that has numerous nested variables inside of it, which each can contain their own nested variables... When I do my knockout mapping sometimes the nested variables are set as objects and other times those exact same variables are functions. So when my data bindings to those nested variables is expecting an object the next time around when the variable is now a function the binding does not update.
The below fiddle gives an idea of what I am seeing:
http://jsfiddle.net/Eves/AUcGM/2/
html:
<p> <span>Name:</span>
<span data-bind="text: TempData().objectA.Name"></span>
<button id="update" data-bind="click: Update">Update!</button>
</p>
Javascript:
var ViewModel = function (data) {
var me = this;
me.TempData = ko.observable(data);
me.Update = function () {
ko.mapping.fromJS(temp2, {}, me);
};
return me;
};
var temp1 = {
objectA: {
Name: 'temp1.objectA.Name'
}
};
var temp2 = {
objectA: function () {
this.Name = 'temp2.objectA.Name';
return this;
}
};
window.viewModel = ko.mapping.fromJS(new ViewModel(temp1));
ko.applyBindings(window.viewModel);
Initially the span text shows "temp1.objectA.Name". However, if you click the update button and the binding switches from the temp1 object to the temp2 object because "objectA" is now a function the data binding never updates. If I were to change the span's data binding to be "TempData().objectA().Name" then temp2 will work fine...but then that breaks temp1.
So the question is:
Is there a way to always evaluate a variable as either an object or function?
I suspect I could use ko.computed to always get the appropriate value regardless of function or object. But that would get really messy considering the complexity of the object I am really dealing with.
I didn't read your entire question but there is a quick answer -
var returnedValue = ko.unwrap(possibleFunction);
(In older versions of ko this is ko.utils.unwrapObservable)
A good way that you could use this would be in a custom binding handler to unwrap a value, regardless of whether it is a function or not.
ko.bindingHandlers.returnAction = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var value = ko.unwrap(valueAccessor());
// now value is a value no matter what the element's value was set as
$(element).keydown(function (e) {
if (e.which === 13) {
value(viewModel);
}
});
}
};
Edit
Here is a working fiddle example of passing in various objects - http://jsfiddle.net/5udhU/3/
That demonstrates passing a string, an observable, a computed, and a function that all evaluate properly.

jQuery: Why would trigger not fire from a JS object?

I've been implementing a form of a publisher/subscriber design pattern in jQuery. I'm basically building classes in Javascript utilizing CoffeeScript that serve as components on my page. i.e. Navigation, DataList, etc.
Instead of having DOM elements fire events, I have instances of these classes that use trigger on themselves to send custom events. These instances can then listen to each other and can update the DOM elements they own accordingly based on the changes in each others behavior!
I know this works as I have one of my components dispatching a custom event properly. However, I've ran into a snag. I've created another component and for the life of me I cannot figure out why it's event is not being fired.
This is the implementation of my class:
window.List = (function() {
List = function(element, settings) {
var _a, _b, _c;
this.list = $(element);
this.settings = jQuery.extend(List.DEFAULTS, settings);
this.links = this.list.find(this.settings.link_selector);
this.links.selectable();
_b = [SelectableEvent.COMPLETED, SelectableEvent.UNDONE, SelectableEvent.SELECTED, SelectableEvent.DESELECTED];
for (_a = 0, _c = _b.length; _a < _c; _a++) {
(function() {
var event_type = _b[_a];
return this.links.bind(event_type, __bind(function(event, selectable_event) {
return this.dispatch(selectable_event);
}, this));
}).call(this);
}
return this;
};
List.DEFAULTS = {
link_selector: "a",
completed_selector: ".completed"
};
List.prototype.change = function(mode, previous_mode) {
if (mode !== this.mode) {
this.mode = mode;
if (previous_mode) {
this.list.removeClass(previous_mode);
}
return this.list.addClass(this.mode);
}
};
List.prototype.length = function() {
return this.links.length;
};
List.prototype.remaining = function() {
return this.length() - this.list.find(this.settings.completed_selector).length;
};
List.prototype.dispatch = function(selectable_event) {
$(this).trigger(selectable_event.type, selectable_event);
return alert(selectable_event.type);
};
return List;
}).call(this);
Pay attention to:
List.prototype.dispatch = function(selectable_event) {
$(this).trigger(selectable_event.type, selectable_event);
return alert(selectable_event.type);
};
This code is triggered properly and returns the expected event type via an alert. But before the alert it is expected to trigger a custom event on itself. This is where I'm encountering my problem.
$(document).ready(function() {
var list_change_handler, todo_list;
todo_list = new List("ul.tasks");
list_change_handler = function(event, selectable_event) {
return alert("Hurray!");
};
$(todo_list).bind(SelectableEvent.COMPLETED, list_change_handler);
$(todo_list).bind(SelectableEvent.UNDONE, list_change_handler);
$(todo_list).bind(SelectableEvent.SELECTED, list_change_handler);
$(todo_list).bind(SelectableEvent.DESELECTED, list_change_handler);
}
You see here the alert "Hurray" is what I want to fire but unfortunately I am having no luck here. Ironically I've done the exact same thing with another class implemented the same way dispatching a custom event and the listener is receiving it just fine. Any ideas on why this wouldn't work?
Update:
Per discussing in the comments, it looks like Logging "this" in console returns the JS Object representing the class. But logging "$(this)" returns an empty jQuery object, thus trigger would never be fired. Any thoughts on why $(this) is coming up empty when "this" is accurately returning the instance of the class?
I found out that jQuery could not index my object because the class implemented it's own version of a jQuery method. In this case, length(). Renaming the length() method to total() resolved the problem completely and any instance of the class can successfully trigger events.

Is it possible to listen for changes to an object's attributes in JavaScript?

I'm working on a fiddly web interface which is mostly built with JavaScript. Its basically one (very) large form with many sections. Each section is built based on options from other parts of the form. Whenever those options change the new values are noted in a "registry" type object and the other sections re-populate accordingly.
Having event listeners on the many form fields is starting to slow things down, and refreshing the whole form for each change would be too heavy/slow for the user.
I'm wondering whether its possible to add listeners to the registry object's attributes rather than the form elements to speed things up a bit? And, if so, could you provide/point me to some sample code?
Further information:
This is a plug-in for jQuery, so any functionality I can build-on from that library would be helpful but not essential.
Our users are using IE6/7, Safari and FF2/3, so if it is possible but only for "modern" browsers I'll have to find a different solution.
As far as I know, there are no events fired on Object attribute changes (edit: except, apparently, for Object.watch).
Why not use event delegation wherever possible? That is, events on the form rather than on individual form elements, capturing events as they bubble up?
For instance (my jQuery is rusty, forgive me for using Prototype instead, but I'm sure you'll be able to adapt it easily):
$(form).observe('change', function(e) {
// To identify changed field, in Proto use e.element()
// but I think in jQuery it's e.target (as it should be)
});
You can also capture input and keyup and paste events if you want it to fire on text fields before they lose focus. My solution for this is usually:
Gecko/Webkit-based browsers: observe input on the form.
Also in Webkit-based browsers: observe keyup and paste events on textareas (they do not fire input on textareas for some reason).
IE: observe keyup and paste on the form
Observe change on the form (this fires on selects).
For keyup and paste events, compare a field's current value against its default (what its value was when the page was loaded) by comparing a text field's value to its defaultValue
Edit: Here's example code I developed for preventing unmodified form submission and the like:
What is the best way to track changes in a form via javascript?
Thanks for the comments guys. I've gone with the following:
var EntriesRegistry = (function(){
var instance = null;
function __constructor() {
var
self = this,
observations = {};
this.set = function(n,v)
{
self[n] = v;
if( observations[n] )
for( var i=0; i < observations[n].length; i++ )
observations[n][i].apply(null, [v, n]);
}
this.get = function(n)
{
return self[n];
}
this.observe = function(n,f)
{
if(observations[n] == undefined)
observations[n] = [];
observations[n].push(f);
}
}
return new function(){
this.getInstance = function(){
if (instance == null)
{
instance = new __constructor();
instance.constructor = null;
}
return instance;
}
}
})();
var entries = EntriesRegistry.getInstance();
var test = function(v){ alert(v); };
entries.set('bob', 'meh');
entries.get('bob');
entries.observe('seth', test);
entries.set('seth', 'dave');
Taking on-board your comments, I'll be using event delegation on the form objects to update the registry and trigger the registered observing methods.
This is working well for me so far... can you guys see any problems with this?
You could attach a listener to a container (the body or the form) and then use the event parameter to react to the change. You get all the listener goodness but only have to attach one for the container instead of one for every element.
$('body').change(function(event){
/* do whatever you want with event.target here */
console.debug(event.target); /* assuming firebug */
});
The event.target holds the element that was clicked on.
SitePoint has a nice explanation here of event delegation:
JavaScript event delegation is a simple technique by which you add a single event handler to a parent element in order to avoid having to add event handlers to multiple child elements.
Mozilla-engined browsers support Object.watch, but I'm not aware of a cross-browser compatible equivalent.
Have you profiled the page with Firebug to get an idea of exactly what's causing the slowness, or is "lots of event handlers" a guess?
Small modification to the previous answer : by moving the observable code to an object, one can make an abstraction out of it and use it to extend other objects with jQuery's extend method.
ObservableProperties = {
events : {},
on : function(type, f)
{
if(!this.events[type]) this.events[type] = [];
this.events[type].push({
action: f,
type: type,
target: this
});
},
trigger : function(type)
{
if (this.events[type]!==undefined)
{
for(var e = 0, imax = this.events[type].length ; e < imax ; ++e)
{
this.events[type][e].action(this.events[type][e]);
}
}
},
removeEventListener : function(type, f)
{
if(this.events[type])
{
for(var e = 0, imax = this.events[type].length ; e < imax ; ++e)
{
if(this.events[type][e].action == f)
this.events[type].splice(e, 1);
}
}
}
};
Object.freeze(ObservableProperties);
var SomeBusinessObject = function (){
self = $.extend(true,{},ObservableProperties);
self.someAttr = 1000
self.someMethod = function(){
// some code
}
return self;
}
See the fiddle : https://jsfiddle.net/v2mcwpw7/3/
jQuery is just amazing. Although you could take a look to ASP.NET AJAX Preview.
Some features are just .js files, no dependency with .NET. May be you could find usefull the observer pattern implementation.
var o = { foo: "Change this string" };
Sys.Observer.observe(o);
o.add_propertyChanged(function(sender, args) {
var name = args.get_propertyName();
alert("Property '" + name + "' was changed to '" + sender[name] + "'.");
});
o.setValue("foo", "New string value.");
Also, Client Side templates are ready to use for some interesting scenarios.
A final note, this is fully compatible with jQuery (not problem with $)
Links: Home page, Version I currently use
I was searching for the same thing and hitted your question... none of the answers satisfied my needs so I came up with this solution that I would like to share:
var ObservedObject = function(){
this.customAttribute = 0
this.events = {}
// your code...
}
ObservedObject.prototype.changeAttribute = function(v){
this.customAttribute = v
// your code...
this.dispatchEvent('myEvent')
}
ObservedObject.prototype.addEventListener = function(type, f){
if(!this.events[type]) this.events[type] = []
this.events[type].push({
action: f,
type: type,
target: this
})
}
ObservedObject.prototype.dispatchEvent = function(type){
for(var e = 0; e < this.events[type].length; ++e){
this.events[type][e].action(this.events[type][e])
}
}
ObservedObject.prototype.removeEventListener = function(type, f){
if(this.events[type]) {
for(var e = 0; e < this.events[type].length; ++e){
if(this.events[type][e].action == f)
this.events[type].splice(e, 1)
}
}
}
var myObj = new ObservedObject()
myObj.addEventListener('myEvent', function(e){// your code...})
It's a simplification of the DOM Events API and works just fine!
Here is a more complete example

Categories