Load knockoutjs component using javascript - javascript

What's the best way to load a ko component with JavaScript code instead of defining a custom element in html? I tried with ko.components.defaultLoader.load but my component constructor does not hit.
I double checked and the component appears to be registered.

I believe what you are looking for is function ko.components.get(componentName, callback). What this method does is ask the component loaders to resolve the component name until it finds one. If it doesn't find one, it will call callback(null). If it does fine one, it will call callback(componentDefinition), where componentDefinition is the object used to register the component, like { viewmodel: ..., template: ...}.
As far as I can tell, there isn't a ready made function which returns a "working" component. What you have to do after getting the componentDefinition object is something like:
convert the template into a DOM element
instantiate the viewmodel (if defined)
bind the viewmodel to the DOM element
Note that this is not straight away because templates and view models can be defined in several ways.
I recommend looking at https://github.com/knockout/knockout/blob/master/src/components/componentBinding.js and see how it's done here (from line 38).
I hope this works for you, otherwise you could consider other options, like dynamically creating a div element in code with a component binding where the component name and parameters are bound to properties of a view model. Then bind this view model to the div element you just created. This should work "code only" which much less code than the other route.

Related

Blaze Meteor dynamically instanciate template and datacontext

I'm dynamically instanciating template on event / or array change (with observe-like functionality).
To achieve that, I use
//whatever event you want, eg:
$(".foo").on("click", function(){
Blaze.renderWithData(Template.widgetCard, d, $(".cards").get(0));
}
That is working, but obviously, instances aren't bound to any parent's template.
Because I just rendered this template on the div.cards I'm unable to use the Template.parentData(1) to get the parent datacontext, even so this div.cards is include on a template.
The quick fix would be to set the wanted reference (which in my case is an object) variable parent's datacontext on global scope, or even use Session, or directly pass this context through the renderWithData's data.
Do you know any other way,even better the proper one (I mean Meteor fancy one), to achieve that?
Is it a good Blaze.renderWithData use case?
Tell me if i'm unclear or more code is needed.
EDIT:
Complementary context info:
I've a chart (d3) where it's possible to select some parts of it.
It has an array property to stock this selected data part.
Chart = function Chart(clickCb, hoverCb, leaveCb, addSelectionCb, removeSelectionCb){
var chart = this;
chart.selectedParts = [];
//... code
}
From outside of this Chart class (so on the Meteor client side), the chart.selectedParts is modified (add/delete).
The dream would be to "bind" this array chart.selectedParts like:
Template.templateContainingAllThoseCards.helpers({
selectedDataChart: function(){
return Template.instance.chart.selectedParts;
},
//...
});
and on the template being able to do something like that:
<div class="row">
<div class="large-12 columns">
<div class="cards">
{{#each selectedDataChart}}
{{> cardWidget}}
{{/each}}
</div>
</div>
</div>
Like that, if the chart.selectedParts was reactive, Blaze could automatically create or remove cardWidget template instance due to the binding.
I've tried to use manuel:reactivearray package on it (and it's kind of anoying cause I'm doing complex manipulation on this array with Underscore, which obviously don't work with none-native Array type such reactiveArray).
Not working, but I dunno if it should have worked.
What do you think?
At this time, I'm doing things a bit dirty I suppose; I juste instanciate/destroying Blaze View on element added/removed chart.selectedParts as: Blaze.renderWithData(Template.widgetCard, {data: d, chart: this}, $(".cards").get(0));
So here how I manage to do that.
Actually I don't think using Blaze.renderWithData() is a good solution.
Best way I've found is to pass your data on "Reactive mode", to be able to use all Template functionalities, and keep using Spacebars to instanciate templates. (Like parent DataContext link).
Easiest way to have reactive datasource is to always match your data with your Mongo, so I don't have to declare a custom Reactive Data source (which could be tricky with complex from a complex js data structure).
If someone have the same problem, I'm pretty sure it's because you don't follow the "good" way to do (which was my case).
One con with always updating your DB as reactive Data source should be a case where you're doing a lot of UI state change, and after all, saving the change. On this case, it's pretty useless to always pass by the DB, but it's from far the quickest solution.
Ask me if you have any similar issue understanding philosophy/way to do, I'm starting to understand what i'm doing!

Activate a LoadingMask for a View from a Store method in ExtJs without coupling

First of all I know how to set a LoadingMask for a component but have a problem with the uncoupling of the system I am making so I am just looking for a hint/idea.
I am using a MVVC architecture and in the View I have a Container with several Components in it, one of which is a Grid.Panel. The grid is bound to a store and has an event that when fired calls a method of the store. The following code happens in the ViewController:
functionForEvent() {
var store = getStoreForThisGrid();
store.update(Ext.getBody());
}
What happens now is the update() method makes a request to a server, that updates the store itself and the view component, and I need the loading mask during that time. How I handle the situation right now is I pass Ext.getBody() (or a DOM Element representation of a specific component) to the method and it deals with that reference. This function part of the store that is attached to the Grid and resides in the Store:
update : function (el) {
el.mask();
makeRequest();
el.unmask();
}
What I am looking for is another way (Pattern maybe if such exists for JavaScript) to access the View component from the Store instead of passing it around because that does not seem like a good practice and couples the system.
Since I come from a Java background I would have used the Observer pattern but cannot find how to apply this in JS.

Ember.js - get the model data of a DOM element

I have a DOM element and want to get the data model that it is bound to.
This has to be done outside of any ember controller / component. All I have to work with is the DOM element and the global Ember variable (because this piece of code runs from an externally loaded script file).
How can this be done?
It can't be done (not out of the box anyway). There is no relationship between DOM elements and models. The best way I could think of doing this would be to bind the ID of the model to and attribute on a particular DOM item. For example:
export default Ember.Component.extend({
model: null,
attributeBindings: ['data-model-id'],
'data-model-id': function() {
return this.get('model.typeKey') + ':' + this.get('model.id');
}.property('model.{typeKey,id}');
});
Then you'd get an element in the DOM like this:
<div id="ember189" data-model-id="user:86">...</div>
But this only works if your HTML is written such that each model gets its own DOM element. And you'd still have to access private API to get the store via App.__container__.lookup().
Unless you feel like you have a really good reason to do something like this, I would avoid it.

GeoJSON layer does not get recreated when rendered using EmberJS's 'link-to' helper

I am trying to render a LeafletJS map where the colours of the states in the map are dependent on a global parameter that is set in the appropriate Ember route. The setting of the parameter is not the issue but rather the (re)creation of the geoJson layer. When hitting the URL for the first time or when reloading the page the correct map is created, however when the page is rendered using Ember's 'link-to' helper, the map still holds the state colours of the previous page.
drawAll: function() {
var that = this;
Ember.$.ajax('/data/sa_provinces.json').then( function(data){
Frontend.globalPaths = data;
that.get('store').findAll('province').then(function(provinces) {
provinces.forEach(function(province) {
var provinceGeoJSON = window.L.geoJson( province.get('dataFromJSON'),
{ style: province.get('geoJSONStyle'),
province: province,
onEachFeature: province.get('onEachFeature') });
province.set('geo_json', provinceGeoJSON);
provinceGeoJSON.addTo(Frontend.map);
window.province = province;
});
});
});
}.property('drawAll')
This drawAll function is located within a Ember controller and is called from an Ember template. The functions dataFromJSON, geoJSONStyle and onEachFeature are all called the first time a page is called or when the page is refreshed but not when the page is rendered using the Ember's 'link-to' helper. Neither are they called when the URL is entered manually.
If anyone has any ideas or experience with LeafletJS and/or Ember I would really appreciate your help. Thanks in advance, Greg.
The first issue I notice is that drawAll is a computed property, not a function - you seem to be confusing computed properties and functions.
http://emberjs.com/guides/object-model/computed-properties/
Ember computed properties are more like normal attributes that observe other variables, and recompute when those variables change. The property() method after the function declaration changes it into a computed property and specifies which variables the property depends on. On the last line you're specifying that drawAll observes itself, which doesn't make much sense.
You can't call functions from handlebars templates - you can only access properties. So you can access a property, with the side effect of causing that property's function to be called.
If you want just a function that is called as soon as the template loads, you can implement the didInsertElement function on that templates corresponding view, and the contents of the didInsertElement function will run when the template loads.
If you want a property that recomputes based on some conditions changing, you should change the last line to specify which conditions it is observing.
I can't be sure without more info about the template and controller you're using, but for your current use case it looks like you just want a function that runs whenever the template is inserted, so changing the drawAll to an actual function (by removing the .property('drawAll)) and calling it from didInsertElement of the corresponding view will rerun it every time the controller is inserted. Like:
didInsertElement: function() {
this.drawAll()
}
(You need to have created a view that corresponds to the controller in this context)

How to handle initializing and rendering subviews in Backbone.js?

I have three different ways to initialize and render a view and its subviews, and each one of them has different problems. I'm curious to know if there is a better way that solves all of the problems:
Scenario One:
Initialize the children in the parent's initialize function. This way, not everything gets stuck in render so that there is less blocking on rendering.
initialize : function () {
//parent init stuff
this.child = new Child();
},
render : function () {
this.$el.html(this.template());
this.child.render().appendTo(this.$('.container-placeholder');
}
The problems:
The biggest problem is that calling render on the parent for a second time will remove all of the childs event bindings. (This is because of how jQuery's $.html() works.) This could be mitigated by calling this.child.delegateEvents().render().appendTo(this.$el); instead, but then the first, and the most often case, you're doing more work unnecessarily.
By appending the children, you force the render function to have knowledge of the parents DOM structure so that you get the ordering you want. Which means changing a template might require updating a view's render function.
Scenario Two:
Initialize the children in the parent's initialize() still, but instead of appending, use setElement().delegateEvents() to set the child to an element in the parents template.
initialize : function () {
//parent init stuff
this.child = new Child();
},
render : function () {
this.$el.html(this.template());
this.child.setElement(this.$('.placeholder-element')).delegateEvents().render();
}
Problems:
This makes the delegateEvents() necessary now, which is a slight negative over it only being necessary on subsequent calls in the first scenario.
Scenario Three:
Initialize the children in the parent's render() method instead.
initialize : function () {
//parent init stuff
},
render : function () {
this.$el.html(this.template());
this.child = new Child();
this.child.appendTo($.('.container-placeholder').render();
}
Problems:
This means that the render function now has to be tied down with all of the initialization logic as well.
If I edit the state of one of the child views, and then call render on the parent, a completely new child will be made and all of its current state will be lost. Which also seems like it could get dicey for memory leaks.
Really curious to get your guys' take on this. Which scenario would you use? or is there a fourth magical one that solves all of these problems?
Have you ever kept track of a rendered state for a View? Say a renderedBefore flag? Seems really janky.
This is a great question. Backbone is great because of the lack of assumptions it makes, but it does mean you have to (decide how to) implement things like this yourself. After looking through my own stuff, I find that I (kind of) use a mix of scenario 1 and scenario 2. I don't think a 4th magical scenario exists because, simply enough, everything you do in scenario 1 & 2 must be done.
I think it'd be easiest to explain how I like to handle it with an example. Say I have this simple page broken into the specified views:
Say the HTML is, after being rendered, something like this:
<div id="parent">
<div id="name">Person: Kevin Peel</div>
<div id="info">
First name: <span class="first_name">Kevin</span><br />
Last name: <span class="last_name">Peel</span><br />
</div>
<div>Phone Numbers:</div>
<div id="phone_numbers">
<div>#1: 123-456-7890</div>
<div>#2: 456-789-0123</div>
</div>
</div>
Hopefully it's pretty obvious how the HTML matches up with the diagram.
The ParentView holds 2 child views, InfoView and PhoneListView as well as a few extra divs, one of which, #name, needs to be set at some point. PhoneListView holds child views of its own, an array of PhoneView entries.
So on to your actual question. I handle initialization and rendering differently based on the view type. I break my views into two types, Parent views and Child views.
The difference between them is simple, Parent views hold child views while Child views do not. So in my example, ParentView and PhoneListView are Parent views, while InfoView and the PhoneView entries are Child views.
Like I mentioned before, the biggest difference between these two categories is when they're allowed to render. In a perfect world, I want Parent views to only ever render once. It is up to their child views to handle any re-rendering when the model(s) change. Child views, on the other hand, I allow to re-render anytime they need since they don't have any other views relying upon them.
In a little more detail, for Parent views I like my initialize functions to do a few things:
Initialize my own view
Render my own view
Create and initialize any child views.
Assign each child view an element within my view (e.g. the InfoView would be assigned #info).
Step 1 is pretty self explanatory.
Step 2, the rendering, is done so that any elements the child views rely on already exist before I try to assign them. By doing this, I know all child events will be correctly set, and I can re-render their blocks as many times as I want without worrying about having to re-delegate anything. I do not actually render any child views here, I allow them to do that within their own initialization.
Steps 3 and 4 are actually handled at the same time as I pass el in while creating the child view. I like to pass an element in here as I feel the parent should determine where in its own view the child is allowed to put its content.
For rendering, I try to keep it pretty simple for Parent views. I want the render function to do nothing more than render the parent view. No event delegation, no rendering of child views, nothing. Just a simple render.
Sometimes this doesn't always work though. For instance in my example above, the #name element will need to be updated any time the name within the model changes. However, this block is part of the ParentView template and not handled by a dedicated Child view, so I work around that. I will create some sort of subRender function that only replaces the content of the #name element, and not have to trash the whole #parent element. This may seem like a hack, but I've really found it works better than having to worry about re-rendering the whole DOM and reattaching elements and such. If I really wanted to make it clean, I'd create a new Child view (similar to the InfoView) that would handle the #name block.
Now for Child views, the initialization is pretty similar to Parent views, just without the creation of any further Child views. So:
Initialize my view
Setup binds listening for any changes to the model I care about
Render my view
Child view rendering is also very simple, just render and set the content of my el. Again, no messing with delegation or anything like that.
Here is some example code of what my ParentView may look like:
var ParentView = Backbone.View.extend({
el: "#parent",
initialize: function() {
// Step 1, (init) I want to know anytime the name changes
this.model.bind("change:first_name", this.subRender, this);
this.model.bind("change:last_name", this.subRender, this);
// Step 2, render my own view
this.render();
// Step 3/4, create the children and assign elements
this.infoView = new InfoView({el: "#info", model: this.model});
this.phoneListView = new PhoneListView({el: "#phone_numbers", model: this.model});
},
render: function() {
// Render my template
this.$el.html(this.template());
// Render the name
this.subRender();
},
subRender: function() {
// Set our name block and only our name block
$("#name").html("Person: " + this.model.first_name + " " + this.model.last_name);
}
});
You can see my implementation of subRender here. By having changes bound to subRender instead of render, I don't have to worry about blasting away and rebuilding the whole block.
Here's example code for the InfoView block:
var InfoView = Backbone.View.extend({
initialize: function() {
// I want to re-render on changes
this.model.bind("change", this.render, this);
// Render
this.render();
},
render: function() {
// Just render my template
this.$el.html(this.template());
}
});
The binds are the important part here. By binding to my model, I never have to worry about manually calling render myself. If the model changes, this block will re-render itself without affecting any other views.
The PhoneListView will be similar to the ParentView, you'll just need a little more logic in both your initialization and render functions to handle collections. How you handle the collection is really up to you, but you'll at least need to be listening to the collection events and deciding how you want to render (append/remove, or just re-render the whole block). I personally like to append new views and remove old ones, not re-render the whole view.
The PhoneView will be almost identical to the InfoView, only listening to the model changes it cares about.
Hopefully this has helped a little, please let me know if anything is confusing or not detailed enough.
I'm not sure if this directly answers your question, but I think it's relevant:
http://lostechies.com/derickbailey/2011/10/11/backbone-js-getting-the-model-for-a-clicked-element/
The context in which I set up this article is different, of course, but I think the two solutions I offer, along with the pros and cons of each, should get you moving in the right direction.
To me it does not seem like the worst idea in the world to differentiate between the intital setup and subsequent setups of your views via some sort of flag. To make this clean and easy the flag should be added to your very own View which should extend the Backbone (Base) View.
Same as Derick I am not completely sure if this directly answers your question but I think it might be at least worth mentioning in this context.
Also see: Use of an Eventbus in Backbone
Kevin Peel gives a great answer - here's my tl;dr version:
initialize : function () {
//parent init stuff
this.render(); //ANSWER: RENDER THE PARENT BEFORE INITIALIZING THE CHILD!!
this.child = new Child();
},
I'm trying to avoid coupling between views like these. There are two ways I usually do:
Use a router
Basically, you let your router function initialize parent and child view. So the view has no knowledge of each other, but the router handles it all.
Passing the same el to both views
this.parent = new Parent({el: $('.container-placeholder')});
this.child = new Child({el: $('.container-placeholder')});
Both have knowledge of the same DOM, and you can order them anyway you want.
What I do is giving each children an identity (which Backbone has already done that for you: cid)
When Container does the rendering, using the 'cid' and 'tagName' generate a placeholder for every child, so in template the children has no idea about where it will be put by the Container.
<tagName id='cid'></tagName>
than you can using
Container.render()
Child.render();
this.$('#'+cid).replaceWith(child.$el);
// the rapalceWith in jquery will detach the element
// from the dom first, so we need re-delegateEvents here
child.delegateEvents();
no specified placeholder is needed, and Container only generate the placeholder rather than the children's DOM structure. Cotainer and Children are still generating own DOM elements and only once.
Here is a light weight mixin for creating and rendering subviews, which I think addresses all the issues in this thread:
https://github.com/rotundasoftware/backbone.subviews
The approach taken by this plug is create and render subviews after the first time the parent view is rendered. Then, on subsequent renders of the parent view, $.detach the subview elements, re-render the parent, then insert the subview elements in the appropriate places and re-render them. This way subviews objects are reused on subsequent renders, and there is no need to re-delegate events.
Note that the case of a collection view (where each model in the collection is represented with one subview) is quite different and merits its own discussion / solution I think. Best general solution I am aware of to that case is the CollectionView in Marionette.
EDIT: For the collection view case, you may also want to check out this more UI focused implementation, if you need selection of models based on clicks and / or dragging and dropping for reordering.

Categories

Resources