Passing in an observableArray as a function parameter - javascript

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.

Related

How to exchange value between two viewmodels in knockout js

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);

Data binding while manipulating the DOM not working with Angular, Knockout

I am looking to implement Angular, Knockout or another to data-bind a wizard-style application form proof-of-concept (no server-side code). However I appear to be breaking the data bindings when I clone the data-bound div.
The first few steps of the application form capture data, while the later steps present the data back to the user to allow them confirm what was entered. I am manipulating the DOM by inserting the appropriate step when 'next' is pressed and taking out the last step when 'previous' is pressed. I do this using detatch, clone and remove.
Can anyone give advise on the approach they would take to make this work, and what frameworks I should look at?
Below is pseudocode to give an idea of the structure. The pseudo-data-binding-code is just how I thought it would work, I'm not bedded to any framework.
HTML View
<div id="wizard">
<div id="step1">Enter your name: <input type="text" id="name" /></div>
</div>
<div id="actions"><input type="button" value="Previous" /><input type="button" value="Next" onClick="goNext();" /></div>
<div id="steps">
<div id="stepA">Enter your age: <input type="text" id="age" databind="theAge" /></div>
<div id="stepB">The age you entered - {{ theAge }} is too young!</div>
<div id="stepC">Enter your favourite colour: <input type="text" id="faveColour" databind="faveCol" /></div>
<div id="stepD">Hi {{ name }}. You are {{ age }} years old and your favourite colour is {{ faveCol }}</div>
</div>
JavaScript
<script>
function goNext() {
// figure out which step is next
insertStepIntoWizard(step, index, title);
}
function insertStepIntoWizard(step, index, title) {
var element = step.detach();
wizard.steps('insert', index, {
title: title,
content: element.clone()
});
console.log('insertStepIntoWizard - just inserted ' + step.attr('id') + ' into wizard position ' + index);
}
</script>
I think you're thinking in terms of having your view reflect your entity model, which is basically templating. If you're looking to use Knockout/Angular, consider using it's view model to manage page state / flow / actions, in addition to controlling the entity. (Writing jQuery code to poke about with the DOM and clone, show/hide, etc is no fun). #sabithpocker makes a similar point.
Working example: I'm familiar with Knockout, and have created an example jsFiddle of your scenario: http://jsfiddle.net/overflew/BfRq8/5/
Notes:
I've used the template tag to house each section of the wizard, and all steps point to the same model/entity within the viewmodel.
To emphasise what's happening with the publish/subscribe nature of the bindings:
The user input is also relayed at the bottom of the page.
The form title is dynamic, as well as the 'step'
ko.computed is used to 'compute' the full name and if there are any 'steps' left to go
Knockout-specific: Notice the occurrence of brackets popping up around the place. This is one thing that may catch you out occasionally if you choose to learn Knockout. It just means that you're evaluating the binding container to get the value.
View model
<div>
<h3 data-bind="text: currentStep().name"></h3>
<div data-bind="template: { name: 'wizard-step1' }, visible: currentStep().id === 0"></div>
<div data-bind="template: { name: 'wizard-step2' }, visible: currentStep().id === 1"></div>
<div data-bind="template: { name: 'wizard-step3' }, visible: currentStep().id === 2"></div>
<input type="button" value="Next step" data-bind="click: onNext, visible: hasNextSteps" />
<input type="button" value="Submit" data-bind="click: onSubmit,visible: !hasNextSteps()" />
<div data-bind="visible: submitResultMessage, text: submitResultMessage"></div>
</div>
<div>
<h3>Your inputs</h3>
<div data-bind="visible: questions.fullName">Full name: <span data-bind="text: questions.fullName"></span></div>
<div data-bind="visible: questions.age">Age: <span data-bind="text: questions.age"></span>
</div>
<div data-bind="visible: questions.favouriteColour">Favourite colour: <span data-bind="text: questions.favouriteColour"></span>
</div>
</div>
<script type="text/html" id="wizard-step1">
<div>
First name: <input data-bind="value: questions.firstName, valueUpdate:'afterkeydown'" />
</div>
<div>
Last name: <input data-bind="value: questions.lastName, valueUpdate:'afterkeydown'" />
</div>
</script>
<script type="text/html" id="wizard-step2">
<div>
Age: <input data-bind="value: questions.age, valueUpdate:'afterkeydown'" />
</div>
</script>
<script type="text/html" id="wizard-step3">
<div>
Favourite colour: <input data-bind="value: questions.favouriteColour, valueUpdate:'afterkeydown'" />
</div>
</script>
View
// Entity for holding form data.
var FormData = function() {
var self = this;
self.firstName = ko.observable("");
self.lastName = ko.observable("");
self.age = ko.observable("");
self.favouriteColour = ko.observable("");
self.fullName = ko.computed(function() {
if (!self.firstName() && !self.lastName()) {
return "";
}
return self.firstName() + " " + self.lastName();
});
}
// Quick handling for managing steps of the wizard
var wizardSteps = [
{ id: 0, name: "Wizard step 1"},
{ id: 1, name: "More questions"},
{ id: 2, name: "Last step"}
];
var ViewModel = function() {
var self = this;
// Properties
self.questions = new FormData();
self.currentStep = ko.observable(wizardSteps[0]);
self.submitResultMessage = ko.observable();
// Actions
self.onNext = function() {
var currentIndex = self.currentStep().id;
if (self.hasNextSteps()) {
// Move forward one step on the wizard
self.currentStep(wizardSteps[currentIndex + 1]);
}
};
self.onSubmit = function() {
self.submitResultMessage("Data is now submitted ");
};
// Page control
self.hasNextSteps = ko.computed(function() {
var currentIndex = self.currentStep().id;
return currentIndex < wizardSteps.length - 1;
});
};
ko.applyBindings(new ViewModel());
What most of the bleeding edge javascript frameworks is trying to do is attempting to manage the huge amount of code in current applications where large amount of business logic is implemented with javascript in client side. So they are mostly trying to provide you with some architecture to organize your code to make it easy to manage, read and scale.
In your case you are trying to neglect the architecture like MVC or MVWhatever they are providing and use the framework to do some templating, If I understand you correctly. For that you can better get the help of some templating engine in javascript like handlebars and use it to manually render your data stored in your current javascript app.
see it here http://handlebarsjs.com/
Simply re-initialize your bindings.
This solves my problem when using knockout.
When you change the main DOM element which is actually being referenced for MVVM, it turn-out that any change or re-structuring that element hampers the dom relationship.
Eventually, though the placement of the item looks same but technically, in java script engine it is not. So the problem occurs.
In order to fix something like this, simply have a constructor which will initialize or re-initialize the mapping for a particular element in DOM.
Hope that solves your problem.
Example:
function ReInitializeVM()
{
ko.cleanNode(document.getElementById("userDetails"));
ko.applyBindings(viewModel, document.getElementById("userDetails"));
}
The function can be called everytime where you feel the need of re-initializing the bindings. Calling the function will remove any binding on the element with id "userDetails" and then the applyBindings can be called to initialize the binding. This will invoke bindings for any new/changed element.

Options Binding overrides initial View Model value

I tried to follow the other similar questions on Stack Overflow but thus far have been unsuccessful in fixing the problem.
I am using jQuery AJAX to retrieve several items: a contact and its associated information, all available salutation types, all available email types and all available phone types.
I have successfully bound the options to the select boxes. However, it appears to overwrite the 'value' binding that holds the initial view model value.
Could any of you help me solve this? Please let me know if you have any questions for clarification.
Please see the code below:
View Model:
function contactPageViewModel() {
var self = this;
self.contact = ko.observable();
self.availableSalutations = ko.observableArray();
self.availableEmailTypes = ko.observableArray();
self.availablePhoneTypes = ko.observableArray();
self.availableAddressTypes = ko.observableArray();
}
where contact is an object coming from the server, which includes the element contact.salutation.
The json coming back for contact is:
{
//...
"createdBy":null,
"createdOn":1392848929000,
"updatedBy":null,
"updatedOn":1392848929000,
"contactId":305,
"salutation":{"salutationId":102,"salutation":"Mrs."},
"firstName":"Laura",
"middleInitial":"K",
"lastName":"Ritchey"
//...
}
the json coming back from availableSalutations (which is a property of a json object wrapper 'listObject') is:
[{"salutationId":41,"salutation":"Ms."},
{"salutationId":101,"salutation":"Mr."},
{"salutationId":66,"salutation":"CDR"},
{"salutationId":81,"salutation":"LCDR"},
{"salutationId":102,"salutation":"Mrs."},
{"salutationId":121,"salutation":"Mr."},
{"salutationId":64,"salutation":"LTC"}]
The code to map the JSON result to the knockout observables:
contactPageViewModel.contact = ko.mapping.fromJS(data.listObject[0]);
contactPageViewModel.availableEmailTypes = ko.mapping
.fromJS(data.listObject[1]);
contactPageViewModel.availableSalutations = ko.mapping
.fromJS(data.listObject[2]);
....
applyBindings();
The HTML:
<label for="rank"> Rank / Title: </label>
<select data-bind="optionsText: 'salutation',
options: availableSalutations,
value: contactPageViewModel.contact.salutation"
class="rankList"
name="Rank"
id="rankSelect">
</select>
Try value: $root.contact().salutation instead of value: contactPageViewModel.contact.salutation.
Or:
<label for="rank"> Rank / Title: </label>
<!-- ko with: contact -->
<select data-bind="options: $root.availableSalutations, optionsText: 'salutation', value: salutation" class="rankList" name="Rank" id="rankSelect">
</select>
<!-- /ko -->
Update:
You could look at this Fiddle. May be it contains a lot of excess code and you can simplify it, but the main things is to separate initial and selected salutations and add optionsCaption to select bindings:
var initialSalutation = new salutationViewModel(data.salutation);
And:
self.salutation = ko.observable();
self.displayedSalutation = ko.computed(function () {
if (self.salutation()) {
return self.salutation();
} else {
return initialSalutation;
}
})
Update 2:
Look at this Fiddle. I've added optionsValue: 'salutationId' to select bindings and move displayedSalutation to contactPageViewModel.
I think problem was with matching objects (select item and salutation from contact). When value of select is salutationId and contact salutation also salutationId (number value, not object) all working good.

Checking if any checkbox is checked Knockout - PhoneJS

So in my mobile web app (using PhoneJS), I am using a dxList to display some records. I have a checkbox next to each list 'item', so that I can mass delete or send the records. I need to know how to figure out if there is one or more checkboxes checked.
I know I can do this with normal Knockout, but I don't the PhoneJS framework actually creates a 'real' HTML checkbox, but makes a clickable element that functions like a checkbox.
So if one or more checkboxes are checked, I need to show a send and delete button. I just need to know how to determine if there are any checked boxes.
I've looked everywhere online for this, but the solutions are for Knockout using REAL checkbox inputs...
Here's my code for the dxList:
<div data-bind="dxList:{dataSource: list_data, grouped:true }">
<div data-options="dxTemplate:{name:'group'}">
<b><span data-bind="text: $data.key"></span></b>
</div>
<div data-options="dxTemplate:{name:'item'}">
<span data-bind="text: $data.item_value"></span>
<div data-bind="dxCheckBox: { }" style="float:right"></div>
</div>
</div>
I've tried binding 'checked' to an observable array, but that affects all the checkboxes.
Can anyone help me with this? Thanks!
The most straightforward MVVM approach is to data-bind dxCheckBox.checked option to a boolean property of a list item view-model. Then you can iterate over the items and understand which are checked.
You mentioned that you
tried binding 'checked' to an observable array
It is not clear why you bind a scalar property to an array.
Actually it does not differ much from the pure HTML approach. You may treat PhoneJS widgets just as fat HTML tags.
So, I have pretty much the same question, but I think I can be more clear on my requirements.
I have a dxList that uses a SQLite table as a datasource. It is setup to allow the user to select from a list of templates to apply to another object. This new list of templates and the associated object ID will be saveed in a DIFFERENT table than the original data and as such, I need to be able to identify the items in the list that have been checked.
<div data-bind="dxList: { dataSource: templateList }">
<div data-bind="dxAction: ''" data-options="dxTemplate : { name: 'item' } ">
<table>
<tr>
<td>
<div data-bind="dxCheckBox: { }"></div>
</td>
<td>
<div style="font-weight: bold; padding-left: 10px;" data-bind="text: TemplateName"></div>
</td>
</tr>
</table>
</div>
</div>
I found this post during my initial search. I can't use the data-bind: {checked: ?} value of each check box, as that would do as the original poster found, setting all or none. I thought about an array. I'm going to try to use the dxAction to add/remove checked list item IDs from an array but I'm not sure how well that will work. Then there's the final parse to get all checked items. I will update this post once I get it working.
Resolution:
ViewModel objects:
selectedTemplates: ko.observableArray(),
selectTemplate: function (args) {
//If it's there. Remove it.
if (args.model.selectedTemplates.indexOf(args.itemData.TemplateID) > -1) {
args.model.selectedTemplates.pop(args.itemData.TemplateID);
args.itemElement[0].style.backgroundColor = '';
args.itemElement[0].style.color = 'Black';
}
//else Add
else {
args.model.selectedTemplates.push(args.itemData.TemplateID);
args.itemElement[0].style.backgroundColor = '#017AFF';
args.itemElement[0].style.color = 'White';
}
},
And the View:
<div data-options="dxView : { name: 'SelectSurveys', title: 'SelectSurveys' } ">
<div data-bind="dxCommand: { title: 'Save', id: 'create', action: saveSelections, icon: 'save' }"></div>
<div data-options="dxContent : { targetPlaceholder: 'content' } ">
<div data-bind="dxList: { dataSource: templateList, itemClickAction: selectTemplate }">
<div data-options="dxTemplate : { name: 'item' } ">
<div style="font-weight: bold; padding-left: 10px;" data-bind="text: SurveyName"></div>
</div>
</div>
</div>
</div>
And looping the selected values for saving to local DB:
$.each(args.model.selectedTemplates(), function (index, value) {
db.transaction(function (tx) {
console.log("Inserting Data");
tx.executeSql(sql, [value],
function (t, result1) {
if (result1 != null) {
console.log("New Item added." + result1.insertId);
}
},
function (t, error) {
console.log(error);
});
});
In the objects, I've added some coloring so you can tell which ones are selected, it doesn't use the dxSwitch or Checkbox, but it works just as well and I think it's more visually appealing as well as informative to the user.

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.

Categories