attaching behavior to elements created by knockoutjs - javascript

Take, for example, the situation using knockoutjs:
<div id="container" data-bind="foreach:containers">
<li class="complex-element">...</li>
</div>
Now I want to attach some complex behavior to complex-element. How do I do that? I can't just attach events directly to complex-element, as they're created and removed dynamically at runtime to match the view model. So, as I see it:
I can riddle my html with data-bind="click:..."s and data-bind="mouseenter:..."s but I would rather avoid this if I could. Maybe I'm too rooted in my old MVC ways, but adding open(), select(), or dragStart() functions and isOpen, isSelected or isDragging observable flags to my view model just makes a mess and my intuition tells me that as the app gets bigger that view model is going to become unmanageable. I'd rather keep my data and my presentation separate if possible.
Or I can use jquery delegation to attach events to something that stays fixed. Something like:
$("#container").on('click', '.complex-element button.open', function(e) {
var elem = $(e.target).parents('.complex-element');
...
});
But this wouldn't work as the app gets more complex because what happens if the container is itself wrapped in an element only shown on login (<div id="wrapper" data-bind="if:isLoggedIn">...</div>). I might as well just bind all events to the body and that's a recipe for disaster.
I found a very cool article on knockmeout.net (http://www.knockmeout.net/2011/07/another-look-at-custom-bindings-for.html) that advocates using custom knockout bindings to drive complex behavior, and this seems to be an awesome solution for widget-like behaviors like autocompletes or date-pickers, but what about just simple old fashioned controllers... would this work?
I guess, after all that, my question is pretty simple: Has anyone used Knockoutjs on a really large web application? And how did you go about it?

You could use a different jquery bind:
$(document).on('click', '#container>.complex-element button.open', function(e) {
var elem = $(e.target).parents('.complex-element');
...
});
This would work if #container does not exist.
There is some work going on in the community to make the knockout bindings unobtrusive and easy to write. For now, knockout offers you dataFor() which can be seen here: Using unobtrusive event handlers or you can use a little library like this: Introducing the Knockout.Unobtrusive Plugin

If you need to execute some arbitrary js on some rendered elements from the foreach you could use the afterAdd callback
<div id="container" data-bind="foreach: { data: containers,
afterAdd: somefunction }">
<li class="complex-element">...</li>
</div>
I have used KO on a really large web app and the way I went about managing complexity was
Split out viewmodels into separate using namespaces within a single master namespace.
Write custom bindings for complex reusable behavior.
Created entity model classes that encapsulated most of the important business logic, akin to backbone models.
Ensured that the viewModels were entirely view specific and view related (helped by point 3)
Kept all jquery selectors to a bare minimum, I feel dirty writing any global delegates but sometimes they are needed.
Hope this helps.

Related

Creating dojox/app Views Programmatically without HTML

I am thinking of making a web app and was contemplating using dojox/app to do it.
I would prefer to use a more programmatic approach to dojo but it seems dojox/app is mostly declarative.
After some searching I found an archive basically asking the same question I have
http://dojo-toolkit.33424.n3.nabble.com/Questions-about-dojox-app-design-td3988709.html
Hay guys,
I've been looking at the livedocs for dojox.app and while it seems quite cool I >have to say some stuff isn't clear to me.
Specifically, is the "template" property of views - specifying an html file - a >must or optional?
This was in 2012.
Since then I have found the customeApp test in the examples in the documentation which seems to show basic programmatic views in dojox/app however I am having some difficulty understanding it.
I would like to create the different views of my app like this
require([
"dojo/dom",
"dojo/ready",
"dojox/mobile/Heading",
"dojox/mobile/ToolBarButton"
], function(dom, ready, Heading, ToolBarButton){
ready(function(){
var heading = new Heading({
id: "viewHeading",
label: "World Clock"
});
heading.addChild(new ToolBarButton({label:"Edit"}));
var tb = new ToolBarButton({
icon:"mblDomButtonWhitePlus",
style:"float:right;"
});
tb.on("click", function(){ console.log('+ was clicked'); });
heading.addChild(tb);
heading.placeAt(document.body);
heading.startup();
});
});
but I can only find examples like this
<div data-dojo-type="dojox/mobile/Heading" data-dojo-props='label:"World Clock"'>
<span data-dojo-type="dojox/mobile/ToolBarButton">Edit</span>
<span data-dojo-type="dojox/mobile/ToolBarButton"
data-dojo-props='icon:"mblDomButtonWhitePlus"'
style="float:right;" onclick="console.log('+ was clicked')"></span>
</div>
Is there a way to go about this programmatically or somewhere I can find some clarification on whats happening here https://github.com/dmachi/dojox_application/tree/master/tests/customApp
Absolutely. I have been creating them programmatically for a long time and believe it is far superior way than templating. Difficulty in tackling a framework is knowing keywords to search for. Your answer, I believe, can be found by learning Dojo WidgetBase, and anything else that uses the word "Widget".
Good start is here http://dojotoolkit.org/reference-guide/1.10/quickstart/writingWidgets.html . To successfully work with Dojo Widgets you will also need:
concept of a LifeCycle http://dojotoolkit.org/reference-guide/1.10/dijit/_WidgetBase.html#id5. Lifecycle injection points will allow you to modify DOM tree of the template using JavaScript native API so you do not have to use data-dojo in attributes all over. You will capture nodes as private class properties during buildRendering phase so you can apply constructor parameters to them passed during instantiation in the parent. Finally, you will return the final DOM in postCreate() or startup(), depending on whether you need to specially handle child components or not.
concept of Evented http://dojotoolkit.org/reference-guide/1.10/dojo/Evented.html . This is what you need to do widgetInstance.on("someEvent", eventHandler) programmatically
Only custom attribute I use within an HTML tags of templateString is data-dojo-attach-point and data-dojo-attach-event. These are very convenient, saving lots of time and makes data binding less bug prone, to automatically connect widget's class properties with values in the tag. Although you can do those programmatically too with innerHTML, the amount of tedious boilerplate code in my opinion is not worth the effort.
Go through that tutorial and if by end you do not understand something do let me know and I will elaborate (I am not the type who just sends askers away on a link and not bother elaborate on the material).

Should all the view logic go in templates?

For example, I need to disable every input when the view's model isn't new (has an id).
I can do :
if(!view.model.isNew()) {
view.$('input, select, textarea').prop('disabled', true);
}
or I can go do an "if" on every input I have in my template:
<input type="text" {{# if model.id }}disabled{{/ if }}/>
If we follow the MVC (or MVP) pattern, I guess the second approach would be best, since the view logic is in the view. However, if I go with this approach and decide to change the condition that disables the inputs, I need to change it for every input in EVERY template. If I leave in the JS code, there is only one place to change.
This is just one example, but I am having similar dilemmas with alot of things. Hopefully you got an answer for that.
Cheers!
Based on your question, I'd say that you're probably running into this general problem for two related reasons -
You're using backbone exclusively, rather than a framework like Marionette or Chaplin.
Your views are "too big" and trying to incorporate too much material into a single view.
I almost never include logic into templates and that's because I almost never need to. That's because I write a lot of very small views, and piece them together to make large views. Marionette's view classes make this very easy to do, by subclassing the backbone view into different view types, adding additional utilities and drastically cutting down on boilerplate. Marionette is also very good at handling nested views, which really allow you to drill down and create the exact view tool you need for a specific use case.
It's not at all uncommon to be able to define a useful view using Marionette in 1-2 lines of code, which gives you a lot of flexibility to make very small, efficient views. The disadvantage to making lots of small views is that it's a lot of code to write, and could become difficult to maintain, but Marionette makes that disadvantage relatively insignificant. Marionette only took me a couple of days to get the hang of, and I highly recommend integrating it into your Backbone apps for exactly this problem.
When your views are big, and try to do too much, you wind up with the problem you're describing - you have to modify them too much to fit your specific needs and your code gets ugly and hard to read. When you have a lot of small views, they're very responsive and need little if any customization.
The example from your question is, I think, a border line case. My gut instinct would be to create two entirely separate views and run them under the following pseudo code:
editableView = { //definition }}
disabledView = { //definition }}
if (newModel)
editableView.render()
else
disabledView.render()
This is my gut instinct because my bet is that there are other differences between the views than whether the inputs are editable. Even if there aren't now, you may find in a few months that your needs have changed and that you'd like to incorporate some changes. The approach I suggest allows you to put those changes right into the appropriate View and not have to worry about logicking them out in a single view and deciding whether that logic belongs in the template or the view.
If you were absolutely certain that the only difference between the two views was going to be whether the inputs were editable, and that your needs are not going to change in the future, then maybe you would want to think about rendering them with the same view. If that is the case, I'd recommend that you put the logic in the javascript, rather than in the template, for the reasons you identified. But as I said, your example really is a borderline case, and in most instances I think you'll find that shrinking your view scope will really help you to see where your "template logic" belongs.
NOTE: I've talked a lot about Marionette in this answer, but I also mentioned Chaplin as another option above. I don't have much experience with Chaplin, but you may want to consider it at it as a Marionette alternative.
I prefer to do implement view logic in templates:
It is easier to change and read
Any dependency to id, element and etc can be implemented in template
List item
complex logic can be implemented in templateHelper of marionette or serializeData of Backbone without any dependencies to content of template or view
you can also implement complex logic using Handlebar Helper
Disadvantages of view logic in code are:
in case of changes to selectors (id, classes and so), all views have to be changed or reviewed
logic has to be applied again if view is re-rendered
Perhaps what you might be looking for presenter (aka decorator).
Instead of sending the template the model directly, consider sending it though a presenter, this way you can construct the attributes for the input field. Something like this:
present = function(model) {
return {
inputAttributes: model.isNew() ? 'disabled' : '',
id: model.id,
name: 'Foobar'
}
}
template(present(model));
then in your template:
<input type="text" {{inputAttributes}}>

Bind javascript events to MVC controls

What is the best way to bind Javascript events to my custom MVC controls? My initial thought is to create the controls using Html Helpers which give them a CSS class that signifies what kind of control they are. Then, on document.ready, I'll use jQuery to select all such controls by their class name and bind their events.
However, I'm concerned about the speed of selecting from the entire dom by class name. I've read (and experienced) how slow this can be, especially in IE8 which we need to target for this project.
I could select by IDs by creating a js file for each page, but I'd rather not do this, as it's a complicated web app with lots of pages. I'd rather have one js file for each type of control that gets included in a view if the view contains at least one of that type of control.
Are CSS classes my best option? Any other ideas? I'm using MVC3.
My advice would be to try it out with classes and test the performance. If you are not satisfied, switch to IDs. I use class selectors all the time and don't find them terribly slow in any browser. When you give jquery a context to search in, things are quite fast. For example:
$('#controls .control').whatever();
Or
$('.control', '#controls').whatever();
Sizzle is great at optimizing these things to be fast.
Edit: Here is a good reference for jQuery performance tips in general (notice #5):
http://net.tutsplus.com/tutorials/javascript-ajax/10-ways-to-instantly-increase-your-jquery-performance/

use templates but keep jquery bindings?

I currently have a LARGE single-page application, with each html "view" being built purely with jQuery DOM creation/manipulation. The javascript files are ridiculously large, and adding or changing a view is a very complex process. I started thinking about using backbone.js and templating. However, if I use templating, will I lose the ability to bind jQuery events and data to elements?
No, you can still apply all your jQuery magic to your resulting DOM elements. Templating will simplify the process of creating DOM elements, but the result is the same and can be used identically.
Why would you lose any capability if you are using templates with jQuery? You can instantiate elements via templates, and bind away as you always have - at template time or via preset .live() handlers.
Although I would take a look to make sure Backbone.Router doesn't give you a more elegant way to handle your interactions.
If performance is a bottleneck issue, inline your events in your template.
<input type="button" onclick="doit(this);">

KnockoutJS bind event after template render

I've been searching for a while, and I'm pretty confident this is a new question, and not a repeat like the title suggests. :)
Basically, I'm trying to find out if there is a subscribe-able event that KnockoutJS creates after a template render when using something like jQuery templates.
I'd use the built-in "afterRender" but I found out that it doesn't fire if the observable array is cleared. I built this demo to illustrate that problem: http://jsfiddle.net/farina/YWfV8/1/.
Also, I'm aware that I could write a custom handler...but that seems really unnecessary for what I need.
I just want one event that fires after the template finishes rendering.
My colleague actually solved this last night using something we were playing with before I went home.
So the whole "problem" with the events “afterRender”, “afterAdd”, and “beforeRemove” is that they act differently in conjunction with a "foreach" binding. KnockoutJS is nice enough to tell you this on their page, but for whatever reason it didn't actually sink in for me until I saw it in practice.
What really works is to scrap the whole "foreach" binding and use Knockout's native "data" bind like this:
data-bind="template: { name: 'item-template', data: items, afterRender: caller }"
Then "afterRender" works exactly as the name suggests.
I was under the impression that you couldn't iterate the collection and render new UI without foreach, but these examples illustrate that it does work.
http://jsfiddle.net/farina/kuFx2/1/ (Using object array style ViewModel)
http://jsfiddle.net/farina/QtZm2/1/ (Using function style ViewModel)
I made an example for both ViewModel styles because I sometimes need one or the other.
Thanks for the help Dan!!
Is beforeRemove is what you are looking for? I am not sure what behaviour you want to achieve. Please checkout this sample: http://jsfiddle.net/romanych/YWfV8/8/
Is it what you want or not?

Categories