I understand from this question & answer that I can use KnockoutJS to bind 2 different view models to 2 different elements.
var viewModel1={...}
var viewModel2={...}
<div id="one"></div>
<div id="two"></div>
ko.applyBindings(viewModel1, document.getElementById('one'));
ko.applyBindings(viewModel2, document.getElementById('two'));
But, is there a way to bind 2 partial view models to the same element?
// This did not work for me
<div id='container'>
<span data-bind='text: firstname' />
<span data-bind='text: lastname' />
</div>
var partialVM1={
firstname:'John',
}
var partialVM2={
lastname:'Adams',
}
var container=document.getElementById('container');
ko.applyBindings(partialVM1,container);
ko.applyBindings(partialVM2,container);
Or alternatively, does KnockoutJS have a way of combining partial viewmodels into a single view model?
Prior Stackoverflow answers appear to say "no way", but I'm asking if there are there newly added Knockout options or if someone has a nice workaround for my dilemma.
// Pseudo-code:
var combinedVM = ko.combine(partialVM1,partialVM2);
ko.applyBindings(combinedVM,container);
I ask because the 2 partial view models are being created by separate processes (In Visual Studio, there are 2 different t4 scripts that create the 2 partial views separately).
If there is no easy workaround, I could refactor the 2 t4 scripts into a single script, but that would require a modest bit of rewiring (I'd rather avoid it, if possible).
Any other alternatives?
You could create a single view model and use data-binding with to change context.
Edit: jsFiddle
Example:
<div id='container'>
<div id='context1' data-bind='with: partial1'>
<span data-bind='text: firstname' />
</div>
<div id='context2' data-bind='with: partial2'>
<span data-bind='text: lastname' />
</div>
</div>
var partialVM1={
firstname:'John',
}
var partialVM2={
lastname:'Adams',
}
var combined = (function () {
function combinedVM() {
this.partial1 = ko.observable(partialVM1);
this.partial2 = ko.observable(partialVM2);
}
return combinedVM;
})();
var container=document.getElementById('container');
ko.applyBindings(combined,container);
Related
I am facing some issue. I have some nested controller within one parent controller and I need it to execute as per some condition using Angular.js. I am explaining my code below.
NABH.html:
<div ng-controller=NABHParentController>
<div ng-show="showNabh">
<div ng-include="'nabh1.html'"></div>
</div>
<div ng-show="showNabh1">
<div ng-include="'nabh2.html'"></div>
</div>
</div>
nabh1.html:
<div class="right_panel" style="display:block;" id="auditnabh" ng-controller="NABHController">
<td class="sticky-cell" ng-click="initiateNABH(nabh.NABHAuditID)">
</td>
</div>
nabh2.html:
<div class="right_panel" ng-controller="NABH2Controller">
<h2 class="page-title">NABH (INT012017001)</h2>
<div>
NABHParentController.js:
var app=angular.module('demo');
app.controller('NABHParentController',function($scope,$http,$state,$window,$location,$filter){
$scope.showNabh=true;
$scope.showNabh1=false;
})
NABHController.js:
var app=angular.module('demo');
app.controller('NABHController',function($scope,$http,$state,$window,$location,$filter,getBothIdToAuditDetailPage)
{
$scope.initiateNABH = function(aid) {
$scope.$parent.$parent.showNabh=false;
$scope.$parent.$parent.showNabh1=true;
}
})
Here Initially all controller are loading and nabh1.html is displaying first. When user will click on that td click event the second part html is showing. Here I need when user will click on that ng-click="initiateNABH(nabh.NABHAuditID)" the second view will open and the resepective controller will start execute. Initially only displaying view related controller will execute. Please help.
It sounds like using ng-if instead of ng-show will solve your problem:
<div ng-if="showNabh">
<div ng-include="'nabh1.html'"></div>
</div>
<div ng-if="showNabh1">
<div ng-include="'nabh2.html'"></div>
</div>
The difference is that while ng-show will "only" hide the element using css when the expression is falsy, ng-if will not create the element if it's falsy and as a result will not initiate the controller until ng-if is truthy.
Also, I would probably move the initiateNABH function to the parent controller - it will still be available in the child controller but makes the code less likely to break since you don't have to use $parent:
var app=angular.module('demo');
app.controller('NABHParentController',function($scope,$http,$state,$window,$location,$filter){
$scope.showNabh=true;
$scope.showNabh1=false;
$scope.initiateNABH = function(aid) {
$scope.showNabh=false;
$scope.showNabh1=true;
}
})
Alright, newbie Knockout question here.
In this example: http://learn.knockoutjs.com/WebmailExampleStandalone.html#Inbox
How does the mail detail view replace the folder list view?
That is, what feature causes the divs to be toggled? In inspecting the dom, I see that what happens is the div's are actually rendered empty when not displayed.
Can someone enlighten me? I know this is rather basic, but its the last piece I need to click into place for my understanding.
Just to be 100% clear: when you click on a row in the folder list, what causes the folder view to be emptied and the mail detail to display? Is it the with binding? (That doesn't seem right.)
You are on the right track with the with binding: in this example the views are changed using the with binding relaying on this feature of the binding:
The with binding will dynamically add or remove descendant elements depending on whether the associated value is null/undefined or not
So in the viewmodel code you will see something like this:
this.get('#:folder', function () {
self.chosenFolderId(this.params.folder);
self.chosenMailData(null);
$.get("/mail", { folder: this.params.folder }, self.chosenFolderData);
});
this.get('#:folder/:mailId', function () {
self.chosenFolderId(this.params.folder);
self.chosenFolderData(null);
$.get("/mail", { mailId: this.params.mailId }, self.chosenMailData);
});
So the functions which are "chaining" the view nulls out one of the properties while filling in the other which toggles the views defined as:
<!-- Chosen mail -->
<div class="viewMail" data-bind="with: chosenMailData">
...
<div/>
<!-- Mails grid -->
<table class="mails" data-bind="with: chosenFolderData">
</table>
This is not the nicest solution but don't forget that Knockout is a Databind/MVVM library and not a full blown SPA framework so it does not have concepts for layouting and higher level view composition.
However this could be made nicer with using the template binding:
<div id="mainView" data-bind="{template: {name: templateName, data: activeView}}">
</div>
And turning the views into templates:
<script type="text/html" id="ChosenMail">
<div class="viewMail">
...
<div/>
</script>
<script type="text/html" id="MailsGrid">
<table class="mails">
...
</table>
</script>
And in the routing only set the activeView property and lookup the corresponding template name for it:
this.get('#:folder', function () {
$.get("/mail", { folder: this.params.folder }, function(data) {
self.activeView(data);
self.templateName('ChosenMail');
});;
});
this.get('#:folder/:mailId', function () {
$.get("/mail", { mailId: this.params.mailId }, function(data) {
self.activeView(data);
self.templateName('MailsGrid');
});
});
But because this is quite much manual and error prone work I would use something like Durandal.js which is a real SPA framework and it was designed for this kind scenarios.
That is just a Demo on a light weight SPA scenario, with binding is just a inline template binding. Not very useful for a dynamic SPA. Like Nemesv suggests use the template binding.
The problem with the template binding is that its very verbose to use, I have addressed this in my Binding convention library (One of many features)
Instead of doing
<div id="mainView" data-bind="{template: {name: templateName, data: activeView}}">
</div>
You do
<div id="mainView" data-name="activeView">
</div>
My library will do the rest, check out the wiki on templates here
https://github.com/AndersMalmgren/Knockout.BindingConventions/wiki/Template-convention
And a little fiddle
http://jsfiddle.net/xJL7u/11/
I'm building an interface with a lot of toggles to control what data is being filtered in a different part of an App's search results. Here is a codepen of it: Here
Coming from a jQuery/Backbone background, what is the most Angular way of toggling the 'active' state of any/all of the filter items? Essentially, almost any <li> tag presented here is a toggle-able feature.
In jQuery, I would put a listener on the view and wait for any click events to bubble up and toggle an 'active' class on the event.target. I want to do it the best way with Angular.
(Also, this is my first Angular project.. I am probably doing all sorts of things the wrong way. Apologies in advance.)
Edit: To clarify the question, I have an App Interface with 20+ possible filter attributes to control a separate module on the page. Every time someone toggles one of these filter attributes, I want to add/remove an 'active' class. Do I put an 'ng-click="function(...)"' in the ng-repeat for each controller? Or is there an easier way to manage this module-wide behavior (a la event bubbling, like in Backbone/jQuery) ?
Thanks!
You can do something like this:
<section ng-init="active = 'areaFoo'">
<div ng-class="{active:active == 'areaFoo'}" ng-click="active = 'areaFoo'"></div>
<div ng-class="{active:active == 'areaBar'}" ng-click="active = 'areaBar'"></div>
</section>
It will populate $scope.active for you, and is very angular as it leverages existing directives, manages the state on scope, and does not leverage dom api's or events outside of directives. There is really no need to involve the controller here, as its display logic.
Learn more about ng-class here.
Multiple active elements
<section>
<div ng-class="{active:areaFoo}" ng-init="areaFoo = true">
<button ng-click="areaFoo = true">activate</button>
<button ng-click="areaFoo = false">de activate</button>
</div>
<div ng-class="{active:areaBar}" ng-init="areaBar = false">
<button ng-click="areaBar = true">activate</button>
<button ng-click="areaBar = false">de activate</button>
</div>
<div ng-class="{active:areaBar}" ng-init="areaBaz = false">
<button ng-click="areaBaz = true">activate</button>
<button ng-click="areaBaz = false">de activate</button>
</div>
</section>
you could also toggle with something like this ng-click="areaFoo = !areaFoo"
I was able to come up with a solution I'm ok with, for anyone curious you can see a demo Here.
Here are the relevant code snippets:
<li ng-repeat='category in data' ng-class='{active: category.isActive}' ng-click='toggleActive(category)' >
<span class='solr-facets-filter-title'>{{category.catTitle}}</span>
<span class='solr-facets-filter-count'>{{category.catResults}}</span>
</li>
An ng-click calls a method on the Controller, toggleActive(category). The current data model gets sent to the method. In the JS:
$scope.toggleActive = function(category){
category.isActive = !category.isActive;
}
The function returns the opposite of the isActive attribute back to the li in question: an ng-class adds the active class for a truthy state of isActive.
I'm not a huge fan of how I have to adjust the data model with flags for active/inactive states like this, but it ends up working out for the best in this case. I can push those isActive states back to the $scope so that other parts of the App can run queries based on that information.
Suppose i have a main view that contains
A bookable item
A shopping cart
Both elements are in the same page, each of them has its own VM, like this:
<div id='page'>
<div id='item'>
<span data-bind='text: item().name'></span>
<span data-bind='text: item().price'></span> EUR
<!-- What to bind on this click handler? -->
<button>Add</button>
</div>
<hr>
<div id='cart'>
You have 0 items in your cart.
</div>
</div>
Javascript
function ItemVM() {
var self = this;
self.item = ko.observable({id: 1, name:'test', price: 3.99});
}
function CartVM() {
var self = this;
// Adds an item to cart.
self.add = function(item) {
// Business logic here
}
// And so on, other methods here.
self.remove = function(item) {}
self.checkout = function() {}
}
ko.applyBindings(new ItemVM(), document.getElementById('item'));
ko.applyBindings(new CartVM(), document.getElementById('cart'));
I have 2 questions.
1) How to use a click handler, within the 'item' context, that is defined elsewhere? In other words, how to make the button use CartVM.add() as the click handler?
2) Is there something wrong i am doing in reference to KO or MVVM itself?
Fiddle Here
The idea of the MVVM pattern is to have a view bound to a single viewModel. Then you will have data objects described in the model.
In the situtation you have I tend to favour composition. So if I have a view composed of functional elements I tend to compose the viewModel of these separate elements.
I do find this overall is easier. It's likely the container VM will have some elements that are actually part of the individual page. It's probably hard to follow if there are a lot of individual viewModels that are part of the page. The other issue is that cart is coupled to one container div. In the case of cart this may be acceptable. In other cases your functional component may have view elements which may be hard to contain under one div so it becomes hard to segregate viewModels like this.
I've amended your design with:
Note I add a call to add in the itemVM as shown.
function ItemVM() {
this.add = function(data, e) {
viewModel.CartVM.add(data);
};
}
function VM() {
this.ItemVM = new ItemVM();
this.CartVM = new CartVM();
}
var viewModel = new VM();
ko.applyBinding(viewModel);
fiddle to illustrate this here: http://jsfiddle.net/q8uWW/4/
HTH
An example using my binding convention library, it makes it easy to work with multiple view models. https://github.com/AndersMalmgren/Knockout.BindingConventions
To communicate between models you can use a Event Aggregate pattern, I have one in a library called SignalR.EventAggregatorProxy, if you have no use for SignalR you can extract the eventaggregatorn part. https://github.com/AndersMalmgren/SignalR.EventAggregatorProxy/
The idea with my convention library is to use template bindings for each view model. Like
<div id='page'>
<div id='item' data-name="item"></div>
<hr>
<div id='cart' data-name="cart"></div>
</div>
<script id="ItemView" type="text/html">
<span data-name='name'></span>
<span data-name='price'></span> EUR
<button data-name="add">Add</button>
</script>
<script id="CartView" type="text/html">
You have <span data-name="count"></span> items in your cart.
</script>
Fiddle
http://jsfiddle.net/hL5rY/
I have several regions with repeatable content which is generated on the server-side. I use knockout-js to dynamically hide/show regions within areas. My markup is like the following:
<div>
<input type="checkbox" data-bind="checked: a1" />
<div data-bind="visible: a1">region0</div>
</div>
<div>
<input type="checkbox" data-bind="checked: a2" />
<div data-bind="visible: a2">region1</div>
</div>
<script>
var viewModel = {
a1: ko.observable(false),
a2: ko.observable(false)
};
ko.applyBindings(viewModel);
</script>
Lets say I have 10 such regions. Is there a more convenient/better way to achieve the same?
Lets say, that I explicitly do not want to use foreach binding and generate markup on the client (for site to be accessible with disabled js).
Is there any way to omit viewModel specification (part within script tags), since it feels to me that knockout could detect and auto-create fields for me?
P.S. I'm a JS-novice, so excuse me for simple questions :)
Is there any way to omit viewModel specification (part within script tags), since it feels to me that knockout could detect and auto-create fields for me?
Although I prefer Knockout personally, you might want to take a look at Angular. Angular does automatically create view model properties as this example shows.