Exclude DOM elements from knockout apply binding? - javascript

I want to target my knockout viewmodel to certain section of the dom as thus:
ko.applyBindings(MyViewModel,$('#Target')[0]);
However I do NOT want it to apply to all the doms below it. The reason for this is that the whole SPA thing isn't working very well - can't keep up with the jumbo sized viewmodel that results from including every potential interaction into one giant object. Hence, the page is composed of multiple partial views. I want each partials to instantiate its own ViewModel and provide interface for the parent to interact with.
Some sample dom
<div id="Target">
<!--Everything here should be included except-->
<div data-bind="DoNotBindBelowThis:true">
<!--Everything here should NOT be included by the first binding,
I will specifically fill in the binding with targetted
ApplyBind eg. ko.applyBindings(MyOtherViewModel, $('#MyOtherTarget')[0])
to fill the gaps-->
<div id="MyOtherTarget">
</div>
</div>
</div>
Again how can I exclude an entire dom tree below the div tagged with DoNotBindBelowThis from applyBindings?

Take a look at the blog post here: http://www.knockmeout.net/2012/05/quick-tip-skip-binding.html
Basically, you can create a custom binding like:
ko.bindingHandlers.DoNotBindBelowThis = {
init: function() {
return { controlsDescendantBindings: true };
}
};

Related

Backbone: how to manipulate elements outside the element associated with View

My html structure is:
<div id="header">
</div>
<div id="screen">
</div>
My Backbone View is associated with screen. How to hide/show element in header?
The direct answer is:
There's nothing special backbone will do for you here. Do it like you would do had backbone not existed at all.
The correct answer is:
If you need to manipulate UI elements outside of your view model, your view model is wrong - fix it.
According to the backbone structure & logic - your headerview or screenview should throw an event, in which header view should listen to. If that event happens - then headerview should hide itself.

Template rendered order: parent triggers before child template(s)

I'm quite new to Meteor and I'm having trouble trying to understand the "rendered" event on templates.
Assuming I have this two templates:
<template name="parent">
<div id="list">
{{#each childs}}
{{> child}}
{{/each}}
</div>
</template>
<template name="child">
<div class="item">
<!-- content -->
</div>
</template>
and these two events:
Template.parent.rendered = function () {
console.log('parent');
};
Template.child.rendered = function () {
console.log('child');
};
I always get this from console:
> parent
> child
> child
> child
So basically the parent template triggers "rendered" before the inner templates have finished rendering.
Because of that I'm unable to do execute any post operations to the DOM like jquery plugins.
e.g:
Template.parent.rendered = function () {
$('#list').myplugin();
};
Since this gets executed before inner templates are rendered it breaks the plugin.
Is there a workaround or a meteor event that I can use to safely now when a template is fully rendered, including it's inner templates?
My general advice for problems like this is you should look for a way to activate your plugin after rendering each child, rather than after rendering the parent - even if it means making extra calls to the plugin. If you can do that then it also solves the related problem of what happens when more children are added sometime later (of course this may not apply in your case).
It's hard to give a precise answer without knowing more details about what your plugin does but I can give an example from one of my applications:
I had to make sure all of the children were the same height as the tallest child. My initial reaction was that I had to somehow wait for all of the children to finish rendering and then adjust their heights once. The easier solution was just to resize all of them every time any of them were rendered. Sure it's O(N^2) comparisons, but it's still fast for a small list and works even when new children are added.
Note that if you absolutely had to call the plugin once and no new children could be added later, you could try an ugly hack to initialize the plugin only after all of them were rendered. For example:
Template.child.rendered = function () {
if ($('.child').length === Children.find().count()) {
$('#list').myplugin();
}
};

Knockout template and unique ID within complex web app causing problems

Knockout templating system is great, however in a web app where there are several separated contexts ("views") that are loaded by ajax, one issue appears:
Templates rely on ID
This means that if my chance you have one template with the same name on a view that on another view loaded previously and still existing in the webapp context, knockout (because the browser does this) will take the first matching #templateId element.
On our webapp, we eliminated the ID of all our elements, and when it really needs to be used, it's an ID that is javascript determined to not have duplicates.
Some views can be loaded multiple times in the lifetime of the app, so
no, we can't say "simply check if the id is already used before making your html code" to our team members.
The only other thing we could do would be to check if a specific template is loaded, and if not load it in async, then apply bindings. But for simplicity purpose and the way our project is set up right now, we can't apply an js AMD-like dependency manager.
Questions
Is that possible to specify directly the DOM reference to the template directly?
data-bind="template:function(){ return $('yourSelectorToTheTemplate')[0]; }"
I've looked knockout code and it's weird because we have this:
templateDocument = templateDocument || document;
var elem = templateDocument.getElementById(template);
if (!elem)
throw new Error("Cannot find template with ID " + template);
return new ko.templateSources.domElement(elem);
This means that it really use the DOM element, so why being forced to give an ID for it if we already have the ID?
How do we retrieve dynamically applied IDtemplate, that is also calling another dynamically applied ID (template recursively calling itself for example)?
Setting ID from a binding handler may be wrong: it may set the ID after other data bound elements referred to it, but it would be simpler to have to.
The best solution found for the moment:
Place templates (script element) at the top of the html view.
Use a bindingHandler that does initialization (I called mine "init" as you can see in the example below) to set the ID of the script element
Store that ID inside the $root context so it can be reused by other elements
The result looks like this:
<script type="text/html" data-bind="init: function(){ $rawData.folderItemTemplate = functionThatSetsAndReturnsUniqueId($element, 'folderItemTemplate'); }">
<li>
Some item
<ul data-bind="template: { name: $rawData.folderItemTemplate, foreach: children }"></ul>
</li>
</script>
As you can see we can use this template binding with template: { name: $rawData.yourPropertyName, foreach:... }

Clearing all observable bindings in Knockout

I'm working on a control panel application right now, where each tool loads its own Javascript file, most of which contain some Knockout bindings. Knockout itself is being loaded in the document head, but tools are loaded asynchronous into a #body div, so my concern is that elements will continue to be bound, even after a different tool is loaded. I assume this would result in memory leaks and probably some glitches, if the same element is bound multiple times. Is it possible to clear all Knockout bindings at once, before I load a new tool?
The general pattern that I would recommend is something like:
//obviously doesn't have to be an object literal
var viewModel = {
currentTool: ko.observable()
};
ko.applyBindings(viewModel);
Then, bind your page like:
<div data-bind="with: currentTool">
...content here
</div>
Now, when the page is initially bound, the area will not be rendered as currentTool is undefined, but KO will copy off the children to use as a "template".
When you populate the currentTool observable, it will render a copy of the elements and bind the content.
When you change currentTool, then KO will clean up the existing bindings and elements, and render/bind a new copy of the elements.
So, you only call ko.applyBindings once and continue to update currentTool based on what you want to display.

How to reattach a jquery function to a rendered template?

I have a view that is rendering my template - the template consists of many elements that I am making editable inline with a jquery plugin. The elements are made editable with a .editable() call to each div accordingly.
I have a template that includes elements like:
<div id="pricing">some value</div>
<div id="terms">some other value</div>
Everytime the view is re-rendered, my elements lose the .editable() ability. The plugin applies it's own class names, etc.
In my view's render, I am able to make this work by doing:
var MyView = new Backbone.View.extend({
render: function() {
var html = this.template(this.model.toJSON());
this.$el.html(html);
this.$el.find('#pricing').editable();
this.$el.find('#terms').editable();
});
Is there a better way to do this without explicitly declaring .editable() to every div i need in the view's render method?
I don't think there are many ways around it. The call to editable() has to be made after the dom elements are ready to be manipulated with the plugin. Since you are replacing the HTML you have to wait until the view is re-rendered. I would suggest giving the divs you need to be editable their own class like editable. Then you could use the class selector instead of doing it for each div separately.
<div id="pricing" class="editable">some value</div>
<div id="terms" class="editable">some other value</div>
Then you could do :
this.$(".editable").editable(); //or this.$el.find('.editable').editable();
//instead of
this.$el.find('#pricing').editable();
this.$el.find('#terms').editable();
//etc.
This will save you having to write a new call for every div and you won't have to constantly update it as you add/remove divs from your view.

Categories

Resources