Knockout doesn't use supplied view model instance - javascript

I have a knockout component that I add to the page. I am trying to call ko.applyBindings with an instance of the view model that I created. But knockout seems to ignore it and create its own instance.
Code:
ko.components.register("my-component", {viewModel: MyViewModel, template: "....."});
ko.applyBindings(new MyViewModel(this.config), document.getElementsByTagName("my-component")[0]);
I have a console.log in the constructor of MyViewModel and I am seeing that two instances are created: One with the parameters I pass and one without. And knockout seems to be using the one without.
What am I doing wrong?

Components actually have their own view models by design. You can, however, pass them an instance of a viewmodel you've created earlier:
var myViewModel = new MyViewModel(this.config);
ko.components.register("my-component", { viewModel: { instance: myViewModel }});
ko.applyBindings(myViewModel);

Related

Using Backbone.Marionette, why can I not reference #ui elements when creating a new instance of a view?

I have created a simple view, MyView, that extends from ItemView. Then when I create the instance of MyView, I am trying to add references to UI elements within the view as well as events that use those UI elements.
HTML
<div id="container"></div>
<script type="text/template" id="my-template">
<p>This is a rendered template.</p>
<button data-ui="changeModelNameButton">Change Model Name</button>
</script>
JS
// Define a custom view that extends off of ItemView
var MyView = Marionette.ItemView.extend({
template: "#my-template"
});
// Instantiate the custom view we defined above
var view = new MyView({
el: "#container",
ui: {
changeModelNameButton: '[data-ui~=changeModelNameButton]'
},
events: {
'click #ui.changeModelNameButton': function() {
alert('here');
}
}
});
// Render the view in the element defined within the custom view instantiation method
view.render();
I am receiving the following error in the console:
Uncaught TypeError: Cannot read property 'changeModelNameButton' of
undefined
I noticed that if I move the ui declarations to the view definition, it works fine, but I would like to know why I can't add those when I create the instance. Is there no way to add them to the instance or am I missing something?
Note: I am using Backbone 1.3.3, Marionette 2.4.4, Underscore 1.8.3, and jQuery 3.1.1
Options overriding view's properties
Not all the options are automatically overriding the view class properties when passed on instantiation.
ui looks like it's not one of them.
Backbone will automatically apply the following (private) viewOptions on a view:
// List of view options to be set as properties.
var viewOptions = [
'model',
'collection',
'el',
'id',
'attributes',
'className',
'tagName',
'events'
];
In the view constructor, this is extended with the chosen options (source):
_.extend(this, _.pick(options, viewOptions));
How to pass the ui option?
You either need to put the ui hash in the view class definition, or to apply the ui hash yourself.
var MyView = Marionette.ItemView.extend({
template: "#my-template",
initialize: function(options) {
// merge the received options with the default `ui` of this view.
this.ui = _.extend({}, this.ui, this.options.ui);
}
});
Note that options passed to a view are available in this.options automatically.
What's the real goal behind this?
If you're going to mess around the ui and the events with its callbacks, it would be best to define a new view.
It looks like a XY problem where the real problem lies in the architecture of the app, but we can't help since you're asking about what's blocking you right now.

model not an instance of a model, but has attributes of a particular instance

The best way to think of the problem is to consider it in terms of the Backbone TodoMVC app (even though that's not what I'm doing, it's similar in that it has one main view and many list views). If you declare a custom destroy method on the Todo model (such as one I have below) and click on a view to delete it, then in the method that's triggered (where this.model.destroy is called), you can't invoke the custom destroy method on the model, because this.model is not an instance of the model. Somehow calling this.model.destroy
clear: function () { //in the todo app, this method is called clear
this.model.destroy();
}
works to destroy the model but not if you're trying to invoke a custom destroy method.
Below, I explain the same in terms of my app.
I have a Backbone application that uses server side storage but am unable to send a delete request to the server. Using another SO answer, I created a custom destroy method on the model (FunkyModel shown below) and I try to call that custom destroy method in a deleteModel method in the view but to no avail.
However, it's not calling the custom destroy method on FunkyModel.When I inspect this.model, it doesn't say it's an instance of FunkyModel, rather it only says Backbone.Model and then lists the model's properties. So obviously it's not an instance of FunkyModel and therefore not a surprise that it can't call the custom destroy method on FunkyModel, but at the same time under the attributes property, it has all the attributes of an instance of FunkyModel.
Question: how can a model have attributes that are unique to an instance of a model but not be an instance of that model, but rather simply Backbone.Model
Further Info:
The application is structured like the BackboneMVC Todo App in that there is one main view and then it attaches more views as list items. When I click on a delete symbol on one of the views, it triggers an event that calls the deleteModel method, in which I call
deleteModel(e){
this.model.destroy();
}
So naturally, depending on which list view I click on, the attributes of this.model will be different, but in no case is it ever an instance of the model. It's simply a Backbone.Model and its attributes have the properties of a particular instance. I'm not sure how a model that's not an instance can have properties in its attributes that are from a particular instance.
model
export class FunkyModel extends Model{
defaults(){
return {
name: '',
type: ''
}
}
addToCollection(){
collection.add(this);
this.save();
}
destroy(options){
var opts = _.extend({url: '/api/'}, options || {});
console.log("never getting run");
return Backbone.Model.prototype.destroy.call(this, opts);
}
}
view
deleteModel(e){
this.model.destroy({success: function(model, response, xhr){
console.log(model,xhr, options, "success callback") //xhr is undefined
},
error: function(model, xhr,options){
console.log(model,xhr,options, "error callback")
}
}
Further Info
When the model's created, I do
this.model = new FunkyModel();
this.model.set({"type": "funky"})
When the model gets saved, it gets added to the collection (code shown above in original OP). I'm wondering if when I call this.model.destroy(), it's only destroying it in terms of removing it from the collection (hence the success callback is fired).

How to modelEvents work in Marionette.CompositeView

I started to learn Marionette.View concept.for this I created model with 1 attribute and assign default value.
I have dropdown list in my html file. Whenever it changes it triggers a function name as myfunction.Inside myFunction I changed model attribute value.
Whenever attribute value changes automatically it triggers another function.that event I written inside Marionette.CompositeView.but it's not working.
Earlier I did same thing using myModel.on there it's working fine.But it's not working modelEvents in Marionette.CompositeView.
let's check the following code.
var mymodel=Backbone.Model.extend({
defaults:{
"name":"oldValue"
}
});
var mymodelObj=new mymodel();
//marionette.View code
var backView=Backbone.Marionette.CompositeView.extend({
events:{
"change #pop1":"myFunction"
},
myFunction:function(){
mymodelObj.set({name:"updatedValue"})
},
modelEvents:{
"change:name":"myFunction1"
},
myFunction1:function(){
alert("Hi");
}
});
//creating instance
var backViewObj=new backView({el:$("#sidebar")});
How can I fix this.
Right Now I am trying to understanding where Marionette.js useful in my Backbone Applications.
If I am not mistaken, model is not attached to the view you have created. For modelEvents to be triggered, there should be a model attribute in CompositeView. For this you should specify the model during initialization of CompositeView;
var backViewObj=new backView({el:$("#sidebar"), model : mymodelObj});
To do this though you need to pass the model in when creating the backView like so:
var backViewObj = new backView({ el:$("#sidebar"), model: myModel });
Marionette accomplishes this by overriding delegateEvents which Backbone.View calls in it's constructor and delegating your modelEvents object to the model:
Marionette.bindEntityEvents(this, this.model, Marionette.getOption(this, "modelEvents"));
Based on your comments above I think that you're unclear about the different types of views that Backbone.Marionette provides as you are asking how to pass more model instances. I also was unclear when I began using Marionette.
Marionette.ItemView represents a single model.
Marionette.CollectionView represents a collection and renders a Marionette.ItemView for every model in the collection. It does not have the ability to render a template.
Marionette.CompositeView represents both a model and a collection (leaf and branch in a tree structure). This is for recursive data structures although you can use a CompositeView to render a template (like a table header, etc.) and use itemViewContainer to place the itemViews in a specific element.
If you want your CompositeView to be able to render multiple models you'll need to pass a collection of models into the CompositeView and not one single model:

In Backbone, how do I have an after_render() on all views?

I am maintaining a javascript application and I would like there to be a jquery function invoked on pretty much every view. It would go something like this:
SomeView = Backbone.Marionette.ItemView.extend
initialize: ->
#on( 'render', #after_render )
after_render: ->
this.$el.fadeOut().fadeIn()
Clearly there is a better way to do this than have an after_render() in each view? What is the better way to do it? If you can give an answer that includes jasmine tests, I'll <3 you ;)
The event you are looking for is onDomRefresh. See here for the documentation:
https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.view.md#view-domrefresh--ondomrefresh-event
Create your own base view class and put your afterRender code in it. When you create a view, inherit from this class.
var MyApp.ItemView = Backbone.Marionette.ItemView.extend({
afterRender: function() {
// This will be called after rendering every inheriting view.
}
});
var SpecificItemView = MyApp.ItemView.extend({
// this view will automatically inherit the afterRender code.
});
In general, it seems to be considered good practice to define your own base views for all 3 view types. It will enable you to easily add global functionality later.
There is a common pattern used across all Backbone frameworks, normally they have a render method which in turn calls beforeRender, renderTemplate and afterRender methods.
render:function(){
this.beforeRender();
this.renderTemplate();// method names are just indicative
this.afterRender();
return this;
}
In your Base view you can have these methods to be empty functions, and implement them wherever you want it. Not sure this answer applies to Marionette
Combining thibaut's and Robert Levy's answer, the correct solution would be:
var baseView = Backbone.Marionette.ItemView.extend({
onDomRefresh: function() {
// This will be triggered after the view has been rendered, has been shown in the DOM via a Marionette.Region, and has been re-rendered
// if you want to manipulate the dom element of the view, access it via this.$el or this.$('#some-child-selector')
}
});
var SpecificItemView = baseView.extend({
// this view will automatically inherit the onDomRefresh code.
});

When specifying the model when creating a view, does it have to be referenced to a specific model?

For instance:
assume that BackBoneModel is a model that i've created and extended to Backbone.Model
var specificModel = new BackBoneModel;
var view = new BackBoneView({model: SpecificModel});
or:
var view = new BackBoneView({model: BackBoneModel});
I am having trouble understanding whether, when creating a view and attaching a model, it is required that I attach an model object I've instantiated or the actual model itself. Thanks for your help.
The short anwser is that you should be using a specific instance. When a view is tied to a specific model it contains a reference to that model and you can then reference that models attributes, and further bind to it's attributes so that changes to them cause the view to rerender.
Not all view's need to have a model specified but if you do want it then doing it this way is (that is when you create the view) is a shortcut to specifying it afterwards so instead of
var view = new BackBoneView();
view.model =specificModel;
You just use
var view = new BackBoneView({model: BackBoneModel});
Should have just had a simple test before posting to Stack. The answer is the first option, where the specific instantiated model must stated in the view.

Categories