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/
Related
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.
I have a function that takes dom elements and operate on them. It is called pageTransition, and it takes two div elements, and performs a transition animation from the one to the other.
function pageTransition(div1, div2){//do the transition}
for example i can call this function like,
pageTransition (document.body.querySelector("#div1"), document.body.querySelector("#div2"))
That is simple, but let us say I want to pass React class components as my div elements. And that isn't possible because react components are class's not html elements. One quick reminder, this react components in the end will be compiled to div elements with some content during build time. I know I could get around this by doing this
...//the react class
render(
return (
<div id="div1">...</div>//this will allow me to call the above function with the same parameters
)
).
But I was just wondering if there was a magic way to compile this react classes before build time, so rather than giving the id's to the returned div's I was wondering if I could do something like this pageTransition(compile(reactClass), compile(reactClass));
The solution will depend on your intended purpose for pageTransition.
However, there are three potential options you may want to look into:
Statically render a React component into html markup or a string: https://reactjs.org/docs/react-dom-server.html#rendertostaticmarkup
Render two div elements in html and use a React portal to manage what is being rendered in the those divs. This could potentially replace what you are doing in pageTransition. https://reactjs.org/docs/portals.html
Use a ref to access the DOM element that is built from the React component: https://reactjs.org/docs/refs-and-the-dom.html
If you explain what pageTransition does it might help me find a solution for you.
I am using Vue.js and one of my components is 2D canvas editor (by using fabric.js library).
The thing is, that the code for this editor and for the operations that I am making in it is getting pretty verbose to be part of the component script tag.
I tried using mixins and divided the code into separate mixins like canvasMoving, copyPaste, grouping.
Now while this works, I feel like this is still not the way to go, that maybe I should use specialized classes. Also I belive mixin is when you have a functionality to share between multiple components.
Because for example the copyPaste mixin, sometimes needs methods, that are contained in the mixin grouping. This then feels really wrong to me, that since the component includes both of those mixins, it works ok, but if I would remove one of them, it would stop working.
More over all of these mixins works with the canvas, but the canvas is initialized only in one of them and again, they can access it, because the component includes all the mixins, but if I remove the mixin that initializes the canvas, they all stop working because this.canvas will be undefined.
What is the correct approach here? I was thinking about classes with dependency injection, like having master class lets say Editor and it would have its dependencies (grouping, copyPaste, drawing) or something like that.
Then the only thing I do not know is how to connect my separate classes with the Vue.js component. Put the master class in the data object of my component?
So in the end I solved this by using normal classes instead of mixins. I also used bottlejs for dependency injection.
Now each class in its constructor specifies other classes, that it needs to use, so it is immediately clear which classes it is dependent on.
Also before when one mixin needed to call other mixin's method, it was a simple this.methodName() call with no knowledge about where this method is located, whereas now it is clearly stated by this._otherClass.methodName()
Since I needed access to canvas, store and also to emit events, I created class Editor, that has a method init(canvas, store, eventBus), because I can only create the Fabric canvas, after the HTML canvas is displayed. Bottle creates this class with empty fields and I call the init function with the parameters in mounted stage of my component.
All of my classes are then ancestors of EditorProvider class, which only has one getter for this Editor class (that it gets in a constructor and stores in a field), from where I can get any of the specified fields. So the call to store in any of my classes looks like:
this.editor.store.commit('anything')
Call to canvas:
this.editor.canvas.renderAll()
Call to eventbus:
this.editor.eventBus.emit('eventName')
My component now just imports the bottlejs and has access to all the classes by their names. The interaction is mainly by the canvas and window events, so I event created an EventHandler class, that moves all these event listeners from the component to separate class. So in the end in the component I only have HTML template and a few lines of imports and the script tag, which is now much more readable and the dependencies are clearly visible.
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
I need to create an custom-element dynamically.
I tried
var newElement= document.createElement('custom-element');
this is work. but My problem is when I want to add attribute to this element, to bind an array to this element.
I tried
newElement.setAttribute('data','{{data}}')
But it says that it expected to array and received '{{data}}'
How can I add this binding to dynamically elements?
I dont think this is possible right now, please see from kevinpschaaf:
https://github.com/Polymer/polymer/issues/1778
No, we don't currently support this, outside of dom-bind, which is the
only template implementation that late-binds instance children. You
can document.createElement('template', 'dom-bind'), then you can
dynamically append children with binding annotations to its content,
and the bindings will only be evaluated once the dom-bind is attached
to the document. See tests here that show this usage of it:
https://github.com/Polymer/polymer/blob/master/test/unit/dom-bind.html#L95
Note that dom-bind does not currently allow binding to outer scope, so
it has limited use in custom element templates (it's main use case is
for binding between elements in the main document), and that's not
likely to change short-term.
We are achieving a lot of performance optimization by baking the
binding connections into the prototype at registration time for an
element (rather than at instance time), and we haven't built up enough
of the machinery to easily allow runtime addition/removal of bindings.