How to obtain item-specific data on click event

2,690 views
Skip to first unread message

JasonBunting

unread,
Jun 22, 2011, 3:19:36 PM6/22/11
to KnockoutJS
First off, I am a complete and utter newbie with Knockout.js, but have
years of experience with jQuery, MochiKit and pure JavaScript, so I
know what I am doing for the most part. :)

That said, the same skills that allowed me to get nearly anything and
everything done client-side with those tools is hindering my ability
to see how to more elegantly solve my problem with Knockout.js.

Here is the essential code I am going to ask about - there is a
section in the JavaScript pane I have highlighted with comments
(titled "QUESTION SECTION"), with additional information in the other
panes.

http://jsfiddle.net/JasonBunting/Qxfqu/9/

Hopefully, what I am trying to do is obvious and the solution is
relatively easy!

rpn

unread,
Jun 22, 2011, 3:25:32 PM6/22/11
to knock...@googlegroups.com
Hi Jason-
If you want to pass parameters, then, unless you use a custom binding, you would need to use an anonymous function.  

Your binding would look like:

<li><span data-bind="attr: {id: DCID}, text: DN, click: function() {  loadContactDetail(DCID); } "></span></li>


Mark Hahn

unread,
Jun 22, 2011, 3:35:33 PM6/22/11
to knock...@googlegroups.com
I don't know if I missed this in the docs or not, but is any javascript legal in a bind-data attribute?  Does knockout do an eval?
--
Mark Hahn
Website Manager
ma...@boutiquing.com
949-342-4246

rpn

unread,
Jun 22, 2011, 3:44:10 PM6/22/11
to knock...@googlegroups.com
Yes, it essentially does an eval with the proper context.  So, you are able to use JavaScript in your expressions.  The bindings themselves expect to be receiving certain types, of course, though.

JasonBunting

unread,
Jun 22, 2011, 3:52:45 PM6/22/11
to KnockoutJS
RPN, thank you for responding so quickly.

I have seen that, but wanted to avoid it - please tell me there is
something more elegant! It would be nice to be able to access the same
data item used to bind the data originally without the string literal
tricks...

Is your suggestion the recommended way of doing it, or is it the case
that you simply know your suggestion works but haven't considered that
there may be another construct for accomplishing this same thing?

I have a hard time believing there isn't a better way, and I don't
want to start digging through the internals of Knockout unless I
really need to. If this isn't how it already works, it sure would be
nice if this capability was added, perhaps by the event handler
receiving not just the event object, but an object that had the event
on it and the original data item.

Thanks,
Jason

Entity Spaces

unread,
Jun 22, 2011, 4:14:53 PM6/22/11
to KnockoutJS
If you do get this working can you update your jsfiddle example for
us? I want to do the same thing and would love to see the final
solution.

rpn

unread,
Jun 22, 2011, 5:01:03 PM6/22/11
to knock...@googlegroups.com
Using an anonymous function is the recommended and really the only way for passing parameters to a function out of the box, as the function is only passed the event.  This is generally the way that it is described in documentation, etc.

This got a little bit long, but I wanted to fully explain some of the options.  Here is a jsFiddle of these options: http://jsfiddle.net/rniemeyer/BATnz/

There are a number of alternatives that you could consider:
1- You can create a custom binding that always passes the current data to the function.  It might look like:

ko.bindingHandlers.clickWithData {
    initfunction(elementvalueAccessorallBindingsAccessorcontext{
        var action valueAccessor();
        var newValueAccessor function({
            return function({
                action(context);
            };
        };
        ko.bindingHandlers.click.init(elementnewValueAccessorallBindingsAccessorcontext);
    }
};

You could play with call/apply here to control "this" in the function.

2- You could create a custom binding that allows you to discretely pass parameters in the binding.  It might look like:

ko.bindingHandlers.clickWithParams {
    initfunction(elementvalueAccessorallBindingsAccessorcontext{
        var options valueAccessor();
        var newValueAccessor function({
            return function({
                options.action.apply(contextoptions.params);
            };
        };
        ko.bindingHandlers.click.init(elementnewValueAccessorallBindingsAccessorcontext);
    }
};


So, you would define this like: <button data-bind="clickWithParams: { action: yourfunction, params: [ id ] }"pass params </button>


3- If your object itself has a method to select itself, then you can avoid an anonymous function in the data-bind.

This would be something like:

function Item(idnameselected{
    this.id ko.observable(id);
    this.name ko.observable(name);
    this.selectMe function({
        selected(this);
    };
}

var viewModel {
    selectedItemko.observable()
};

viewModel.items ko.observableArray([new Item(1"one"viewModel.selectedItem)new Item(2"two"viewModel.selectedItem)]);

So, now you can bind directly to selectMe, as "this" takes care of knowing what item we are talking about.  The viewModel.selectedItem is passed in for scope reasons, as you may not have access to the viewModel inside of your item.  


4- If you are using the template binding's foreach option, you could simply rely on the fact that, by default, "this" will be your item inside of the function.  This would look like:

var viewModel {
    itemsko.observableArray([{name"one"}{name"two"}{name"three"}]),
    selectedItemko.observable(),
    selectItemfunction({
       viewModel.selectedItem(this)
    }  
};


<ul data-bind="template: { name: 'itemTmpl', foreach: items, templateOptions: { select: selectItem } }"></ul>

<script id="itemTmpl" type="text/html">
    <li>
        <href="#" data-bind="click: $item.select, text: name"></a>
    </li>
</script>


------------------------------------------
Some notes:
-#1 works okay.  Might be nice if the actual click/event binding passed this data into the function, by default.
-#2 is probably my preference.  You can discretely indicate the parameters that you want passed to the binding.  No room for confusion.
-#3 is okay, but the method needs to be on each item.  You can put it on the prototype, but it would need access to the viewModel.selectedItem
-#4 is not preferable to me.  It is simple and works without any custom bindings, but relying on "this" being the item rather than the viewModel doesn't feel right to me and makes selectItem less flexible and you need to know  how to call it properly.

-Also, I am using templateOptions in the samples to show the viewModel not having global scope, so the bindings can't reference viewModel.method directly.
-"this" is really important when passing references to these functions around.  For the viewModel functions it is useful to bind them to the viewModel, if you want to use this as the viewModel inside the function.
-Since, you are currently not using the "foreach" option, you might look at something like #2 where you can pass in your id or your object as a param.  If you are dynamically adding/removing items, then you might want to look at foreach.
-the bindings are just a sample.  They can be tweaked based on how someone would want to use them.

Hope this helps.  I would be happy to help explore the options further with you.





JasonBunting

unread,
Jun 22, 2011, 6:19:55 PM6/22/11
to KnockoutJS
@rpn -

Wow, that's a lot to take in, esp. for a newbie like me. :)

While you were typing up that epic response, I made a small change to
my copy of knockout-1.2.1.debug.js that allows me to do exactly what I
want to do, though I am going to try to improve upon it even more as
soon as I dig into the source code more.
Details:

Modified knockout-1.2.1.debug.js from the original - specifically,
line 1323 in original file was changed from this:

handlerReturnValue = handlerFunction.apply(viewModel, arguments);

to this:

handlerReturnValue = handlerFunction.apply(viewModel, [event,
(allBindings.data || null)]);

As you can see, this is not a big change at all, really. Coupled with
that change, I do the following in the template:

{{each(i, contact) contacts}}
<li><span data-bind="data: contact, text: DN, click:
loadContactDetail"></span></li>
{{/each}}

Finally, my loadContactDetail handler's signature now looks like this:

loadContactDetail: function(event, data) {.....}

So, the data variable now holds the value that was originally used to
bind data to that element. You can see this all working here:
http://jsfiddle.net/JasonBunting/Qxfqu/11/

Hope that makes sense - ideally, as you can probably guess, I
shouldn't have to specify "data: contact" in the data-bind attribute's
value, it should simply be implicit. That is exactly what I expected
Knockout to do from having used MochiKit for years and from writing a
fair share of jQuery widgets/plugins/extensions/etc.

Now, a few questions:

* Does the change I made have any implications that would cause
problems? I don't know much, at the moment, about the internals, but
maybe you can tell by quickly just by looking at it.
* Would it be possible to get a change like this into Knockout? I
don't know anything about future plans...

I am still going to look over your answers, but for now, I am going to
stick with my change (though I hate forking things unless absolutely
necessary) since it works the way I feel it should have. Of course, I
honestly only started using this library in the last couple of days,
so I reserve the right to change my mind! :)

Jason


On Jun 22, 3:01 pm, rpn <rnieme...@gmail.com> wrote:
> Using an anonymous function is the recommended and really the only way for
> passing parameters to a function out of the box, as the function is only
> passed the event.  This is generally the way that it is described in
> documentation, etc.
>
> This got a little bit long, but I wanted to fully explain some of the
> options.  Here is a jsFiddle of these options:http://jsfiddle.net/rniemeyer/BATnz/

<snip/>

Mark Bradley

unread,
Jun 22, 2011, 7:01:40 PM6/22/11
to knock...@googlegroups.com

given that you are using jquery templates to iterate over your items
and not knockout templates (i.e. using a foreach in the template
binding), how is knockout supposed to determine which object you mean
to pass into the click handler? There are at least 4 variables that
could make sense to be used in that binding: $item, $data,
contactGroup, and contact. Even the top level viewModel object would
make sense depending on the programmers intent.

>
> Now, a few questions:
>
>     * Does the change I made have any implications that would cause
> problems? I don't know much, at the moment, about the internals, but
> maybe you can tell by quickly just by looking at it.
>     * Would it be possible to get a change like this into Knockout? I
> don't know anything about future plans...
>
> I am still going to look over your answers, but for now, I am going to
> stick with my change (though I hate forking things unless absolutely
> necessary) since it works the way I feel it should have. Of course, I
> honestly only started using this library in the last couple of days,
> so I reserve the right to change my mind! :)
>
> Jason
>
>
> On Jun 22, 3:01 pm, rpn <rnieme...@gmail.com> wrote:
>> Using an anonymous function is the recommended and really the only way for
>> passing parameters to a function out of the box, as the function is only
>> passed the event.  This is generally the way that it is described in
>> documentation, etc.
>>
>> This got a little bit long, but I wanted to fully explain some of the
>> options.  Here is a jsFiddle of these options:http://jsfiddle.net/rniemeyer/BATnz/
>
> <snip/>

--
-barkmadley
sent from an internet enabled device

rpn

unread,
Jun 22, 2011, 7:48:21 PM6/22/11
to knock...@googlegroups.com
Hi Jason-
I don't think that your change would cause a big problem.  As, Mark said without the "data" param, it might be somewhat confusing to decide what to pass, in some cases.

I generally try to suggest options that don't require forking Knockout, but if a change is generally useful then it might be a good candidate for a pull request.

I know that you are new to Knockout, so it might take a little bit to absorb some of the options that you have.  Writing custom bindings is actually a pretty easy thing to do, especially if they are just a wrapper to an existing binding.

Without forking Knockout, you can write a custom binding that actually does what your change is doing.  This is similar to what I had posted before.   To use a "data" binding, it would look something like:

ko.bindingHandlers.clickWithData {
    initfunction(elementvalueAccessorallBindingsAccessorcontext{
        var action valueAccessor();
        var data ko.utils.unwrapObservable(allBindingsAccessor().data);
        
        var newValueAccessor function({
            return function(event{
                action.apply(context[eventdata]);
            };
        };
        ko.bindingHandlers.click.init(elementnewValueAccessorallBindingsAccessorcontext);
    }
};


So, this is just a wrapper to the actual click binding that uses the information on the binding to create a new function that passes the right parameters.  You would use this binding just like yours and of course you could call it whatever you like:

<button data-bind="data: id, clickWithData: $item.byId">#1 pass data </button>


If you really wanted to, you could even replace the "click" binding at run-time like:

ko.bindingHandlers.click {

    initfunction(elementvalueAccessorallBindingsAccessorcontext{
        var action valueAccessor();
        var data ko.utils.unwrapObservable(allBindingsAccessor().data);

        var newValueAccessor function({
            return {
                clickfunction(event{
                    action.apply(context[eventdata]);
                }
            }
        };
        ko.bindingHandlers.event.init(elementnewValueAccessorallBindingsAccessorcontext);
    }
};


So, there are lots ways to modify/extend Knockout, once you start to get familiar with the various options.  Not sure if it is helpful to you, but here is post that I had on some of these options.

Also, in your new fiddle, you would likely want to make currentContactDetail (currentContactDetail: ko.observable()) an observable and then set it in your function (this.currentContactDetail(detailDataFromServer)).  That way your template will react to the change and you won't have to call ko.applyBindings again (calling it multiple times on the same elements is generally not advisable, as it can attach multiple event handlers to the same elements).

I know that you are new to KO.  I am sorry that these answers were verbose.  I hope that you are enjoying it so far and I look forward to helping you out, if you need it, as you learn more about Knockout.



JasonBunting

unread,
Jun 23, 2011, 11:46:28 AM6/23/11
to KnockoutJS

On Jun 22, 5:01 pm, Mark Bradley <barkmad...@gmail.com> wrote:
>
> given that you are using jquery templates to iterate over your items
> and not knockout templates (i.e. using a foreach in the template
> binding), how is knockout supposed to determine which object you mean
> to pass into the click handler? There are at least 4 variables that
> could make sense to be used in that binding: $item, $data,
> contactGroup, and contact. Even the top level viewModel object would
> make sense depending on the programmers intent.

As stated, I don't know that much about this tool - thus, I didn't
quite understand that there were built-in templates; I saw a mention
of jQuery's templates and was off and running! :)

Regardless, to your statement about what makes sense depending on the
developer's intent: I would generally agree, since developers that
"know what they are doing" often see numerous ways of solving complex
problems. However, it has been my experience that seasoned developers,
coming into a new language, or even a framework or toolkit within a
language he already knows, have a "gut feeling" about how things
"should" work based on years of experience solving problems with
software.

Knowing nothing about Knockout, I made an assumption as I wrote this
code (learning as I went, mind you) about what I could expect, based
on experiences with similar JavaScript constructs. Here's the funny
thing - I actually expected no more than 2 possibilities; either two
parameters would be available or just one - if two, then the first one
would be the event, the second an object that had at a property
holding the data I expected, as well as other properties that may have
useful information; if one, an object similar to the one I just
described for the second parameter of the other possibility, but with
an additional property holding onto the event object.

So, to answer your question, asking me how knockout is "supposed to
determine which object you mean to pass into the click handler?" I
would say it doesn't have to - why not pass a single object containing
references to all relevant objects via properties? Let the developer-
specified handler worry about what specific data it needs out of the
lot, Knockout can simply hand it over and forget it. Unless, of
course, it needs to track/manage one of the objects; but, then again,
this is JavaScript after all - the developer has hundreds of ways to
shoot himself in the foot and Knockout can't be there to stop them
every step of the way, so why worry now?

;)

I appreciate the continued help and input as I get this all figured
out. Regardless of any particulars, Knockout is definitely useful and
time-saving, and I am glad that it works with so many browsers.

Off-topic, I would like to see all of the jQuery-aware code (hopefully
it's not aware of others as well, e.g. Dojo) removed from the core of
Knockout and moved into, perhaps, a compatibility file. Ditto on the
templating, if possible. The more this thing can be cut down the
better, right? Tell me I'm smoking crack if any of this sounds
crazy... If not, I may even be willing to help work on these things,
if needed. I just don't see why it can't be broken out a bit better to
make it as lean as possible.

JasonBunting

unread,
Jun 23, 2011, 1:14:23 PM6/23/11
to KnockoutJS

@rpn:

Thanks again for your help and I will look over your post and
everything else a bit more in the coming days, as I want to really
know this tool well, considering how much time and effort it has
already saved me.

Jason

On Jun 22, 5:48 pm, rpn <rnieme...@gmail.com> wrote:
> Hi Jason-
> I don't think that your change would cause a big problem.  As, Mark said
> without the "data" param, it might be somewhat confusing to decide what to
> pass, in some cases.
>
> I generally try to suggest options that don't require forking Knockout, but
> if a change is generally useful then it might be a good candidate for a pull
> request.
>
> I know that you are new to Knockout, so it might take a little bit to absorb
> some of the options that you have.  Writing custom bindings<http://knockoutjs.com/documentation/custom-bindings.html>is actually a pretty easy thing to do, especially if they are just a wrapper
> here is post that I had on some of these options<http://www.knockmeout.net/2011/03/reacting-to-changes-in-knockoutjs.html>
> .

Mark Bradley

unread,
Jun 23, 2011, 8:02:05 PM6/23/11
to knock...@googlegroups.com

From what I understand of the code base, the only thing requiring
knockout to use jquery (all the jquery specific code is protected by
checks for its existence) is the template binding which you don't have
to use (I also believe someone attempted to get knockout working with
mustache earlier in the year).

JasonBunting

unread,
Jun 24, 2011, 2:09:51 PM6/24/11
to KnockoutJS


On Jun 23, 6:02 pm, Mark Bradley <barkmad...@gmail.com> wrote:
My response to this off-topic conversation of ours has been posted to
a new thread, "Questions and comments on Knockout, jQuery and
agnosticism," found here:
http://groups.google.com/group/knockoutjs/browse_thread/thread/6d4dc8ce0cf5b80

Mark Bradley

unread,
Jun 26, 2011, 8:09:20 AM6/26/11
to knock...@googlegroups.com
I was just thinking that there is another way around the original
problem: a function that returns a function.

define a function that takes an id, and returns a function that is
evaluated when the event happens:
https://github.com/barkmadley/barkmadley.github.com/blob/master/things/contacts.html#L214

to use it as the click handler see here (it gets renamed by the
template binding as a template option so don't worry about the names
not matching):
https://github.com/barkmadley/barkmadley.github.com/blob/master/things/contacts.html#L7

This is a little bit less weighty than a custom binding, but is less reusable.

http://barkmadley.com

Reply all
Reply to author
Forward
0 new messages