How to exchange value between two viewmodels in knockout js - javascript

Hi i have two view models and i want to transfer a value between them.
here is js fiddle
`http://jsfiddle.net/sohimohit/e84u8L85/`
i want when a user click on show another div button then corresponding item name should be display on another div . means i want to show one viewmodel value into another.
secondly when a user click on show another div button another div appears i want an option of cancel so that user can go back to firstdiv. how can i achieve this.

You can use global variables container1VM and container2VM for it.
E.g. call of
container1VM.isVisible(!container1VM.isVisible());
container2VM.isVisible(!container2VM.isVisible());
will make visible container - invisible.
JSFiddle DEMO
Code:
HTML:
<div id="container1">
<div data-bind="visible: !isVisible()">
<ul >
<li >Container1 item</li>
<!-- ko foreach: myItems -->
<li>Item <span data-bind="text: $data"></span></li>
<button data-bind="click:$root.showDiv">show another div</button>
<!-- /ko -->
</ul>
</div>
</div>
<div id="container2" data-bind="visible:isVisible">
<ul>
<li >Container2 item</li>
<!-- ko foreach: myItems -->
<li>Item <span data-bind="text: $data"></span></li>
<!-- /ko -->
</ul>
<button data-bind="click:$root.cancel">cancel</button>
</div>
Javascript:
function Container1ViewModel()
{
var self = this;
self.isVisible = ko.observable(false);
self.showDiv = changeVisibility;
self.myItems = ko.observableArray();
self.myItems.push("ABC");
self.myItems.push("CDE");
}
function Container2ViewModel() {
var self = this;
self.isVisible = ko.observable();
self.myItems = ko.observableArray();
self.myItems.push("XYZ");
self.myItems.push("PQR");
self.cancel = changeVisibility;
}
function changeVisibility()
{
container1VM.isVisible(!container1VM.isVisible());
container2VM.isVisible(!container2VM.isVisible());
}
var container1VM = new Container1ViewModel();;
var container2VM = new Container2ViewModel();;
ko.applyBindings(container1VM, document.getElementById("container1"));
ko.applyBindings(container2VM, document.getElementById("container2"));

Also consider using Knockout Postbox by the excellent Ryan Niemeyer:
https://github.com/rniemeyer/knockout-postbox
You could use syncWith to update an observable controlling visibility. That way both viewmodels don't need to know about each other and you're not hard coding references to your viewmodels.
syncWith - syncWith(topic, [initializeWithLatestValue], [skipInitialPublish], [equalityComparer])
The syncWith function tells an observable to both subscribe and publish on a topic. This allows observables in two different view models to stay in sync with each other without having direct knowledge of its counterpart.
//subscribe to and publish on a topic
this.value = ko.observable(value).syncWith("mytopic");
//subscribe to and publish on a topic and use the last published value to initialize the observable
this.value = ko.observable().syncWith("mytopic", true);
//subscribe to and publish on a topic, but do not publish out the observable's value initially
this.value = ko.observable(value).syncWith("mytopic", false, true);
//subscribe to and publish on a topic, but only publish when the comparer function returns false
var comparer = function(newValue, oldValue) {
return newValue < oldValue;
};
this.value = ko.observable(value).syncWith("mytopic", false, false, comparer);

Related

KnockoutJS Observable being rebound appropriately, but calling gives an empty string?

So I've got a section that updates based on a ko.observable called toClicked, see below:
<div id="longInfoWindow">
<div id="longInfo_goBack"><span class="fa fa-arrow-left"></span> Go Back</div>
<div id="location_mainInfo">
<h1 id="location_title" data-bind="text: toClicked.title">Title</h1>
<h2 id="location_address" data-bind="text: toClicked.address">Address</h2>
<h6 class="location_latlng">LAT: <span data-bind="text: toClicked.lat">LATITUDE</span></h6>
<h6 class="location_latlng">LNG: <span data-bind="text: toClicked.lng">LONGITUDE</span></h6>
<p id="location_description" data-bind="text: toClicked.content">
Empty
</p>
</div>
</div>
toClicked is data-bound via a for-each ul that passes in bits of information from an observableArray, so this data is constantly changing - here's what the click function looks like in the viewmodel.
var viewModel = {
// Nav open and close via knockoutjs
self : this,
userQuery : ko.observable(''),
toClicked : ko.observable({}),
logClick : function(clicked){
var toClicked = ko.observable({});
toClicked().title = clicked.title;
toClicked().lat = clicked.lat;
toClicked().lng = clicked.lng;
toClicked().placeID = clicked.placeID;
toClicked().address = clicked.address;
toClicked().content = clicked.content;
return toClicked();
}
};
// at the end of the document...
ko.applyBindings(viewModel);
For some reason, I can call any toClicked parameter, like toClicked.title, and I get the proper output. But I can't get anything to bind in the longInfoWindow bit of code, it removes the filler text with empty strings. Is there something I'm missing here with Knockout that's keeping it from updating appropriately?
As a side note, I've tried setting the databinds to viewModel.toClicked.title with no joy. Have also tried $root, $parent. either comes back as not defined or gives the same result.
You need to change the way toClicked is accessed. Given that it is an observable, it must access the properties using syntax like toClicked().property. Another problem is that you should specify toClicked as an object, before setting properties on it.
Also, since clicked is an array, it should be accessed by index, like clicked[0].title.
Note the use of self.toClicked.valueHasMutated(); in function logClick. It tells the view model that observable variable toClicked has some non-observable properties that might have changed. As a result the view model is updated. You should avoid using it excessively.
var viewModel = function() {
// Nav open and close via knockoutjs
var self = this;
self.test = ko.observable('text');
self.userQuery = ko.observable('');
self.toClicked = ko.observable({});
self.markers = ko.observableArray([
{ title: 'Eagle River Airport', lat: 45.934099, lng: -89.261834, placeID: 'ChIJdSZITVA2VE0RDqqRxn-Kjgw', content: 'This is the Eagle River Airport. Visit us for fly-ins!' }
]);
self.logClick = function(clicked) {
// var toClicked = ko.observable({});
self.toClicked().title = clicked[0].title;
self.toClicked().lat = clicked[0].lat;
self.toClicked().lng = clicked[0].lng;
self.toClicked().placeID = clicked[0].placeID;
self.toClicked().address = clicked[0].address;
self.toClicked().content = clicked[0].content;
self.toClicked.valueHasMutated();
return self.toClicked();
};
};
// at the end of the document...
var vm = new viewModel();
ko.applyBindings(vm);
var markers = vm.markers();
vm.logClick(markers);
Your view model must also change slightly. Note the () brackets after toClicked, they are used to access the properties of an observable.
<div id="longInfoWindow">
<div id="longInfo_goBack"><span class="fa fa-arrow-left"></span> Go Back</div>
<div id="location_mainInfo">
<h1 id="location_title" data-bind="text: toClicked().title">Title</h1>
<h2 id="location_address" data-bind="text: toClicked().address">Address</h2>
<h6 class="location_latlng">LAT: <span data-bind="text: toClicked().lat">LATITUDE</span></h6>
<h6 class="location_latlng">LNG: <span data-bind="text: toClicked().lng">LONGITUDE</span></h6>
<p id="location_description" data-bind="text: toClicked().content">
Empty
</p>
</div>
I'd also suggest that instead of accessing toClicked directly within logClick you use something like self.toClicked to avoid ambiguity. See this.
Here's the working fiddle: https://jsfiddle.net/Nisarg0/hx0q6tt6/13/
The more obvious way without having to use valueHasMutated would be to assign directly to the observable:
self.logClick = function(clicked) {
self.toClicked({
lat: clicked[0].lat,
lng: clicked[0].lng,
placeID: clicked[0].placeID,
adress: clicked[0].address,
content: clicked[0].content
});
};
You normally do not need to use valueHasMutated when using knockout. Also there is no need to return the observable from the click handler. In your bindings you need then to access the properties as already stated like this:
<h1 id="location_title" data-bind="text: toClicked().title">Title</h1>
Knockout will automatically update the heading, whenever toClicked changed.

How to call function dynamically in JQuery

I've set of html codes, I want to have a function for each set where I can pull some data or do some activities, so basically I need to be calling function according to the html codes present in DOM, suppose I've header section I want to collect menu items and I've sliders where I want to collect slider information, So I need to call header function to do the activity accordingly and slider function to do the activity, I went across some info about eval() but I guess it has lot of demerits and is being obsolete. Please suggest me how can I achieve it.
HTML Code:
Header
<div class="header" data-nitsid="1">
<div class="branding">
<h1 class="logo">
<img src="images/logo#2x.png" alt="" width="25" height="26">NitsOnline
</h1>
</div>
<nav id="nav">
<ul class="header-top-nav">
<li class="has-children">
Home
</li>
</ul>
</nav>
</div>
Slider
<div id="slideshow" data-nitsid="2">
<div class="revolution-slider">
<ul> <!-- SLIDE -->
<!-- Slide1 -->
<li data-transition="zoomin" data-slotamount="7" data-masterspeed="1500">
<!-- MAIN IMAGE -->
<img src="http://placehold.it/1920x1200" alt="">
</li>
</ul>
</div>
</div>
Now I want to collect data from both elements and pass the data to my views of laravel framework where it will generate a popup for each section for editing purpose.
$('.page-content-wrapper').find('[data-nitsid]').each(function() {
// getting data to call function
var nits = $(this).data("nitsid");
// calling function
design.nits();
}
var design = {};
design.1 = function() {
// do ajax request to views of header
}
design.2 = function() {
// do ajax request to views of slider
}
You canot use literal number as a property name. If you want to call property 1 of object design use design[1] instead. Also, you cannot assing property to non-initialized variable, you must use var design = {}; to make it object. If your property of design object is stored in nits variable, then call it as design[nits]();. Also, next time don't forget to test your code before posting it here. You've forget ) after your first function.
$('.page-content-wrapper').find('[data-nitsid]').each(function() {
// getting data to call function
var nits = $(this).data("nitsid");
// calling function
design[nits]();
});
var design = {};
design[1] = function() {
// do ajax request to views of header
};
design[2] = function() {
// do ajax request to views of slider
};
You want to use design[nits]();.
This will get the property nits of design and execute it with ().
But there is another problem. Your design will be declared after the each loop, so it is not available inside. You have to place it before.
$(function() {
var design = {};
design.funcOne = function() {
alert("funcOne called");
}
design.funcTwo = function() {
alert("funcTwo called");
}
$('div[data-nitsid]').each(function() {
var nits = $(this).data("nitsid");
design[nits]();
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div data-nitsid="funcOne">
I will call 'funcOne'!
</div>
<div data-nitsid="funcTwo">
I will call 'funcTwo'!
</div>

KnockoutJS, how to update view model using HTML5 content editable?

I have the following code:
HTML:
<ul class="list" data-bind="foreach: list">
<li class="title" data-bind="text:title" contenteditable="true"></li>
<li class="item" data-bind="text:item" contenteditable="true"></li>
</ul>
<button type="button">Save</button>
JS:
var data = {
"list": [{
"title" : "title one",
"item" : "item one"
}]
}
var viewModel=
{
list : ko.observable(data.list)
};
ko.applyBindings(viewModel);
$("button").on("click", function(){
var vm = viewModel;
ko.applyBindings(vm);
var data = ko.toJSON(vm);
console.log(data);
});
However when I do this I get this error:
Uncaught Error: You cannot apply bindings multiple times to the same element.
knockout-3.1.0.js:58
What I would like to do is change the text of one of the items and have it save to the view model when I click the save button.
FIDDLE:
http://jsfiddle.net/sr4Fg/13/
There are few things.
only call applyBindings once, then ko will sync data for you.
you don't need to create a save button, ko sync data automatically.
your title and item are NOT ko.observable, hence ko has no way to auto-update it for you.
ko "text" binding is kind of one way binding, it only updates view when your value changes. You need to use "value" binding in an input tag to get two way binding.
right now, there is no existing ko binding supporting contenteditable. You may build a custom bindingHandler for it, but beware it's tricky to get contenteditable change event.
you list should be an observableArray.
Here is the working example with "value" binding: http://jsfiddle.net/sr4Fg/41/
<ul class="list" data-bind="foreach: list">
<li class="title"><input data-bind="value:title" /></li>
<li class="item"><input data-bind="value:item" /></li>
</ul>
<button type="button">Save</button>
var viewModel=
{
list : ko.observableArray([{
"title" : ko.observable("title one"),
"item" : ko.observable("item one")
}])
};
ko.applyBindings(viewModel);
$("button").on("click", function(){
var data = ko.toJSON(viewModel);
console.log(data);
});
Understand you may load JSON data from ajax call, it's tedious to change all the values into ko.observable. Try http://knockoutjs.com/documentation/plugins-mapping.html if you need.

Using Knockout with repeated user controls

I have a web page that contains a list of games. Each game is presented by a user control, that contains a few labels that hold the properties of the game (time, scores, players, etc.). So the same user control is repeated a few times on the page.
The data changes every minute to support live covarage of the game.
I was hoping to use knockout to update all labels in the user control, but since every user control should bind to a different game data, and a user control cannot have its own view model, I dont know what is the best approach to this scenario.
I need something like a dynamic ViewModel and a dynamic data-bind attributes, but I couldnt find any information on the subject.
Here is a demonstration of the template binding using both data and foreach with the same template. You can see in the JS that the data is the type, a game, but we are dislpaying them separately in the HTML.
HTML
<!-- ko if: favoriteGame -->
<h1>Favorite Game</h1>
<div data-bind="template: { name: 'gameTemplate', data: favoriteGame }"></div>
<!-- /ko -->
<h1>All Games</h1>
<div data-bind="template: { name: 'gameTemplate', foreach: games }"></div>
<script type="text/ko" id="gameTemplate">
<div>
<span class="gameName" data-bind="text: name"></span>
<span data-bind="text: publisher"></span>
<input data-bind="value: score" />
<button data-bind="click: $parent.favoriteGame">Favorite</button>
</div>
</script>
Javascript
var Game = function(data) {
this.name = ko.observable(data.name || "");
this.publisher = ko.observable(data.publisher || "");
this.score = ko.observable(data.score || 0);
};
var ViewModel = function(init) {
var self = this;
self.favoriteGame = ko.observable();
self.games = ko.observableArray(ko.utils.arrayMap(init, function(g) {
return new Game(g);
}));
};
Note that the click: $parent.favoriteGame binding selects the favorite game directly. Knockout passes the current context as the first parameter to function bindings, and since observables are functions, this updates the observable directly, without needing a wrapper function.
You can take a look at this in this fiddle. Its not perfectly clear what you where after, you don't have any code in your question. I hope this isn't too far off.

Passing in an observableArray as a function parameter

What I'm trying to achieve:
A form with one text input field(Name), two select fields with some options(Type & Column) with a submit button that creates a Widget with it's title set to Name, data-type set to Type and Column - being it's position in the page.
Type in this case has a couple of different options defined in the view, it works just dandy so I won't go into how I got that working, for now.
As of now I have a page with three columns, each as it's own observableArray - like so:
self.col0 = ko.observableArray([]);
self.col0.id = 'col0'
The same goes for col1 and col2. My goal here is to get the select field to point to these arrays. Atleast that's what I think I need to do.
I have a createWidget function, that looks at the Name and Type values in the DOM (Correct me if I'm wrong here, this is the very first thing I'm creating with KnockOut) and creates a new Widget from that data - like so:
var Widget = function(name, type) {
var self = this;
self.name = name;
self.type = type;
};
var ViewModel = function() {
var self = this;
self.newWidgetName = ko.observable();
self.newType = ko.observable();
// Unrelated code in between
self.createWidget = function(target) {
newName = self.newWidgetName();
newType = self.newType();
widget = new Widget(newName, newType);
target.unshift(widget)
self.newWidgetName("");
};
The input/select elements in the DOM
input.widget-name type="text" placeholder="wName" data-bind="value: newWidgetName"
select data-bind="value: newType"
option value="calendar" Calendar
option value="article" Article
option value="document" Document
select.colPick data-bind="value: colPick"
option value="col0" Column 1
option value="col1" Column 2
option value="col2" Column 3
And my click handler for the createWidget function - like so:
a.btn.create-widget data-bind="click: function(){ createWidget(col0); }"
Shazam, it works!
However, this will only ever output the new Widget into the first col (col0), it won't take the value of the column select field into account and unshift the new widget into the correct column. The errors I'm getting after much trial and error pretty much boils down to:
1) "Cannot call method unshift of undefined"
2) "Uncaught TypeError: Object function observable() .... has no method 'unshift'
So as of right now the code looks like the example above, It's not ideal by any means but with the different columns connected with knockout-sortable, it's not a huge deal if this functionality has to get scrapped. The user can still move the Widgets around from col0 to col2 and vice versa.
If anyone has a resource that would point me in the right direction, or the answer up their sleeve - feel free to share!
All the best, Kas.
Edit: Followup questions for Tyrsius
With the code you supplied I now have the three columns working in the select box, however I am struggling a bit when it comes to outputting the Widgets into view.
With my previous code, this is what the view looked like:
<div class="cols">
<div class="col col-25">
<ul data-bind="sortable: { template: 'widgetTmpl', data: col0, afterMove: $root.widgetData }">
</ul>
</div>
<div class="col col-50">
<ul data-bind="sortable: { template: 'widgetTmpl', data: col1, afterMove: $root.widgetData }">
</ul>
</div>
<div class="col col-25">
<ul data-bind="sortable: { template: 'widgetTmpl', data: col2, afterMove: $root.widgetData }">
</ul>
</div>
</div>
What I'm working with right now is this:
<div data-bind="foreach: columns">
<div data-bind="foreach: items" class="col">
<ul data-bind="sortable: { template: 'widgetTmpl', data: columns, afterMove: $root.widgetData }"></ul>
</div>
</div>
My first question, I realised I wasn't thinking at the time so skip that one.
Question #2: How would I now get these Widgets bound to the col that I picked in the form?
Would I do this?
<ul data-bind="sortable: { template: 'widgetTmpl', data: entryColumn, afterMove: $root.widgetData }"></ul>
Or am I way off? :)
I would keep a collection of the items on the column as a type, which would look like this:
var Column = function(header) {
this.header = ko.observable(header);
this.items = ko.observableArray([]);
};
The trick would be to bind the column select straight to the list of columns on your viewmodel:
<select data-bind="options: columns, optionsText: 'header', value: entryColumn"
Whats happening here is that the actual column object that is selected by the dropdown will be stored in the entryColumn property. Later we can put the item directly into its items list since we will have a reference to it. This also allows us to support any number of columns without changing the logic.
The add code would stay simple:
self.columns = ko.observableArray(columns);
self.entryName = ko.observable('');
self.entryType = ko.observable('');
self.entryColumn = ko.observable('');
self.types = ko.observableArray(['Small', 'Medium', 'Large']);
self.addWidget = function() {
var widget = new Widget(self.entryName(), self.entryType());
//Here is where we can directly add to the selected columns item list
self.entryColumn().items.push(widget);
self.resetForm();
};
Here is the fiddle demonstrating these.
FollowUp Question
You're close, but the data is the items not the EntryColumn
<div data-bind="foreach: columns">
<div data-bind="sortable: { template: 'widgetTmpl', data: items}" class="column">
</div>
</div>
<script type="text/html" id="widgetTmpl">
<p data-bind="text: name() + ' - ' + type()"></p>
</script>
Here is the updated fiddle.

Categories