I'm unable to get the radio button checked even with the text matching the radio button value.
`
<div class="well pull-left clearfix" style="margin:5px;width:90px;padding:5px;" data-bind="click: $root.select_litho_layer_definition, css: {'background-highlight': $root.current_litho_layer_definition() === $data}">
<p class="text-center tight-padding no-margin"><strong data-bind="text: layer_name"></strong></p>
<div class="btn-group" style="width:90px">
<button class="btn btn-mini dropdown-toggle" data-toggle="dropdown" style="width:90px">
<span data-bind="text: 'Rev.' + revision"></span>
<span class="caret"></span>
</button>
<ul class="dropdown-menu small-list" data-bind="foreach: $root.litho_layer_name_definitions()[_.indexOf($root.litho_layer_names(), layer_name)]" style="width:90px">
<li><a data-bind="text: 'Rev.' + revision, click: $root.litho_layer_name_select_revision"></a></li>
</ul>
</div>
<!-- ko foreach: exposure_array_names-->
<input type="radio" name="ExposureGroup" value="$parent.layer_name+$data" data-bind="click: $root.litho_layer_name_select_exposure, checked:$root.litho_layer_exposure"/>
<span data-bind="text: $data"></span><br>
<!--/ko-->
</div>
<!-- /ko -->
`
Each of the litho_layer_name_selected_definitions has an array of exposures called exposure_array_names
Now object structure looks like this.
{
"litho_layer_definition_sk": 90000026426,
"exposure_array_names": [
"IF1_EDGE",
"Combined"
],
"layer_name": "3HG",
"revision": 0
}
In the js, I have created an observable object of litho_layer_exposure
self.litho_layer_exposure = ko.observable(true);
self.litho_layer_exposure = ko.computed(function() {
var maskset = self.maskset();
if (!maskset) return [];
var defs = maskset.litho_layer_definitions;
if (_.isEmpty(defs)) return [];
var name = self.current_litho_layer_name();
if (!name) return [];
var revision = self.current_litho_layer_revision();
if (!_.isNumber(revision)) return;
_.reduce(defs, function(memo, d) {
if (d.layer_name === name && d.revision === revision){
memo.push(_.last(d.exposure_array_names));
}
return memo;
}, []);
});
self.litho_layer_exposure.subscribe(function(exposure) {
if (!_.isEmpty(exposure)) self.current_litho_layer_exposure(_.last(exposure));
});
I tried making the below change, which didn't work either
self.litho_layer_exposure = ko.computed({
read: function () {
var maskset = self.maskset();
if (!maskset) return [];
var defs = maskset.litho_layer_definitions;
if (_.isEmpty(defs)) return [];
var name = self.current_litho_layer_name();
if (!name) return [];
var revision = self.current_litho_layer_revision();
if (!_.isNumber(revision)) return;
/*var exposure = self.current_litho_layer_exposure();
if (!exposure) return [];*/
return _.reduce(defs, function(memo, d) {
if (d.layer_name === name && d.revision === revision) memo.push(d.default_exposure_name);
return memo;
}, []);
},
write: function (value) {
//update your self.chosenAge().population value here
self.current_litho_layer_exposure(value);
self.litho_layer_exposure(value);
},
owner: self
});
self.litho_layer_exposure.subscribe(function(exposure) {
if (!_.isEmpty(exposure)) self.current_litho_layer_exposure(_.last(exposure));
});
I´m pretty sure the initial error is
value="$parent.layer_name+$data"
which should lead to all inputs have literally "$parent.layer_name+$data" as value
you probably wanted to use a databinding to fill the attribute
data-bind="attr:{value: $parent.layer_name+$data}, checked: ..."
I am altering an observableArray, modifying some data in a subscribe event. First I am converting the ObservableArray using ko.toJS(), mapping trough the data, and altering. At the end I call self.menuCategories(jsArray) to set the observableArray again.
It seems like I lose the "connection" to the observableArray in some way, since the foreach statement in my code suddenly breaks.
Either there is a very much easier way to handle this, or I am not handling the observables correctly.
CODE :
var MenuWizardModel = function() {
var self = this;
self.menuCategories = ko.observableArray();
self.commonDiscount = ko.observable(0);
// Handling adding items to menuCategories.
self.addNewSubMenuItem = function () {
var newSubMenuItem = new SubMenuItemViewModel(self.newSubMenuItemName(), []);
self.menuCategories.push(newSubMenuItem);
self.newSubMenuItemName(null);
self.createNewSubMenu(false);
}
function SubMenuItemViewModel(name, foodItemList) {
var self = this;
self.name = ko.observable(name);
self.foodItemList = ko.observableArray(foodItemList);
}
self.commonDiscount.subscribe(function(val) {
var discount = parseInt(val) / 100;
var jsArray = ko.toJS(self.menuCategories);
console.log(jsArray)
jsArray = ko.toJS(jsonArray[0].foodItemList.map(item => {
item.price = parseInt(item.price) - (parseInt(item.price) * discount);
return item;
}));
self.menuCategories(jsArray);
});
MARKUP :
<div data-bind="foreach: menuCategories">
<h4 data-bind="text: name"></h4>
<div data-bind="foreach: foodItemList" class="list-group">
...
DATA :
I think the best way to handle this type of thing is to add a computed observable to the fooditem that captures the global discount and calculates the discounted price.
something like the following.
var MenuWizardModel = function() {
var self = this;
self.menuCategories = ko.observableArray([{
name: 'Main Meals'
}]);
self.commonDiscount = ko.observable(0);
self.newSubMenuItemName = ko.observable();
// Handling adding items to menuCategories.
self.addNewSubMenuItem = function() {
var newSubMenuItem = new SubMenuItemViewModel(self.newSubMenuItemName(), [{name: 'Oranges', price: 3.99}]);
self.menuCategories.push(newSubMenuItem);
self.newSubMenuItemName(null);
//self.createNewSubMenu(false);
}
function mapFoodItem(item){
item.discountedPrice= ko.pureComputed(function(){
var discount = parseInt(self.commonDiscount()) / 100
return parseInt(item.price) - (parseInt(item.price) * discount);
});
return item;
}
function SubMenuItemViewModel(name, foodItemList) {
var self = this;
self.name = ko.observable(name);
self.foodItemList = ko.observableArray(foodItemList.map(mapFoodItem));
}
};
ko.applyBindings(new MenuWizardModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<label>Discount <input data-bind="value: commonDiscount"></label>
<label>Sub Menu Name: <input data-bind="value: newSubMenuItemName" /></label>
<button data-bind="click: addNewSubMenuItem">Add Sub Menu</button>
<div data-bind="foreach: {data: menuCategories, as: 'menu' }">
<h4 data-bind="text: menu.name"></h4>
<div data-bind="foreach: {data: menu.foodItemList, as: 'food'}" class="list-group">
<div class="list-group-item">
Name: <span data-bind="text: food.name"></span>
Price: <span data-bind="text: food.price"></span>
Discounted Price: <span data-bind="text: food.discountedPrice"></span>
</div>
</div>
</div>
I have the following button:
<button class="btn actionButtonIcon" id="DashboardEdit" data-bind="click: changeButtonText">
<figure>
<img src="../../../Images/NotesPink.png" />
<figcaption data-bind="text: $data.ProcurementbuttonText() ? 'Save': 'Edit'"></figcaption>
</figure>
</button>
I want to only show it in this specific url
http://localhost:5595/#scorecard/ec5aa8ed-2798-4e71-b13d-f3e525994538/dashboard/PrefDashBoard
Bearing in mind that ec5aa8ed-2798-4e71-b13d-f3e525994538 is an id, thats always changing but i want it to show with all ids as well for example the button should show here as well
http://localhost:5595/#scorecard/2356789-234-234d-g3g3-reg456452/dashboard/PrefDashBoard
and i want to hide it where this isnt the url.
I tried the following code but it doesnt seem to work:
<script>
$(document).ready(function(){
if(window.location.pathname.match(/\/dashboard/PrefDashBoard))
{
$(".DashboardEdit").show();
}
else
{
$(".DashboardEdit").hide();
}
});
</script>
Here is the JS of that button:
self.ProcurementbuttonText = ko.observable(false);
self.changeButtonText = function(){
self.ProcurementbuttonText(!self.ProcurementbuttonText())
if (!self.ProcurementbuttonText()){
var data = {
'ScorecardId':ko.observable(localStorage.getItem('scorecardId'))(),
'DashboardConfig':ko.observable(localStorage.getItem('ElementDataWidget'))()
};
PreferentialProcurementDashboardApi.Save(data);
}
}
self.DashboardEdit = ko.computed({
read: function () {
var text = 'Customise your dashboard';
if (!self.EnableScorecardFeatures()) {
text = 'This feature is currently unavailable for this scorecard type';
} else {
if (!self.HasDocumentsRole()) {
text = 'You do not have sufficient rights to access the Supporting Documents view';
}
}
return text;
}
});
i think you can take advantage of the visible binding to show/hide the button based on your criteria
function PageViewModel() {
var self = this;
self.buttonVisible = ko.observable(true);
self.changeButtonText = function() {
self.ProcurementbuttonText(!self.ProcurementbuttonText());
}
self.ProcurementbuttonText = ko.observable(false);
self.buttonText = ko.pureComputed(function() {
return self.ProcurementbuttonText() ? "Save" : "Edit";
});
self.isButtonVisible = ko.computed(function() {
//do some your logic here. just need to return a true or false value;
return self.buttonVisible();
});
self.labelText = ko.pureComputed(function() {
var messageStart = "click to ";
var state = self.buttonVisible() ? 'hide' : 'show';
var messageEnd = " button";
return messageStart + state + messageEnd;
});
}
ko.applyBindings(new PageViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<button class="btn actionButtonIcon" id="DashboardEdit" data-bind="click: changeButtonText, visible: isButtonVisible, text: buttonText">
Click me.
</button>
<br/>
<br/>
<label><span data-bind="text: labelText " ></span>
<input type="checkbox" data-bind="checked: buttonVisible" />
</label>
If you have Durandal's router plugin installed and configured, you can also use the activeInstruction() observable to get info about the current route. You can then use this in your computed to check if the current fragment matches your page route.
More info here: http://durandaljs.com/documentation/api#class/Router/property/activeInstruction
Yesterday I make this question:
How can I refresh or load JSON to my viewModel on Knockout JS with complex models
Everything works OK with the fixes but when I try to use a complex json to load in the viewModel some of the buttons (specifically on Groups) doesn't work.
To resume the problem. I have a json with the previous serialized data. I use that json to fill the viewModel, this works, load correctly the data but the problem are in the "group" template, because the data is loaded but the buttons doesn't work, the only button which is working is the "remove group".
(Please refer to the image)
Any idea to fix this? Thanks.
Jsfiddle example with the problem
http://jsfiddle.net/y98dvy56/26/
!Check this picture.
The red circles indicates the buttons with problems.
The green circles indicates the buttons without problems.
Here is the body html
<div class="container">
<h1>Knockout.js Query Builder</h1>
<div class="alert alert-info">
<strong>Example Output</strong><br/>
</div>
<div data-bind="with: group">
<div data-bind="template: templateName"></div>
</div>
<input type="submit" value="Save" data-bind="click: Save"/>
</div>
<!-- HTML Template For Conditions -->
<script id="condition-template" type="text/html">
<div class="condition">
<select data-bind="options: fields, value: selectedField"></select>
<select data-bind="options: comparisons, value: selectedComparison"></select>
<input type="text" data-bind="value: value"></input>
<button class="btn btn-danger btn-xs" data-bind="click: $parent.removeChild"><span class="glyphicon glyphicon-minus-sign"></span></button>
</div>
</script>
<!-- HTML Template For Groups -->
<script id="group-template" type="text/html">
<div class="alert alert-warning alert-group">
<select data-bind="options: logicalOperators, value: selectedLogicalOperator"></select>
<button class="btn btn-xs btn-success" data-bind="click: addCondition"><span class="glyphicon glyphicon-plus-sign"></span> Add Condition</button>
<button class="btn btn-xs btn-success" data-bind="click: .addGroup"><span class="glyphicon glyphicon-plus-sign"></span> Add Group</button>
<button class="btn btn-xs btn-danger" data-bind="click: $parent.removeChild"><span class="glyphicon glyphicon-minus-sign"></span> Remove Group</button>
<div class="group-conditions">
<div data-bind="foreach: children">
<div data-bind="template: templateName"></div>
</div>
</div>
</div>
</script>
<!-- js -->
<script src="js/vendor/knockout-2.2.1.js"></script>
<script src="js/vendor/knockout-mapping.js"></script>
<script src="js/condition.js"></script>
<script src="js/group.js"></script>
<script src="js/viewModel.js"></script>
<script>
window.addEventListener('load', function(){
var json =
{"group":{"templateName":"group-template","children":[{"templateName":"condition-template","fields":["Points","Goals","Assists","Shots","Shot%","PPG","SHG","Penalty Mins"],"selectedField":"Points","comparisons":["=","<>","<","<=",">",">="],"selectedComparison":"=","value":0,"text":"Points = 0"},{"templateName":"condition-template","fields":["Points","Goals","Assists","Shots","Shot%","PPG","SHG","Penalty Mins"],"selectedField":"Points","comparisons":["=","<>","<","<=",">",">="],"selectedComparison":"=","value":0,"text":"Points = 0"},{"templateName":"condition-template","fields":["Points","Goals","Assists","Shots","Shot%","PPG","SHG","Penalty Mins"],"selectedField":"Points","comparisons":["=","<>","<","<=",">",">="],"selectedComparison":"=","value":0,"text":"Points = 0"},{"templateName":"group-template","children":[{"templateName":"condition-template","fields":["Points","Goals","Assists","Shots","Shot%","PPG","SHG","Penalty Mins"],"selectedField":"Points","comparisons":["=","<>","<","<=",">",">="],"selectedComparison":"=","value":0,"text":"Points = 0"},{"templateName":"condition-template","fields":["Points","Goals","Assists","Shots","Shot%","PPG","SHG","Penalty Mins"],"selectedField":"Points","comparisons":["=","<>","<","<=",">",">="],"selectedComparison":"=","value":0,"text":"Points = 0"},{"templateName":"condition-template","fields":["Points","Goals","Assists","Shots","Shot%","PPG","SHG","Penalty Mins"],"selectedField":"Points","comparisons":["=","<>","<","<=",">",">="],"selectedComparison":"=","value":0,"text":"Points = 0"}],"logicalOperators":["AND","OR"],"selectedLogicalOperator":"AND","text":"(Points = 0 AND Points = 0 AND Points = 0)"}],"logicalOperators":["AND","OR"],"selectedLogicalOperator":"AND","text":"(Points = 0 AND Points = 0 AND Points = 0 AND (Points = 0 AND Points = 0 AND Points = 0))"},"text":"(Points = 0 AND Points = 0 AND Points = 0 AND (Points = 0 AND Points = 0 AND Points = 0))"};
var vm = new QueryBuilder.ViewModel();
ko.mapping.fromJS(json.group, {}, vm.group);
ko.applyBindings(vm);
}, true);
</script>
Condition.js:
window.QueryBuilder = (function(exports, ko){
function Condition(){
var self = this;
self.templateName = 'condition-template';
self.fields = ko.observableArray(['Points', 'Goals', 'Assists', 'Shots', 'Shot%', 'PPG', 'SHG', 'Penalty Mins']);
self.selectedField = ko.observable('Points');
self.comparisons = ko.observableArray(['=', '<>', '<', '<=', '>', '>=']);
self.selectedComparison = ko.observable('=');
self.value = ko.observable(0);
}
exports.Condition = Condition;
return exports;
})(window.QueryBuilder || {}, window.ko);
Group.js
window.QueryBuilder = (function(exports, ko){
var Condition = exports.Condition;
function Group(){
var self = this;
self.templateName = 'group-template';
self.children = ko.observableArray();
self.logicalOperators = ko.observableArray(['AND', 'OR']);
self.selectedLogicalOperator = ko.observable('AND');
// give the group a single default condition
self.children.push(new Condition());
self.addCondition = function(){
self.children.push(new Condition());
};
self.addGroup = function(){
self.children.push(new Group());
};
self.removeChild = function(child){
self.children.remove(child);
};
}
exports.Group = Group;
return exports;
})(window.QueryBuilder || {}, window.ko);
ViewModel.js
window.QueryBuilder = (function(exports, ko){
var Group = exports.Group;
function ViewModel() {
var self = this;
self.group = ko.observable(new Group());
self.load = function (data) {
ko.mapping.fromJS(data, self);
}
self.Save = function () {
console.log(ko.toJSON(self));
}
}
exports.ViewModel = ViewModel;
return exports;
})(window.QueryBuilder || {}, window.ko);
Your issue is caused by the fact that the mapping plugin makes your data observable, but doesn't augment your data with the functions in your model such as the add, remove, etc... functions. If you do a console log for the json data when it's inserted into the view model you will notice that the data is observable but the functions are missing. You need to provide a mapping to customize your Group, Condition, etc.. constructors. Because the children array in your case is of mixed types (condition or group) Here is a custom mapping to take care of that:
var childrenMapping = {
'children': {
create: function(options) {
var data = options.data;
console.log(data);
var object;
switch(data.templateName) {
case 'condition-template':
object = new QueryBuilder.Condition(data);
break;
case 'group-template':
object = new QueryBuilder.Group(data);
break;
}
return object;
}
}
};
Then you simply need to provide this mapping in your initial mapping
ko.mapping.fromJS(json.group, childrenMapping, vm.group);
Then inside the constructor of the Group object:
function Group(data){
var self = this;
self.templateName = 'group-template';
...
ko.mapping.fromJS(data, childrenMapping, this);
}
You also need to update the Condition constructor to accept the data provided by the mapping, but since conditions don't have children you do not need to provide the childrenMapping here:
function Condition(data){
var self = this;
self.templateName = 'condition-template';
...
ko.mapping.fromJS(data, {}, this);
}
I've the mapping at the end of both function so that the mapped values override you initial value.
The updated jsfiddle here:
http://jsfiddle.net/omerio/y98dvy56/32/
This answer is related:
knockout recursive mapping issue
I have this div which displays a letter, but I want to add an if statement of when to show this div based on the following condition:
if usersCount() > 3 then show letter
<div class=" header" id="letter" data-bind="text: Letter">
....
</div>
How could i add the if statement along with the text - expression statement?
data-bind="if: UserCount() > 13 then text:Letter"` ....??
var userViewModel = function (data) {
var _self = this;
_self.Letter = ko.observable(data.Letter);
};
var roleViewModel = function (data) {
var _self = this;
_self.UserCount = ko.observable(data.UserCount);
};
Check out the Visible Binding. You'll want to create a property in you View Model to handle the logic of hiding/showing the div. Here is a JSFiddle to demonstrate.
<div data-bind="visible: shouldShowMessage, text: Letter">
</div>
<script type="text/javascript">
var viewModel = function(){
var self = this;
self.Letter = ko.observable('Hello, World!');
self.UserCount = ko.computed(function() {
return Math.floor((Math.random() * 20) + 1);
});
self.shouldShowMessage = ko.computed(function() {
return (self.UserCount() > 13);
});
};
</script>