So with the introduction of components and custom elements it seems a lot easier to encapsulate your logic and markup, however I am a bit unsure how to make use of components within foreach sections when you need to pass in a viewmodel.
So my current scenario is that I have a view model which looks something like:
function SomePartialViewModel()
{
this.Name = ko.observable();
this.Score = ko.observable();
this.SomeDate = ko.observable();
}
function SomeViewModel()
{
this.DataFromWebServiceCall = ko.observableArray();
this.GetDataFromServer = function(){
// get some data from service and populate DataFromWebSeriviceCall with instances of SomePartialViewModel
}
}
Now in the above we have a POJO to contain partial data, and we have a main view model for the view which will contact a web service or something, and populate its array with instances of the partial. Then this would currently be used like so:
<div id="partial-data" data-bind="template: { name: 'partial-view', foreach: DataFromWebServiceCall }"></div>
<div class="partial-view">
<label data-bind="text: Name"></label>
<label data-bind="text: Score"></label>
<label data-bind="text: SomeDate"></label>
</div>
Now assume the .partial-view is in a script tag with correct template name etc and is a correct template, then the #partial-data is in the main view and wants to display all the instances on the page. Now currently it all works, but I would like to move to a more component based model, and currently we can see that the template relies upon the SomePartialViewModel data, so we have our template and our viewmodel for that component, however the problem is around getting the viewmodel into the component, as currently you register the component at setup time, then you use params to populate chunks of it. However in this case I want to pass in the viewmodel to the component at binding time...
So I was wondering how I can go about doing this, as I imagine I could register the component with a template but no viewmodel, but is there the notion of a data style binding where I can set the $data property and move to a foreach from a template binding on the view?
Hopefully the problem I am trying to solve can be seen and any info would be great.
There are loads of ways to pass values and/or viewmodels to components using the params.
If you use the createViewModel method, you can just pass in the viewmodel via the params and use the partial viewmodel as the component viewmodel:
ko.components.register("partial-view", {
viewModel: {
createViewModel: function (params) {
return params.value;
}
},
template: "<div>Partial View for <b data-bind='text: Name'></b></div>"
});
You can see a working example in this fiddle: http://jsfiddle.net/Quango/fn1ymf9w/
You can define viewModels under viewModels :)
just like defining an observable you can define another viewModel and using "with" binding you can create a component based model you desire.
First you create your components and sub-Components and sub-sub-sub-Components etc viewModels seperately.
var SomePartialViewModel = function()
{
this.Name = ko.observable();
this.Score = ko.observable();
this.SomeDate = ko.observable();
}
var SomeViewModel = function()
{
this.DataFromWebServiceCall = ko.observableArray();
this.GetDataFromServer = function(){
// get some data from service and populate DataFromWebSeriviceCall with instances of SomePartialViewModel
}
this.SPM = new SomePartialViewModel(); // partial-1
}
And then you create a MainViewModel and bind all the main elements here.
var MainViewModel = function() {
var self = this;
self.SVM = new SomeViewModel();
self.SPM = new SomePartialViewModel(); // partial-2
}
ko.applyBindings(new MainViewModel());
then in your html you can create your components obeying the context you created on knockout entities
...
<body>
<div data-bind="with: SVM">
....
<div data-bind="with: SPM">
<!-- partial-1 data -->
</div>
...
</div>
<div data-bind="with: SPM">
<!-- partial-2 data -->
</div>
</body>
...
You may want to create seperate files for your component models and using a modular script loader like Require js you can bind all together to a complete component based knockout web application
Related
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);
I have seen many a times that a web page has multiple templates for a specific region of the page, one of which is loaded depending on the scenario. Typically a corresponding VM is instantiated at that point and bound to the template.
Does this design pattern not violate OOP principles in that the objects (VMs in this case) should be based on the functional entities that actually exist and not necessarily based on a specific area of the UI.
Lemme substantiate this with an example. Lets say we have a web page dealing with online sales of clothes, shoes and furniture. Once a selection in made among the three in a particular part of the page, a separate template is loaded in another part of the page which is dependent on the selection made. Should my object model be ItemVM<-ShoeVM,ClothesVM,FurntitureVM as per OOP rules, or should it be ParentVM containing ItemSelectorVM (for the UI to select the item type), shoeAdverisementVM (to bind to the shoe template loaded as a result), FurnitureAdvertisementVM and many more VMs for every part of the page?
When you say OOP principles, I'm assuming you are referring to SOLID.
View Models model the UI and do not have to be coupled to a single template. You may use a view model with a template that has been created to target desktops and then reuse that view model with a template that has been created to target mobiles.
Should my object model be ItemVM<-ShoeVM,ClothesVM,FurntitureVM as per
OOP rules, or should it be ParentVM containing ItemSelectorVM (for the
UI to select the item type), shoeAdverisementVM (to bind to the shoe
template loaded as a result), FurnitureAdvertisementVM and many more
VMs for every part of the page?
This depends on the complexity of the application and domain.
Let's assume that your UI is doing more than just iterating over an ObservableArray<T> where T could be of type Shoe or Furniture. And let's also assume that ShoeAdvertisementVM is not some decorator of ItemVM<T>.
Now, lets say you have FurnitureAdvertisementVM and ShoeAdverisementVM that look like this:
function FurnitureAdvertisementVM(furniture){
this.items = ko.observableArray(furniture);
this.doSomething = function(){
// doing something
}
}
function ShoeAdverisementVM(shoes){
this.items = ko.observableArray(shoes);
this.doSomething = function(){
// doing something
}
}
var shoeVM = new ShoeAdverisementVM(shoes);
var furnitureVM = new FurnitureAdverisementVM(furniture);
If the two view models are pretty much a copy and paste job and the only difference is the name of the view model and the type of the items that go into the collection then it's probably worth changing the implementation into something generic like this:
function ItemsVM(items){
this.items = ko.observableArray(items);
this.doSomething = function(){
// doing something
}
}
var shoeVM = new ItemsM(shoes);
var furnitureVM = new ItemsVM(furniture);
But if each view model has some very specific properties and functions, for example:
function FurnitureAdvertisementVM(furniture, someOtherDependency){
this.items = ko.observableArray(furniture);
this.doSomething = function(){
// doing something
}
this.propertyIsBoundToATextbox = ko.observable();
this.clickHandlerUsesDependency = function(ctx){
if(ctx.SomeCondition){
someOtherDependency.doSomething();
}
}
}
function ShoeAdverisementVM(shoes){
this.items = ko.observableArray(shoes);
this.doSomething = function(){
// doing something
}
this.propertyIsBoundToACheckbox = ko.observable();
}
Then it it is not as clear cut as making both view models generic.
Roy J commented:
your viewmodels are intended to model the view (the UI)
Which is so true, so if the views have different requirements, i.e. one has 3 checkboxes the other has loads of buttons etc, then you are likely going to need different view models.
If you want to factor out some of common functionality into separate classes then you can:
function ItemService(items){
this.items = ko.observableArray(items);
this.doSomething = function(){
// doing something
}
}
function FurnitureAdvertisementVM(itemService, someOtherDependency){
this.service = itemService;
this.propertyIsBoundToATextbox = ko.observable();
this.clickHandlerUsesDependency = function(ctx){
if(ctx.SomeCondition){
someOtherDependency.doSomething();
}
}
}
function ShoeAdverisementVM(itemService){
this.service = itemService;
this.propertyIsBoundToACheckbox = ko.observable();
}
I'm really just starting into knockout and so far it is pretty impressive. With that, there is also an issue I am running into.
I'm trying to take a list of binded objects and when on is clicked, I want it take that model and bind it to another view for further editing, no issues there. The problem I run into is that I get an error saying I cannot bind multiple times to the same element. I have read the ko.cleanNode([domElement]) can be used to circumvent it, but that is really meant to be used internally. I have done some searching and people mention you should use with but I don't think that applies to what I want to do.
Is there a way to apply the bindings but then just update the underlying model context?
Simplified jsfiddle example. My js code starts on like 119, above it the ko mapping plugin.
First, use the external resources to add external resources to your fiddle rather than pasting them into your fiddle.
Second, on your problem, the easiest way to do something like this is to have a SelectedItem property in your view model and bind the child view to it. Something like this:
var MyViewModel = function() {
var self = this;
self.MyItems = ko.observableArray();
self.SelectedItem = ko.observable();
}
var MyChildViewModel = function() {
var self = this;
self.SomeProperty = ko.observable();
}
Now populate MyItems with instances of MyChildViewModel and set SelectedItem to whichever item in MyItems you want to have as your child and bind the child view to SelectedItem:
<div id="theChildView" data-bind="with: SelectedItem">
<span data-bind="text: SomeProperty"></span>
</div>
I'm using backbone js for handling my views and models but I wish to render the templates using Html.EditorFor in ASP.NET MVC. This is because my forms are dynamically created based on a C# class. I have only tried underscore for JavaScript templating but it requires markup in the value field like this <%=heading%> and that is not an option for me. I need a template engine that can map my form using the name of each form component, or if there are some other view engine that can render the same markup that works for both the server and the js template engine.
UPDATE
In my view I'm using Html.EditorFor like this:
#foreach (var type in Html.GetAvailablePageModels()) {
var content = Activator.CreateInstance(type) as IContent;
<script id="view-template-#type.Name" type="text/html">
#using (Html.BeginForm()) {
#Html.EditorFor(x => content)
<input type="submit" value="Save"/>
}
</script>
}
Then in my backbone view I'm doing something like this:
var PageModel = Backbone.Model.extend({
urlRoot: '/api/page'
});
var page = new PageModel({ id: 'articles/85' });
page.fetch();
var EditView = Backbone.View.extend({
el: $('div#main'),
initialize: function () {
this.template = _.template($('#view-template-Article').html());
this.render();
},
render: function () {
$(this.el).html(this.template(this.model.toJSON())); // <-- set the values correct in my pre rendered form
return this;
}
});
window.editView = new EditView({ model: page });
In the above code where I bind the model to the template I need to make sure that the field called heading binds to the correct form field with name="heading".
Change the way underscore templates work:
_.templateSettings = {
interpolate : /\{\{([\s\S]+?)\}\}/g
};
Now you can write HTML templates like this:
This is a message: {{message}}
Instead of the old-school <%= message %> style. This will let you generate the templates you want from your Razor views on the server. I did this on a project once and it worked out really well.
Nested components presuppose nested view models also.
However, in the sample components, I don't see this kinda of dependency to appear (except the BackboneJS TODO app which is not very clear for a KO user).
Could you elaborate on how to do such a design, e.g. for a collection:
ItemViewModel with properties Name and IsSelected
CollectionViewModel with has an Items property that contains a collection of ItemViewModel and SelectedCount which is computed by counting how many items are selected. (I know this can be done with KO in a simpler way, but for the sake of illustration.
Viewmodels (VMs) are just objects (that get bound using ko.applyBindings()) - which means you can arbitrarily nest VMs into a parent object (what #Hasith said). You only need to pass one parent object back to Boilerplate. Bear with some ultra commented code:
// Let's assume you have your items nicely formatted in an array
// data source (and technically the objects in this array can be
// considered 'dumb' viewmodels)
var items = [
{Name:'a',isSelected:false},
{Name:'b',isSelected:true}
]
// We can apply some bindings to make 'smarter' viewmodels
// One approach is just to map using rocking KO mapping plugin
var koMapItems = ko.mapping.fromJS( items )
// You could skip the mapping plugin and go for broke
// creating your own VM mappings manually
// (you could do this using the mapping plugin with less work)
var goforbrokeVM = function( item )
{
var _name = ko.observable( item.Name )
var _dance = function() { return _name+' is space monkey' }
return {
Name: _name,
isSelected: ko.observable( item.isSelected ),
spaceMonkey: _dance
}
}
// Don't forget to assign and create your goforbrokeVMs
var epicItemVMs = []
for (var i=0;i<items.length;i++)
epicItemVMs.push( new goforbrokeVM( items[i]) )
// Now the interesting part, lets create a 'child' VM that
// we can embed in another VM. Notice this VM has as a
// property an array of VMs itself.
var childVM = {
elements: epicItemVMs,
// A sub method, because we can
otherMethod: function() { return 'wat' }
}
// And ultimately our 'master' VM with its own properties
// including the nested child VM we setup above (which you'll
// remember contains its own sub VMs)
var ParentVM = {
// And its own property
parentPropA: ko.observable('whatever'),
// Oooow look, a list of alternative ko.mapping VMs
simpleMappedItems: koMapItems,
// And a nested VM with its own nested goforbrokeVMs
nested: childVM
}
// Apply your master viewmodel to the appropriate DOM el.
ko.applyBindings( ParentVM, document.getElementById('items'))
And your HTML:
<div id="items">
<span data-bind="text: parentPropA"></span>
<!-- Step through one set of items in ParentVM -->
<div data-bind="foreach: simpleMappedItems">
<input type="checkbox" data-bind="checked: isSelected">
<span data-bind="text: Name"></span>
</div>
<!-- Or jump into the nested models and go crazy -->
<!-- ko with: nested -->
<div data-bind="foreach:elements">
<div data-bind="text: spaceMonkey"></div>
</div>
<div data-bind="text: otherMethod"></div>
<!-- /ko -->
</div>
In this way you can pass a single object (in this case ParentVM) to Boilerplate with as many nested viewmodels as you need.
Info for mapping plugin for knockout lives here: http://knockoutjs.com/documentation/plugins-mapping.html
The 'todo' sample is done by adopting Addy Osmani's implementation.
There is an implementation of knockoutjs also here.