First I'm new to using knockout.
I have bound array1 to my template now I would like change it to use array2 is this possible with knockout?
What I was messing with
var viewModel = function(){
var _this = this;
this.test = [{ name: 'Fruit4'}, {name: 'Vegetables'}];
this.categories = ko.observableArray(this.test);
this.changeItems = function()
{
this.test= [{ name: 'Fruit2'}, {name: 'Vegetables2'}];
categories = ko.observableArray(this.test);
}
};
ko.applyBindings(viewModel());
Create a computed observable that will return one of the two arrays based on your conditions whatever they would be and bind to it. Make sure that the conditions that decide which to choose are also observable so it will update properly.
function ViewModel(data) {
this.array1 = ko.observableArray(data.array1);
this.array2 = ko.observableArray(data.array2);
// change this value to true to use array2
this.chooseArray2 = ko.observable(false);
this.array = ko.computed(function () {
return this.chooseArray2()
? this.array2()
: this.array1();
}, this);
}
<div data-bind="foreach: array">
...
</div>
Of course the logic could be more complex than that. To be more manageable, I would make the condition observable computed as well and create the logic in there. The computed observable that returns the array wouldn't have to change much.
function ViewModel(data) {
this.array1 = ko.observableArray(data.array1);
this.array2 = ko.observableArray(data.array2);
// which to choose depends on a number of conditions
this.someCondition = ko.observable(false);
this.anotherCondition = ko.observable(true);
this.someNumber = ko.observable(132);
this.chooseArray2 = ko.computed(function () {
// some complex logic
if (this.someNumber() < 0) {
return this.someCondition();
}
return this.someCondition() || !this.anotherCondition();
}, this);
this.array = ko.computed(function () {
return this.chooseArray2()
? this.array2()
: this.array1();
}, this);
}
Related
Im struggling to find a way to get the properties Override & Justification available outside of the function. The code is:
self.CasOverridesViewModel = ko.observable(self.CasOverridesViewModel);
var hasOverrides = typeof self.CasOverridesViewModel === typeof(Function);
if (hasOverrides) {
self.setupOverrides = function() {
var extendViewModel = function(obj, extend) {
for (var property in obj) {
if (obj.hasOwnProperty(property)) {
extend(obj[property]);
}
}
};
extendViewModel(self.CasOverridesViewModel(), function(item) {
item.isOverrideFilledIn = ko.computed( function() {
var result = false;
if (!!item.Override()) {
result = true;
}
return result;
});
if (item) {
item.isJustificationMissing = ko.computed(function() {
var override = item.Override();
var result = false;
if (!!override) {
result = !item.hasAtleastNineWords();
}
return result;
});
item.hasAtleastNineWords = ko.computed(function() {
var justification = item.Justification(),
moreThanNineWords = false;
if (justification != null) {
moreThanNineWords = justification.trim().split(/\s+/).length > 9;
}
return moreThanNineWords;
});
item.isValid = ko.computed(function() {
return (!item.isJustificationMissing());
});
}
});
}();
}
I've tried it by setting up a global variable like:
var item;
or
var obj;
if(hasOverrides) {...
So the thing that gets me the most that im not able to grasp how the connection is made
between the underlying model CasOverridesviewModel. As i assumed that self.CasOverridesViewModel.Override() would be able to fetch the data that is written on the screen.
Another try i did was var override = ko.observable(self.CasOverridesViewModel.Override()), which led to js typeError as you cannot read from an undefined object.
So if anyone is able to give me some guidance on how to get the fields from an input field available outside of this function. It would be deeply appreciated.
If I need to clarify some aspects do not hesitate to ask.
The upmost gratitude!
not sure how far outside you wanted to go with your variable but if you just define your global var at root level but only add to it at the moment your inner variable gets a value, you won't get the error of setting undefined.
var root = {
override: ko.observable()
};
root.override.subscribe((val) => console.log(val));
var ViewModel = function () {
var self = this;
self.override = ko.observable();
self.override.subscribe((val) => root.override(val));
self.load = function () {
self.override(true);
};
self.load();
};
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
I've got two ways that I'm filling in an observableArray, one for testing purposes and one for the way I intend on using this array.
The first way I'm defining these objects and pushing them in one at a time, the second way I'm reading a JSON stream and pushing them in with a loop.
Here's my code for this shuttle-menu I'm using.
var StateModel = function() {
var self = this;
// initialize containers
self.leftStateBox = ko.observableArray();
self.rightStateBox = ko.observableArray();
// selected ids
self.selectedLeftStateBox = ko.observableArray();
self.selectedRightStateBox = ko.observableArray();
self.moveLeft = function () {
var sel = self.selectedRightStateBox();
for (var i = 0; i < sel.length; i++) {
var selCat = sel[i];
var result = self.rightStateBox.remove(function (item) {
return item.id == selCat;
});
if (result && result.length > 0) {
self.leftStateBox.push(result[0]);
}
}
self.selectedRightStateBox.removeAll();
}
self.moveRight = function () {
var sel = self.selectedLeftStateBox();
for (var i = 0; i < sel.length; i++) {
var selCat = sel[i];
var result = self.leftStateBox.remove(function (item) {
return item.id == selCat;
});
if (result && result.length > 0) {
self.rightStateBox.push(result[0]);
}
}
self.selectedLeftStateBox.removeAll();
}
self.leftStateBox.push({
id: "CAA"
, name: 'State 1'
});
self.leftStateBox.push({
id: "VAA"
, name: 'State 2'
});
self.leftStateBox.push({
id: "BAA"
, name: 'State 3'
});
self.loadStates = function() {
var self = this;
$.getJSON("${baseAppUrl}/public/company/" + companyId + "/json/searchStates/list",
function (searchStatesData) {
var states = JSON.parse(searchStatesData).searchStates;
for(var i = 0; i < states.length; i++) {
self.leftStateBox.push(new State(states[i]));
}
});
};
self.loadStates();
}
var State = function (state) {
var self = this;
self.name = ko.observable(state.name);
self.id = ko.observable(state.id);
}
$(function () {
ko.applyBindings(new StateModel(), document.getElementById("statesBox"));
});
Here's my view section:
'<div id="statesBox">
<div>
Available States:
<select multiple='multiple' data-bind="options: leftStateBox, optionsText: 'name', optionsValue: 'id', selectedOptions: selectedLeftStateBox"></select>
</div>
<div>
<p><button data-bind="click: moveRight">Add Selected</button></p>
<p><button data-bind="click: moveLeft">Remove Selected</button></p>
</div>
<div>
Selected States:
<select multiple='multiple' data-bind="options: rightStateBox, optionsText: 'name', optionsValue: 'id', selectedOptions: selectedRightStateBox"></select>
</div>
<br /><br />
</div>'
When I try to shuttle things back and forth on the list it works for the three I manually entered in but it doesn't work for the ones imported through the JSON call. They all show up on the list though and seem to have the same information, I structured the manually created objects after how the JSON objects look. When I trace the JS function moveRight, the remove works for the manually created objects but fails on the imported ones. I'm not really sure what I'm doing wrong at this point, has anyone seen something like this?
I grabbed the shuttle menu code from this post
The items you're adding have an important difference.
self.leftStateBox.push({
id: "BAA"
, name: 'State 3'
});
...
var State = function (state) {
var self = this;
self.name = ko.observable(state.name);
self.id = ko.observable(state.id);
}
The first have non-observable property values and the second have observable property values. In general, you should only make things observable if they need to be so (are you ever going to want to change the name or id of an item?).
var State = function (state) {
var self = this;
self.name = state.name;
self.id = state.id;
}
If a property is always observable, you can just "unwrap" it directly: item.id(). If it may sometimes be observable, you can use ko.unwrap(item.id).
var result = self.rightStateBox.remove(function (item) {
return ko.unwrap(item.id) == selCat;
});
Manipulating arrays is very easy by using underscore js
you can easily remove an item in KO observable array by the following code.
self.rightStateBox(_.without(self.rightStateBox(), toRemove));
toRemove is the object to remove from the array.
I have recently started playing with Knockout and I have hit a problem. I have tried Googling this in all sort of ways but I couldn't find any applicable results.
Let's say that I have this model:
var model = new function () {
var that = this;
this.parameterRegex = ko.observable(/\##{1}\w+/ig);
this.query = ko.observable('SELECT ##par1 from ##par2');
this.parameterNames = ko.computed(function () {
var allParameters = that.query().match(that.parameterRegex());
return (allParameters == undefined) ? [] : jQuery.unique(allParameters);
});
this.parameters = ko.computed(function () {
return ko.utils.arrayMap(that.parameterNames(), function (item) {
return {
Name: ko.observable(item),
Example: ko.observable()
}
});
});
};
In the HTML I am binding with the Parameters computed observable, but every time the Query observable changes and the Parameters observable recomputes, I lose all the state of the items in that computed.
What I mean by this is that if I bind a foreach in HTML with Parameters and I have some input boxes in that foreach, such as:
<textarea name="query" class="form-control" data-bind="value: query, valueUpdate:'afterkeydown'" rows="10" style="margin-bottom:20px"></textarea>
<div data-bind="foreach: parameters">
<p data-bind="text: Name"></p>
<input type="text"></input>
</div>
Any text that the user has typed in the input will be lost once the Computed Observeable is recalculated.
How would I go about solving this?
The solution is to keep a separate array with the objects in them and then re-use the objects if they exist in the array instead of re-creating them each time.
var parameters = [];
this.parameters = ko.computed(function () {
var newParams = [];
for (var i = 0; i < that.parameterNames().length; i++) {
var name = that.parameterNames()[i];
var result = $.grep(parameters, function(p){ return p.Name() == name; });
var param;
if (result.length === 0) {
param = {
Name: ko.observable(name),
Example: ko.observable()
};
}
else {
param = result[0];
}
newParams.push(param);
}
parameters = newParams;
return newParams;
});
jsfiddle
I have binded my json array to knockout by using knockout-mapping plugin
JSON
{
"info":[
{
"Name":"Noob Here",
"Major":"Language",
"Sex":"Male",
"English":"15",
"Japanese":"5",
"Calculus":"0",
"Geometry":"20"
},
{
"Name":"Noob Here",
"Major":"Calculus",
"Sex":"Female",
"English":"0.5",
"Japanese":"40",
"Calculus":"20",
"Geometry":"05"
}
]
}
Binded using knockout-mapping plugin
var data = [];
$.each(data1.info, function (index, element) {
data.push({
English: element.English,
Japanese: element.Japanese,
Calculus: element.Calculus,
Geometry: element.Geometry,
name: element.Name,
major: element.Major,
sex: element.Sex
});
});
dataFunction.prototype = function () {
var getAllItems = function () {
var self = this;
ko.mapping.fromJS(data, {}, self.Items);
};
Now I want to alert the value of English.
I tried alert(this.English()); inside dataFunction.prototype and it doesn't work.
How to alert that value?
JS-Bin code: http://jsbin.com/ipeseq/4/edit
You need to define a proper view model and work from that in your mark-up.
I put together a view model with a custom view model mapping where I map your data into objects I called 'Student' that you can use in your markup. This object I extended with a ko.computed that calculates the total (It is in this object you can read and manipulate your observables).
var Student = function(data) {
var self = this;
ko.mapping.fromJS(data, { }, self);
self.total = ko.computed(function() { // Calculate total here
return self.English() + self.Japanese() + self.Calculus() + self.Geometry();
});
};
var viewModelMapping = { // Map all objects in 'info' to Student objects
'info': {
create: function(options) {
return new Student(options.data);
}
}
};
var ViewModel = function(data) { // Create a view model using the mapping
var self = this;
ko.mapping.fromJS(data,viewModelMapping,self);
}
$(document).ready(function () {
vm = new ViewModel(data);
ko.applyBindings(vm);
});
You can see the resulting JSBin code here
You can read more in the Customizing object construction using “create” and Customizing object updating using “update” sections here
How do I use jQuery functions in a custom knockout extender. Here is an example of adding a class to a knockout target from a custom extender.
ko.extenders.addClass = function(target, option) {
if (option == true)
{
target.subscribe(function(newValue) {
$(this.target).addClass('new_class');
});
}
return target;
}
Combine a normal dirty-flag with the css-binding.
ko.dirtyFlag = function(root) {
var result = function() {}, // A function will not get serialized to JSON
_initialState = ko.observable(ko.toJSON(root));
result.isDirty = ko.dependentObservable(function() {
return _initialState() !== ko.toJSON(root);
});
result.reset = function() {
_initialState(ko.toJSON(root));
};
return result;
};
function ViewModel() {
// Normal properties
this.someProperty = ko.observable("initial value");
// Dirty-flag for this object.
this.dirtyFlag = ko.dirtyFlag(this);
}
<div data-bind="css: { 'new_class': dirtyFlag.isDirty }"></div>
You could also pass an observable, or an array of observables, if you want to track just a subset of the properties.
this.dirtyFlag = ko.dirtyFlag(this.someProperty);
http://jsfiddle.net/MizardX/7esdy/