knockout and applyBindings - how to control the scope? - javascript

I am new to knockout, so going to fire a lot of questions. But I'm not new to data binding. So I am knocking my head on the difference between my expectations and reality. Here is a very basic question about applyBindings.
It looks like applyBindings has the parameter "view model", which is to me the domain object graph (as javascript objects) plus perhaps additional helper things added for the purpose of the view creation. But what I am completely missing at first is the scope of the bindings! I expected this to be applied to the current parent DOM element. But no, it is applied globally, in the entire page!
So is the expectation that in one HTML document there can only ever be one view model? This is very surprising to me! How am I supposed to create a single page web app where I have one panel showing the address book, another panel showing my appointments, another panel showing one loan application to review, and yet another showing the underwriting of another loan? They are all completely different things, am I really supposed to link them all into a single view model???
In my expectation, you bind a javascript object to a DOM element, and everything in there renders it. With every new nested DOM element, the focus object may change. It may be flowing out of some foreach binding from the parent's object. But then two sibling (or cousin) DOM elements might be sitting side by side and having completely different view model, and also a different life cycle. Like while I am in my underwriting workflow, I quickly need to bring up an address book or my calendar. All of it in a single page app. There should be no global interference between different view models used by different unrelated DOM elements.
And yet here we are with knockout I see it has only one ko.applyBindings(viewModelObject) for a the entire page.
What am I missing? What is preventing us from modifying ko.applyBindings to take two arguments, the view model-object and the DOM element in which to show it? I could try doing that, but I am afraid if knockout has been designed in this global mindset, there might be lots of issues running the knockout machinery more than once on the same page?

I'm sorry if I am frustrating people by shooting an answer already. But since I have a very urgent project I need to try to resolve my issues ASAP, and I am reading the knockout source code, which is quite nicely organized and uses good names, so it's quite intelligible; therefore I have found the answer myself.
The answer is that, yes, you can applyBindings to a parent element that you choose. The document.body is only the default if you don't say anything.
Therefore, from now on, I shall (tell my team to) always call applyBindings with the second argument specified for the rootNode. Like this:
<div>
... all my UI elements for this thing ...
... then last element in this div:
<script type="text/javascript">ko.applyBindings(viewModelObject, document.currentScript.parentElement);</script>
</div>
so, that way I can have multiple view models each in their own DOM element.
And additionally also, I was wondering, does knockout not somehow assign the model object to the DOM element? I could do that in my script tag too:
<div>
... all my UI elements for this thing ...
... then last element in this div:
<script type="text/javascript">
const viewElement = document.currentScript.parentElement;
viewElement.viewModelObject = viewModelObject;
ko.applyBindings(viewModelObject, viewElement);
</script>
</div>
and this allows me then -- if only for debugging -- to find the current view model object on the DOM element that is the root of a view. (It would be nice if that would happen with all other descendant bindings too, but that is perhaps the subject of another question.)
In fact, I decided to put into our general configuration a hard replacement of the ko.applyBindings function:
ko._applyBindings = ko.applyBindings;
ko.applyBindings(viewModelObject, rootNode, extendContextCallback) {
rootNode = rootNode || document.currentScript.parentElement;
rootNode.viewModelObject = viewModelObject;
ko._applyBindings(viewModelObject, rootNode, extendContextCallback);
}
now I don't even have to convince my team to do it this way, they will automatically, even without being aware of it.

Related

What is this code for in the head of the DiscoverWestworld.com website? [duplicate]

In Google Chrome’s Developer Tools, I see a #shadow-root right under <html lang="en"> tag. what does it do and what is it used for? I don’t see it in Firefox nor in IE; only in Chrome, is this a special feature?
If I open it, it shows <head> and <body> and a link beside named reveal, by clicking, it points to the <head> and <body>, nothing else.
This is a special indicator that a Shadow DOM exists. These have existed for years, but developers have never been given APIs into it until recently. Chrome has had this functionality for a while, other browsers are still catching up. It can be toggled in the DevTools Settings under the "Elements" section. Uncheck the "Show User Agent Shadow DOM". This will at least hide away any Shadow DOMs created internally (like select elements.) I am unsure right away if it affects user-created ones, such as custom elements.
These come up in things like iframes as well, where you have a separate DOM tree nested inside of another.
The Shadow DOM is simply saying that some part of the page, has its own DOM within it. Styles and scripting can be scoped within that element so what runs in it only executes in that boundary.
This is one of the primary pieces needed for Web Components to work. Which is a new technology allowing developers to build their own encapsulated components that developers can use just like any other HTML element.
As an example of Shadow DOM, when you have a <video> tag on a web page, its shown as just one tag in the main DOM, but if you enable Shadow DOM, you will be able to see the video player's HTML(player DOM).
This is explained aptly in this article, http://webcomponents.org/articles/introduction-to-shadow-dom/
In the case of web components, there is a fundamental problem that makes widgets built out of HTML and JavaScript hard to use.
Problem: The DOM tree inside a widget isn’t encapsulated from the rest of the page. This lack of encapsulation means your document stylesheet might accidentally apply to parts inside the widget; your JavaScript might accidentally modify parts inside the widget; your IDs might overlap with IDs inside the widget and so on.
Shadow DOM addresses the DOM tree encapsulation problem.
For example, if you had markup like this:
<button>Hello, world!</button>
<script>
var host = document.querySelector('button');
var root = host.createShadowRoot();
root.textContent = 'こんにちは、影の世界!';
</script>
then instead of
Hello, world!
your page looks like
こんにちは、影の世界!
Not only that, if JavaScript on the page asks what the button’s textContent is, it isn’t going to get “こんにちは、影の世界!”, but “Hello, world!” because the DOM subtree under the shadow root is encapsulated.
NOTE: I have picked up above content from https://www.html5rocks.com/en/tutorials/webcomponents/shadowdom/ as it helped me understand shadow DOM a little better than answers already here. I have added relevant content here so that it helps others but do take a look at the link for detailed discussion on same.

Can you "hijack" rendering part of a Ractive template?

I have a "SuperSelect" control currently implemented as a Ractive component, which augments a regular drop-down select list with searching, filtering, extended option descriptions, and various other goodies. This generally works really well, except that I now need to fill one of these SuperSelects with approximately 7,800 options, and it gets really slow, and slows down the rest of the page as well. The problem seems to be Ractive's internal memory usage; if I re-implement the SuperSelect in vanilla JS, most of the problem goes away. Unfortunately, I can't see a good way to actually make use of my more-efficient SuperSelect without tearing out Ractive completely every place that it's used, which seems like throwing the baby out with the bathwater.
So, basically, I need a way to insert a chunk of DOM that's managed by other code into the middle of a Ractive template, while still allowing the controlling code to be notified when relevant keypaths are updated by the containing Ractive instance, and as far as I can tell none of the existing plugin/extension methods quite fit the bill. So far, I've come up with two hacks combining multiple plugin methods that might work:
Combine an adapter and a decorator. In this case, the decorator would simply replace whatever element it was attached to with the DOM fragment for the SuperSelect. A special SuperSelect control object would then be added to the Ractive instance's data with an adaptor that would let it participate in 2-way binding with the rest of the template, and independently communicate with the decorator code to update the SuperSelect DOM.
Combine a decorator with a mini-component and ractive.observe. In this case, the decorator would again replace a particular template element with the SuperSelect DOM fragment, but it would only be used locally within a component whose template consists of nothing but that one decorated element. The component would serve as a means of resetting the keypath root, so that the decorator code can observe a static set of keypaths in order to update the state of the SuperSelect DOM regardless of how the SuperSelect is embedded in a larger parent ractive instance.
Is there any simpler way to do what I need?
Yes – you could create a component with an empty DOM node and re-render its contents inside an observe handler:
const SuperSelect = Ractive.extend({
template: `
<div><!-- we'll render this bit ourselves --></div>`,
onrender () {
const div = this.find( 'div' );
this.observe( 'items', items => {
// render the items however we want
});
}
});
More complete demo here: http://jsfiddle.net/9w9rrr9s/

No DIVs created by D3 on enter() data with multiple values

Please note that it's not a question on how to get a bar. I've got that covered, luckily. It's more of a why not issue. When the reality doesn't meet the expectation, there's usually a lesson to be learned. Also, based on the awesomely helpful comments, I realized that the diagnostics were incorrect so I updated the question.
I've just started experimenting with D3. Following the examples, I get some nice graphs. Then, I tested this (just to accumulate knowledge on less usual approach with purely academic purpose).
var graphs = d3.select("#graph").selectAll("div");
graphs.data([1, 2, 3, 4]).enter()
.append("div").text(function(d){ return d; });
The number of DIVs doesn't change. That confuses me, because I'd expect an increment by 4 (if I'm creating a DIV for each such element). When I fiddle the code, it works. However, I'm stuck on how to trouble-shoot it.
I checked that d3.select("#graph") does contain precisely one element.
I've run the script from the console to avoid load order issues.
My page is a partial view rendered by Razor. All other scripts, except jQuery, which I'need to keep as it's called on page load, are removed.
...
<div id="#graph" ...></div>
...
#section style{ #Styles.Render("~/Page.css") }
#section script{
#Scripts.Render("~/Page.js")
#Scripts.Render("~/Scripts/d3.min.js")
}
I see three things in your code. First of all, using the selector d3.("#graph") refers to <div id="graph" />. You've got a sharp sign in the id of the HTML. That's why you don't see the number of elements increase - they're created into a different component. Use this, instead.
<div id="graph" ...>
An additional point's the order of your scripts. You markup is wrong because you read in Page.js before d3.min.js. If you're not getting errors because of lacking d3 in the onload of your page (which I'm assuming is in the first one), then you've got multiple references to it. This one is unneeded because you've got a global one covering it. Or you can switch the order of scripts like this and skip the global one.
#section script{
#Scripts.Render("~/Scripts/d3.min.js")
#Scripts.Render("~/Page.js")
}
Last point's about your sections. I'm assuming they're called from the layout and that this is only a partial view. If so, keep an eye on the order of rendering. Invoking the rendering of sections in partial views at different stages of the layout can create issues.

Shadow DOM, aim of using it

I have studied Shadow DOM recently, and I was wondering what are the aims of using it instead of the main one.
What does it gives ? Why dont we use standard DOM instead of it (except for styling scoping) ?
It allows you to encapsulate functionality, effectively putting it in a black box. It means you can create [reusable] components whose inner workings aren't exposed; this is impossible using standard DOM.
As an example, take HTML input elements. So, say, the file type of input. To use it on an HTML page, you simply add <input type="file" />, and it works. You don't need to add any extra code or HTML or CSS to handle how it works, it just does, and you can't access the internal bits of it. If you were to write a piece of UI, using HTML/CSS/JS, that did the same thing, it would be fairly complex. But the file input is just a single tag that you can use anywhere, it always does the same thing. The web component family of specs allow you to create your own elements that work in this way, and the Shadow DOM is a critical part of this. You can create a new element, like <my-fantastic-file-input />, with its functionality encapsulated. It has its own internal DOM subtree, but that subtree isn't directly accessible; ditto with scoped styles. The new component does not expose its implementation details to the document.
You can do most of this stuff using the DOM, but the implementation will be wholly tied into the document/application structure. With components, you extract that implementation, and you can reuse it, pass it around, publish it and let other people drop it into their applications/documents, and be sure it will work in exactly the same way, anywhere. You cannot really do that as things currently stand by using the standard DOM.
This is from 2011, and slightly out-of-date, but it's a list of some possible use cases for the component model: http://www.w3.org/2008/webapps/wiki/Component_Model_Use_Cases

How to avoid locking my HTML structure when using jQuery to create rich client experiences?

I've had this happen to me three times now and I feel it's time I learned how to avoid this scenario.
Typically, I build the HTML. Once I'm content with the structure and visual design, I start using jQuery to wire up events and other things.
Thing is, sometimes the client wants a small change or even a medium change that requires me to change the HTML, and this causes my javascript code to break because it depends on HTML selectors that no longer exist.
How can I avoid digging myself into this hole every time I create a website? Any articles I should read?
Make your selectors less brittle.
Don't use a selector by index, next sibling, immediate child, or the like
Use classes so even if you have to change the tag name and the element's position in the HTML, the selector will still work
Don't use parent() or child() without specifying a selector. Make sure you look for a parent or child with a specific class
Sometimes, depending on the amount of rework, you'll have to update the script. Keep them as decoupled as possible, but there's always some coupling, it's the interface between script and HTML. It's like being able to change an implementation without having to change the interface. Sometimes you need new behavior that needs a new interface.
I think the best way to help you is for you to show a small sample of a change in the HTML that required a change to your jQuery code. We could then show you how to minimize changes to JS as you update the HTML

Categories

Resources