I'm making a system that allows reorder of products. Each display area in a shop is a unit, which has several groups which (in turn) house several products. I am currently using knockout to load a unit via a select element, then cycling through the groups providing quantity fields for products.
This all works, but I need a select all button which sets all products in a group to have a quantity of 1. I have given the group object a function (allForOne()) which should cycle through each product in the group's observable array and set this to one, however, no matter how I bind this to the #allInOne button, it always applies this to the last group in the unit's observable group array.
For example, when the code first loads, I want to select all in the first group and make their quantity one, but it only changes the quantity in the last group.
I use the same selector in the same binding to "alert" the correct unit name (based on the unit's groupCounter property) before I call the function, but it returns two different values.
Why is it not affecting the current group? I can't work this out for the life of me and Google hasn't been much help.
Here is a JSFiddle, or you can see the code below:
Here is the view:
<div class="select-container">
<label for="unit" class="label">Unit</select>
<select name="unit" class="select-unit drop-shadow" data-bind='options: units, optionsText: "skuName", optionsCaption: "Select...", value: unit'></select>
<div>
</div>
<div class="unit-image" data-bind="with: unit">
<img data-bind="attr{ src: $parent.unit().image, alt: $parent.unit().name }">
</div>
<div data-bind="with: unit" class="clearfix products-container">
<div class="clearfix" data-bind="with: $parent.groups()[$parent.groupCounter()]">
<!--<div data-bind="style: { width: (((parseInt(limit) / parseInt($root.totalLimit)) * 100) - 1)+'%', marginRight: '0.5%', marginLeft: '0.5%'}" style="display: block; position: relative; float: left;">-->
<h2 data-bind="text: name" style="margin-bottom: 12px;"></h2>
<div data-bind="foreach: {data: products/*, beforeRemove: hideProduct, afterRender: showProduct*/}">
<div class="prod-page-product drop-shadow" data-bind="style: { width: ((100 / $parent.limit) - 1)+'%', marginRight: '0.5%', marginLeft: '0.5%'}">
<p><span data-bind='text: sku'></span></p>
<div>
<div class="add-container">+</div>
<div><input type="number" class="is-numeric" min="0" data-bind='value: quantity, valueUpdate: "afterkeydown"' /></div>
<div class="remove-container">-</div>
</div>
</div>
</div>
<!--</div>-->
<div class="clearfix">
<button id="nextProdGroup" class="products-button float-left" data-bind="enable:$root.firstGroupBool, click: $root.prevGroup, style: { width: productWidth, maxWidth: ((100/4) - 1)+'%', marginRight: '0.5%', marginLeft: '0.5%'}">Prev Group</button>
<button id="prevProdGroup" class="products-button float-left" data-bind="enable:$root.nextGroupBool, click: $root.nextGroup, style: { width: productWidth, maxWidth :((100/4) - 1)+'%', marginRight: '0.5%', marginLeft: '0.5%'}">Next Group</button>
<!--This is the offending button binding-->
<button id="allForOne" class="products-button float-left" data-bind="click: function() { alert('Expected Group: '+$root.groups()[$root.groupCounter()].name()); $root.groups()[$root.groupCounter()].allForOne()}, style: { width: productWidth, maxWidth :((100/4) - 1)+'%', marginRight: '0.5%', marginLeft: '0.5%'}">This is what messes up</button>
</div>
</div>
</div>
<div id="shadow-overlay" class="ui-overlay" data-bind="visible: loaderBool">
<div class="ui-widget-overlay"></div>
<div id="top-overlay" class="ui-overlay" style="width: 50%; height: 80%; position: absolute; left: 25%; top: 10%;"></div>
<div id="ajax-loading-container">
<p class="ajax-loader-text">Loading...</p>
</div>
Here is the viewModel:
var Product = function(id, sku) {
var self = this;
//Properties
self.id = ko.observable(id);
self.sku = ko.observable(sku);
self.quantity = ko.observable(0);
//Methods
self.incrementQuantity = function(product) {
var previousQuantity = parseInt(self.quantity());
self.quantity(previousQuantity+1);
};
self.decrementQuantity = function(product) {
var previousQuantity = parseInt(self.quantity());
if(self.quantity() > 0)
{
self.quantity(previousQuantity-1);
}
};
};
//The object with the called function
var Group = function(name, limit, productList)
{
self = this;
//Properties
self.name = ko.observable(name);
self.nametwo = name;
self.limit = limit;
self.products = ko.observableArray();
self.productWidth = ko.pureComputed(function(a, b)
{
return ((100 / limit) - 1)+'%';
});
//Methods
//---------The offending function
self.allForOne = function() {
alert("Returned Group: "+self.name());
ko.utils.arrayForEach(self.products(), function(product) {
product.quantity(1);
});
};
//Initial population
$.each(productList, function(key, product) {
self.products.push(new Product(product.id, product.sku));
});
}
var Unit = function() {
var self = this;
//Properties
self.unit = ko.observable();
self.groups = ko.observableArray();
self.groupCounter = ko.observable(0);
self.lastGroup = ko.observable(true);
self.totalLimit = 0;
self.saved = true;
self.loaderBool = ko.observable(false);
self.firstGroupBool = ko.pureComputed(function()
{
return (self.groupCounter() < 1 ? false : true);
});
self.nextGroupBool = ko.pureComputed(function()
{
return (self.lastGroup() == true ? false : true);
});
self.unit.subscribe(function() {
self.populateGroup();
});
//Methods
self.onLoadCheck = (function() {
if(units.length == 1)
{
self.unit(units[0]);
}
});
self.populateGroup = function() {
self.loaderBool(true);
self.groups([]);
//setTimeout(function() {
self.TotalLimit = 0;
$.each(self.unit().group, function(groupKey, groupVal) {
//setTimeout(function() {
self.groups.push(new Group(groupVal.name, groupVal.limit, groupVal.product));
//}, 0);
self.totalLimit += parseInt(groupVal.limit);
});
self.groupCounter(0);
self.lastGroup(false);
self.loaderBool(false);
//}, 0);
console.log(self.groups());
console.log(self.groups()[self.groupCounter()]);
};
self.prevGroup = function(a, b) {
self.save(a, b);
var groupCounter = parseInt(self.groupCounter());
self.groupCounter(groupCounter-1);
if(self.groupCounter() != self.groups().length-1)
{
self.lastGroup(false);
}
};
self.nextGroup = function(a, b) {
self.save(a, b);
var groupCounter = parseInt(self.groupCounter());
self.groupCounter(groupCounter+1);
if(self.groupCounter() == self.groups().length-1)
{
self.lastGroup(true);
}
};
self.save = function(a, b) {
var productsToSave = self.groups()[self.groupCounter()].products();
var dataToSave = $.map(productsToSave, function(line) {
return {
id: line.id(),
quantity: line.quantity()
}
});
if(productsToSave.length == 0)
{
dialog("Error", "You cannot submit before you add products.");
return false;
}
else
{
var caller = $(b.toElement);
//sendOrder("baskets/update", {products: dataToSave}, caller);
if(caller.attr("id") == "submitProdGroup")
{
window.location = baseUrl+"basket";
}
}
};
};
Take a look at this: http://jsfiddle.net/rks5te7v/5/ I rewrote your code using a viewmodel like this (look at the url above):
function ViewModel() {
var self = this;
self.units = ko.observableArray();
self.chosenUnit = ko.observable();
self.chosenGroup = ko.observable(0);
self.goToNextGroup = function () {
self.chosenGroup(parseInt(self.chosenGroup()) + 1);
};
self.goToPrevGroup = function () {
self.chosenGroup(parseInt(self.chosenGroup()) - 1);
};
self.setGroupAs1 = function () {
var products = self.chosenUnit().groups()[self.chosenGroup()].products();
ko.utils.arrayForEach(products, function (product) {
product.quantity(1);
});
};
//loading data
var product1 = new Product('1', 'P1');
var product2 = new Product('2', 'P2');
var product3 = new Product('3', 'P3');
var product4 = new Product('4', 'P4');
var group1 = new Group('G1', [product1, product2]);
var group2 = new Group('G2', [product3, product4]);
self.units.push(new Unit('Unit 1', [group1, group2]));
};
Does it solve your problem? :)
Related
ng-click inside ui-gmap-windows not working...
Here is codepen link http://codepen.io/aoakeson/pen/ZYLJeyhttp://codepen.io/aoakeson/pen/ZYLJey
Any suggestion as how this issue is to be solved....
Here is html code:
<ui-gmap-google-map center='map.center' zoom='map.zoom' draggable="true">
<ui-gmap-markers models="randomMarkers" coords="'self'" icon="'icon'" click="'onClick'">
<ui-gmap-windows show="show" ng-cloak>
<div class="lp_tuxtla" >
<div >
<a ng-click="nextpage()">click here</a>
<h3 ng-non-bindable >{{title}}</h3>
<h4 ng-non-bindable>{{loc}}</h4>
</div>
<span class="right_arw"></span>
<span class="down_arw"></span>
</div>
<!-- <div ng-non-bindable >{{title}}</div>-->
</ui-gmap-windows>
</ui-gmap-markers>
</ui-gmap-google-map>
Here javascript code:
$scope.map = {center: {latitude: 40.1451, longitude: -99.6680 }, zoom: 4, bounds: {}};
$scope.options = {scrollwheel: false};
var createRandomMarker = function (i, bounds, idKey) {
var lat_min = bounds.southwest.latitude,
lat_range = bounds.northeast.latitude - lat_min,
lng_min = bounds.southwest.longitude,
lng_range = bounds.northeast.longitude - lng_min;
if (idKey == null) {
idKey = "id";
}
var latitude = lat_min + (Math.random() * lat_range);
var longitude = lng_min + (Math.random() * lng_range);
var ret = {
latitude: latitude,
longitude: longitude,
title: 'm' + i,
show: false
};
ret.onClick = function() {
console.log("Clicked!");
ret.show = !ret.show;
};
ret[idKey] = i;
return ret;
};
$scope.randomMarkers = [];
// Get the bounds from the map once it's loaded
$scope.$watch(function() { return $scope.map.bounds; }, function(nv, ov) {
// Only need to regenerate once
if (!ov.southwest && nv.southwest) {
var markers = [];
for (var i = 0; i < 50; i++) {
markers.push(createRandomMarker(i, $scope.map.bounds))
}
$scope.randomMarkers = markers;
}
}, true);
Somehow, its not able to reach the 'windowClicked' method in the hierarchy.
Create the method on rootScope and use it in the tempalte.
In Controller
$scope.$root.windowClicked = function () {alert('here')}
In Markup
<a ng-click="$root.windowClicked()">test</a>
Here is the updated pen. http://codepen.io/anon/pen/qEKjjb
First way
You can use your $scope declared variables in ng-click using $parent:
<button ng-click="$parent.nextPage()">next</button>
Here is a working plunker.
Second way
You can assign a controller to the div containing the window.
<div ng-controller="mainCtrl">
<button ng-click="nextPage()">next</button>
</div>
Here is another working plunker
Credits
People in Github issue
Most likely it is the same issue that has been discussed in this thread.
Apparently it occurs since nextpage event declared in controller scope is not accessible from the ui-gmap-windows child scope.
The following solution worked for me:
First we need to introduce an additional controller:
appMaps.controller('infoWindowCtrl', function($scope) {
$scope.showInfo = function() {
console.log('Button clicked!');
}
});
and then specify the following layout:
<ui-gmap-windows show="show">
<div ng-controller="infoWindowCtrl">
<span ng-non-bindable>{{title}}</span>
<button ng-click="showInfo()">Show info</button>
</div>
</ui-gmap-windows>
Working example
var appMaps = angular.module('appMaps', ['uiGmapgoogle-maps']);
appMaps.controller('mainCtrl', function($scope,uiGmapIsReady) {
$scope.map = { center: { latitude: 40.1451, longitude: -99.6680 }, zoom: 4, bounds: {} };
$scope.options = { scrollwheel: false };
var getRandomLat = function() {
return Math.random() * (90.0 + 90.0) - 90.0;
};
var getRandomLng = function () {
return Math.random() * (180.0 + 180.0) - 180.0;
};
var createRandomMarker = function(i) {
var ret = {
latitude: getRandomLat(),
longitude: getRandomLng(),
title: 'm' + i,
show: false,
id: i
};
return ret;
};
$scope.onClick = function(marker, eventName, model) {
logInfo("Marker clicked!");
model.show = !model.show;
};
$scope.markers = [];
for (var i = 0; i < 200; i++) {
$scope.markers.push(createRandomMarker(i));
}
});
appMaps.controller('infoWindowCtrl', function($scope) {
$scope.showInfo = function() {
logInfo('Button clicked!');
}
});
function logInfo(message){
console.log(message);
document.getElementById('output').innerHTML += message;
}
html, body, #map_canvas {
height: 100%;
width: 100%;
margin: 0px;
}
#map_canvas {
position: relative;
}
.angular-google-map-container {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
}
<script src="https://code.angularjs.org/1.3.14/angular.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.js"></script>
<script src="http://rawgit.com/angular-ui/angular-google-maps/2.0.X/dist/angular-google-maps.js"></script>
<div ng-app="appMaps" id="map_canvas" ng-controller="mainCtrl">
<ui-gmap-google-map center="map.center" zoom="map.zoom" draggable="true" options="options" bounds="map.bounds">
<ui-gmap-markers models="markers" coords="'self'" icon="'icon'" click="onClick">
<ui-gmap-windows show="show">
<div ng-controller="infoWindowCtrl">
<span ng-non-bindable>{{title}}</span>
<button ng-click="showInfo()">Show info</button>
</div>
</ui-gmap-windows>
</ui-gmap-markers>
</ui-gmap-google-map>
</div>
<pre id="output"></pre>
Plunker
I have implemented a slider for height in my application.
When I slide the min and max values, it will go up to 0.9 and on to 1.0 because it's a decimal value. I want it to go up to 0.11 and at 0.12 it will convert to 1.0, because I want height in feet and inches.(1 Feet = 12 inches).
I have implemented knockoutjs for it as below :
ko.bindingHandlers.TwoSideSlider = {
init: function (element, valueAccessor, allBindingsAccessor) {
var options = allBindingsAccessor().sliderOptions || {};
var sliderValues = ko.utils.unwrapObservable(valueAccessor());
if (sliderValues.min !== undefined) {
options.range = true;
}
options.slide = function (e, ui) {
if (sliderValues.min) {
console.log(ui.values[0]);
sliderValues.min(ui.values[0]);
sliderValues.max(ui.values[1]);
} else {
sliderValues.value(ui.value);
}
};
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).slider("destroy");
});
$(element).slider(options);
},
update: function (element, valueAccessor) {
var sliderValues = ko.toJS(valueAccessor());
if (sliderValues.min !== undefined) {
$(element).slider("values", [sliderValues.min, sliderValues.max]);
} else {
$(element).slider("value", sliderValues.value);
}
}
};
};
And my Html Code is :
<div class="slider-control">
<div data-bind="TwoSideSlider: { min: MinHeight, max: MaxHeight }, sliderOptions: {min: 0, max: 15, step: 0.1}"></div>
<div style="top:-12px;" data-bind="TwoSideSlider: { value: MaxHeight }, sliderOptions: {min: 0, max: 15, step: 0.1}"></div>
</div>
Can anyone advise how I can achieve this?
Decimal values are base 10, so you won't be able to do what you are trying to do without writing some custom code to convert decimal values to feet and inches.
So ideally, you need 1.5 to equal 1ft 6in.
If you use computed values based on an observable, you can just perform simple maths to convert the values for you when the slider moves. For example you can get feet and inches from 1.5 like so:
// Round 1.5 down to nearest whole number
Math.floor(1.5); // would return 1 (feet)
// Take the value after decimal point, multiply by 12 & round to a whole number
Math.round(((1.5) % 1) * 12); // would return 0.5 * 12 = 6 (inches)
Integrating that in to a computed is demonstrated on the snippet below:
ko.bindingHandlers.slider = {
init: function(element, valueAccessor, allBindingsAccessor) {
var options = allBindingsAccessor().sliderOptions || {};
var observable = valueAccessor();
if (observable().splice) {
options.range = true;
}
options.slide = function(e, ui) {
observable(ui.values ? ui.values : ui.value);
};
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).slider("destroy");
});
$(element).slider(options);
},
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
$(element).slider(value.slice ? "values" : "value", value);
}
};
ViewModel = function() {
this.values = ko.observable([1.9, 15.0]);
this.valuesJson = ko.computed(function() {
return ko.toJSON({
values: this.values
});
}, this);
this.firstFeet = ko.computed(function() {
return Math.floor(this.values().slice(0, 1));
}, this);
this.firstInches = ko.computed(function() {
return Math.round(((this.values().slice(0, 1)) % 1) * 12);
}, this);
this.lastFeet = ko.computed(function() {
return Math.floor(this.values().slice(1, 2));
}, this);
this.lastInches = ko.computed(function() {
return Math.round(((this.values().slice(1, 2)) % 1) * 12);
}, this);
}
ko.applyBindings(new ViewModel());
body,
html {
font-family: Verdana;
font-size: 8pt;
}
.slider {
width: 60%;
margin: 20px auto;
}
<link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.10/themes/base/jquery-ui.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.9.2/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class="slider" data-bind="slider: values, sliderOptions: {min: 1.0, max: 15.0, step: 0.05}"></div>
<span data-bind="text: valuesJson"></span>
<hr />
<p>Min: <span data-bind="text: firstFeet"></span>ft
<span data-bind="text: firstInches"></span>in</p>
<p>Max: <span data-bind="text: lastFeet"></span>ft
<span data-bind="text: lastInches"></span>in</p>
<hr />
<p>Min: <span data-bind="text: firstFeet"></span>.<span data-bind="text: firstInches"></span>
</p>
<p>Max: <span data-bind="text: lastFeet"></span>.<span data-bind="text: lastInches"></span>
</p>
Here is my first template:
<script type="text/html" id="Testing">
<!-- ko if: $data.CLASSIFICATION_NAME().toLowerCase() == 'general' -->
<!-- ko if: $data.ATTRIBUTE_DISPLAY_TYPE().toLowerCase() == AttributeEnum.NumberRange -->
<li>
<div class="divFilterRow">
<div class="divFilterCol">
<input type="checkbox" data-bind="checked: ISATTRIBUTESELECTED, attr:{'id': ATTRIBUTE_NAME}, click: function(){checkedCallback($data); return true; }" />
</div>
<div class="divFilterCol divFilterCph">
<label data-bind=" text: ATTRIBUTE_NAME, attr:{'for': ATTRIBUTE_NAME,'attributetooltip': ATTRIBUTE_DESCRIPTION}"></label>
<a class="iClose" href="javascript:void(0);" data-bind=" click: $data.removeSelectedAttribute , style: {display: ISATTRIBUTESELECTED() ? 'block' : 'none'}"></a>
</div>
</div>
<div class="iClearBoth" />
<div data-bind=" visible: ISATTRIBUTESELECTED">
<div data-bind=" template: {name: 'sliderTemplate',foreach: filterSliderValues} "></div>
</div>
</li>
<!-- /ko -->
<!-- ko if: $data.ATTRIBUTE_DISPLAY_TYPE().toLowerCase() == AttributeEnum.MultipleChoiceList -->
<li>
<div class="divFilterRow">
<div class="divFilterCol">
<input type="checkbox" data-bind="checked: ISATTRIBUTESELECTED, attr:{'id': ATTRIBUTE_NAME}, click: function(){checkedCallback($data); return true; }" />
</div>
<div class="divFilterCol divFilterCph">
<label data-bind="text: ATTRIBUTE_NAME, attr:{'for': ATTRIBUTE_NAME,'attributetooltip': ATTRIBUTE_DESCRIPTION }"></label>
<a class="iClose" href="javascript:void(0);" data-bind="click: $data.removeSelectedAttribute , style: {display: ISATTRIBUTESELECTED() ? 'block' : 'none'}"></a>
</div>
</div>
<div class="iClearBoth" />
<div data-bind="visible: ISATTRIBUTESELECTED">
<div data-bind=" template: {name: 'sliderTemplate',foreach: filterSliderValues} "></div>
</div>
<div data-bind="visible: ISATTRIBUTESELECTED">
<div data-bind="dialog: {'autoOpen': false, 'title': ATTRIBUTE_NAME,'modal': true,
'resizable': false, width: 950, draggable: false, closeOnEscape: false
#*,buttons: {
'Apply': function () {
$(this).dialog('close');
}
} *#
},
dialogVisible: isDialogOpen"
class="iSmallDialog" >
<div class="HelpBar" style="display: block; margin-bottom: 12px;">
<div class="divHelpContent" data-bind="text: ATTRIBUTE_DESCRIPTION">
</div>
</div>
<div style="padding-top: 5px; padding-bottom: 10px;">
Please select options from the list below:
</div>
<div style="overflow-y: auto; max-height: 200px;" class="ListOptions">
<ul data-bind="sortable: { template: 'TestingTemplate', data: filterListOptions },class:'ui-sortable'"></ul>
</div>
<div class="iDialogSubmit">
<button data-bind="click: $data.onDialogCloseCallback,enable: isOptionsSelected" class="active">TestingApply</button>
<button class="btnInactive" data-dismiss="modal" data-bind="click: $data.onDialogClose" aria-hidden="true">TestingCancel</button>
</div>
</div>
</div>
</li>
<!-- /ko -->
<!-- /ko -->
</script>
Here is my second template:
<script id="TestingTemplate" type="text/html">
<li><label data-bind="text: Value, attr:{'for': Value().replace(/ /g,'_')}"></label></li>
</script>
Here is the way i call it:
<div class="LHSListItem">General</div>
<div class="contentDivWithPadding fixHeightAccordian">
<div class="divAccordianContent" id="divAttributes_General">
<ul data-bind="template: { name : 'Testing', foreach : filterData} "></ul>
</div>
</div>
</li>
Here is my output:
When i drag Active or Out of Study,output becomes as follow:
Here is My Model:
var extendedAttributeMapping = {
//'ignore': ["ISATTRIBUTESELECTED"],
create: function (options) {
var extendedAttributeModel = ko.mapping.fromJS(options.data);
//drop down for SECTION attribute
extendedAttributeModel.selectedFilterStateforSection = ko.observable();
// extendedAttributeModel.sectionCounter = ko.observable("2509");
extendedAttributeModel.removeSelectedAttribute = function (modelObject) {
modelObject.ISATTRIBUTESELECTED(false);
if(modelObject.ATTRIBUTE_DISPLAY_TYPE().toLowerCase() == AttributeEnum.MultipleChoiceList)
{
modelObject.isDialogOpen(false);
extendedAttributeModel.onAttributeUncheckCallback(modelObject);
//modelObject.OptionsCountLabel("");
//modelObject.isOptionsSelected(false);
//modelObject.OptionsSelectedCount(0);
}
if(modelObject.ATTRIBUTE_DISPLAY_TYPE().toLowerCase() == AttributeEnum.NumberRange)
{
extendedAttributeModel.resetSlider(modelObject);
}
sltModel.getsectionCount();
$(document).trigger('OnApplyAttribute');
};
extendedAttributeModel.resetSlider = function(modelObject){
var minCurrentVal = modelObject.filterSliderValues()[0].MINCURRENT();
var maxCurrentVal = modelObject.filterSliderValues()[0].MAXCURRENT();
modelObject.filterSliderValues()[0].min(minCurrentVal);
modelObject.filterSliderValues()[0].max(maxCurrentVal);
};
//jquery UI Dialog custom Helper events
extendedAttributeModel.onDialogCloseCallback = function(modelObject){
//alert("Callback");
//debugger;
modelObject.isDialogOpen(false);
if(modelObject.ATTRIBUTE_CODE().toLowerCase() == "section")
{
extendedAttributeModel.getSectionAttributeOptionsOnApply(modelObject);
}
else
{
extendedAttributeModel.getOptionsOnApply(modelObject);
}
sltModel.getsectionCount();
extendedAttributeModel.setDefaultModified(modelObject);
$(document).trigger('OnApplyAttribute');
};
extendedAttributeModel.onDialogOpen = function(){
extendedAttributeModel.isDialogOpen(true);
//modelObject.isDialogOpen(true);
//modelObject.ISATTRIBUTESELECTED(true);
};
//SECTION ATTRIBUTE IMPLEMENETATION- EXTENDED MODEL
//SECTION CHECKBOXES SELECTION
extendedAttributeModel.getSectionAttributeOptionsOnApply = function(modelObject) {
var totalSelected = 0;
var selectedAttributeID = modelObject.ATTRIBUTE_ID();
ko.utils.arrayForEach(modelObject.filterddlSectionListOptions(), function(outeritem) {
// sltModel.setSectionAttributeOriginalStateName(outeritem);
if(outeritem.sectionList().length > 0)
{
ko.utils.arrayForEach(outeritem.sectionList(),function(item){
if (item.isSectionSelected()) {
totalSelected++;
// outeritem.isSelected(true);
//highlight selected states
}
item.SavedSectionCheckedState(item.isSectionSelected());
});
}
});
extendedAttributeModel.OptionsCountLabel("(" + totalSelected + ")");
extendedAttributeModel.OptionsSelectedCount(totalSelected);
if(totalSelected > 0)
extendedAttributeModel.isOptionsSelected(true);
else
{
extendedAttributeModel.isOptionsSelected(false);
extendedAttributeModel.ISATTRIBUTESELECTED(false);
}
};
//ali method code starts
extendedAttributeModel.isTaskSelected = function(task) {
return task === self.selectedTask();
};
//ali method code ends
extendedAttributeModel.OnSectionAttributeOptionSelect = function(modelObject,parentObject){
//SavedSectionCheckedState
//isSectionModified
//isSectionSelected
//sectionId
//sectionName
//stateCode
modelObject.isSectionModified(true);
var totalSelected=0;
ko.utils.arrayForEach(parentObject.filterddlSectionListOptions(), function(outeritem) {
if(outeritem.sectionList().length > 0)
{
ko.utils.arrayForEach(outeritem.sectionList(),function(item){
if (item.isSectionSelected()) {
totalSelected++;
}
});
}
});
//sections selected per state
var sectionSelectedPerState=0;
ko.utils.arrayForEach(parentObject.filterddlSectionListOptions()[sltModel.SectionAttributeStateIndex()].sectionList(), function(item) {
if (item.isSectionSelected()) {
sectionSelectedPerState++;
}
});
parentObject.filterddlSectionListOptions()[sltModel.SectionAttributeStateIndex()].sectionSelectedCount = (sectionSelectedPerState);
var indexCounter = parentObject.filterddlSectionListOptions()[sltModel.SectionAttributeStateIndex()].Text().indexOf("(");
var stateNameWithCount = 0;
if(indexCounter > -1)
{
stateNameWithCount = parentObject.filterddlSectionListOptions()[sltModel.SectionAttributeStateIndex()].Text().substring(0,indexCounter);
}
else{
stateNameWithCount = parentObject.filterddlSectionListOptions()[sltModel.SectionAttributeStateIndex()].Text();
}
if(sectionSelectedPerState == 0)
parentObject.filterddlSectionListOptions()[sltModel.SectionAttributeStateIndex()].Text($.trim(stateNameWithCount));
else
parentObject.filterddlSectionListOptions()[sltModel.SectionAttributeStateIndex()].Text($.trim($.trim(stateNameWithCount) + " ("+sectionSelectedPerState+")"));
//sections selected per state
if(totalSelected > 0)
{
parentObject.isOptionsSelected(true);
}
else
{parentObject.isOptionsSelected(false);}
};
extendedAttributeModel.getSectionAttributeOptionsSelectedCount = function(modelObject) {
var totalSelected = 0;
var perStateCounter = 0;
var selectedAttributeID = modelObject.ATTRIBUTE_ID();
ko.utils.arrayForEach(modelObject.filterddlSectionListOptions(), function(outeritem) {
if(outeritem.sectionList().length > 0)
{
ko.utils.arrayForEach(outeritem.sectionList(),function(item){
//if user unchecks checked and press cancel
if(item.isSectionModified()) {
item.isSectionSelected(item.SavedSectionCheckedState());
//if(!item.SavedSectionCheckedState())
//sltModel.setSectionAttributeOriginalStateName(outeritem);
}
if (item.isSectionSelected()) {
totalSelected++;
perStateCounter++;
}
});
}
sltModel.setSectionAttributeOriginalStateName(outeritem,perStateCounter);
perStateCounter = 0;
});
//setting the state name for selected sections
extendedAttributeModel.OptionsCountLabel("(" + totalSelected + ")");
extendedAttributeModel.OptionsSelectedCount(totalSelected);
if(totalSelected > 0)
extendedAttributeModel.isOptionsSelected(true);
else
{
extendedAttributeModel.isOptionsSelected(false);
extendedAttributeModel.ISATTRIBUTESELECTED(false);
}
};
//END OF SECTION ATTRIBUTE IMPLEMENETATION- EXTENDED MODEL
extendedAttributeModel.OnOptionSelect = function(modelObject,parentObject){
modelObject.isModified(true);
var totalSelected=0;
ko.utils.arrayForEach(parentObject.filterListOptions(), function(item) {
if(modelObject.childObjects()==null)
{
if (item.isSelected()) {
totalSelected++;
}
}
});
//Get All the child elements for the selected item
//if the selected option is checked then check all the child options
//else vice versa
if(modelObject.childObjects()!=null){
var isChecked = modelObject.isSelected();
ko.utils.arrayForEach(modelObject.childObjects(), function(item) {
//item.isModified(true);
item.isSelected(isChecked);
totalSelected++;
});
}
if(totalSelected > 0)
{
parentObject.isOptionsSelected(true);
}
else
{parentObject.isOptionsSelected(false);}
};
//new event added for the child options
extendedAttributeModel.OnChildOptionSelect = function(modelObject,parentContext, parentContextParentObject, parentObject){
modelObject.isModified(true);
var totalChildSelected=0;
ko.utils.arrayForEach(parentObject.childObjects(), function(item) {
if (item.isSelected())
{
totalChildSelected++;
}
});
if(totalChildSelected == parentObject.childObjects().length)
{
parentObject.isSelected(true);
}
else
{
parentObject.isSelected(false);
}
if(totalChildSelected > 0)
{
parentContextParentObject.isOptionsSelected(true);
}
else
{
parentContextParentObject.isOptionsSelected(false);
}
};
//eof child option event
extendedAttributeModel.setDefaultModified = function(modelObject){
if(modelObject.ATTRIBUTE_CODE().toLowerCase() == "section")
{
ko.utils.arrayForEach(modelObject.filterddlSectionListOptions(), function(outeritem) {
if(outeritem.sectionList().length > 0)
{
ko.utils.arrayForEach(outeritem.sectionList(),function(item){
item.isSectionModified(false);
});
}
});
}
else
{
ko.utils.arrayForEach(modelObject.filterListOptions(),function(elem){
elem.isModified(false);
});
}
};
//instead of using click event , binding applied to observable property itself to show PopUp
// extendedAttributeModel.ISATTRIBUTESELECTED.subscribe(function(value) {
extendedAttributeModel.checkedCallback = function(modelObject,SOURCE){
if(SOURCE == "LABEL")
{
modelObject.ISATTRIBUTESELECTED(true);
}
if(modelObject.ATTRIBUTE_DISPLAY_TYPE().toLowerCase() == AttributeEnum.MultipleChoiceList)
{
extendedAttributeModel.setDefaultModified(modelObject);
if(modelObject.ISATTRIBUTESELECTED())
{
modelObject.isDialogOpen(true);
//if(modelObject.ATTRIBUTE_DISPLAY_TYPE.toLowerCase()=="list")
//{
//}
}
else
{
modelObject.isDialogOpen(false);
extendedAttributeModel.onAttributeUncheckCallback(modelObject);
sltModel.getsectionCount();
$(document).trigger('OnApplyAttribute');
}
}
if(modelObject.ATTRIBUTE_DISPLAY_TYPE().toLowerCase() == AttributeEnum.NumberRange)
{
if(!modelObject.ISATTRIBUTESELECTED())
{
extendedAttributeModel.resetSlider(modelObject);
}
sltModel.getsectionCount();
$(document).trigger('OnApplyAttribute');
}
};
// },extendedAttributeModel);
extendedAttributeModel.onDialogClose = function(modelObject){
//alert("Close");
//debugger;
modelObject.isDialogOpen(false);
if(modelObject.ATTRIBUTE_CODE().toLowerCase() == "section")
{
extendedAttributeModel.getSectionAttributeOptionsSelectedCount(modelObject);
}
else
{
extendedAttributeModel.getOptionsSelectedCount(modelObject);
}
};
//Helper functions for label count selected Options
extendedAttributeModel.OptionsCountLabel = ko.observable();
extendedAttributeModel.OptionsSelectedCount = ko.observable();
extendedAttributeModel.isOptionsSelected = ko.observable();
extendedAttributeModel.getOptionsSelectedCount = function(modelObject) {
debugger;
var totalSelected = 0;
var selectedAttributeID = modelObject.ATTRIBUTE_ID();
ko.utils.arrayForEach(modelObject.filterListOptions(), function(item) {
//Checkbox is checked but cancel button clicked
//if (item.isSelected() && item.isModified()) {
// item.isSelected(item.SavedCheckedState());
//}
//if user unchecks checked and press cancel
if(item.isModified()) {
item.isSelected(item.SavedCheckedState());
}
if (item.isSelected()) {
totalSelected++;
}
});
extendedAttributeModel.OptionsCountLabel("(" + totalSelected + ")");
extendedAttributeModel.OptionsSelectedCount(totalSelected);
if(totalSelected > 0)
extendedAttributeModel.isOptionsSelected(true);
else
{
extendedAttributeModel.isOptionsSelected(false);
extendedAttributeModel.ISATTRIBUTESELECTED(false);
}
};
extendedAttributeModel.getOptionsOnApply = function(modelObject) {
debugger;
var totalSelected = 0;
var selectedAttributeID = modelObject.ATTRIBUTE_ID();
var i=0;
ko.utils.arrayForEach(modelObject.filterListOptions(), function(item) {
if(modelObject.filterListOptions()[i].childObjects()==null)
{
if (item.isSelected()) {
totalSelected++;
}
}
//Get All the child elements for the selected item
//if the selected option is checked then check all the child options
//else vice versa
if(modelObject.filterListOptions()[i].childObjects()!=null){
//var isChecked = modelObject.filterListOptions()[i].isSelected();
//if(isChecked){
ko.utils.arrayForEach(modelObject.filterListOptions()[i].childObjects(), function(item) {
if( item.isSelected())
{
totalSelected++;
}
});
//}
}
//if (item.isSelected()) {
// totalSelected++;
//}
item.SavedCheckedState(item.isSelected());
i++;
});
extendedAttributeModel.OptionsCountLabel("(" + totalSelected + ")");
extendedAttributeModel.OptionsSelectedCount(totalSelected);
if(totalSelected > 0)
extendedAttributeModel.isOptionsSelected(true);
else
{
extendedAttributeModel.isOptionsSelected(false);
extendedAttributeModel.ISATTRIBUTESELECTED(false);
}
};
//extendedAttributeModel.getOnlySelectedCount = function(modelObject) {
// var totalSelected = 0;
// var selectedAttributeID = modelObject.ATTRIBUTE_ID();
// ko.utils.arrayForEach(modelObject.filterListOptions(), function(item) {
// if (item.isSelected()) {
// totalSelected++;
// }
// });
// return totalSelected;
//};
extendedAttributeModel.onAttributeUncheckCallback = function(modelObject) {
if(modelObject.ATTRIBUTE_CODE().toLowerCase() == "section")
{
ko.utils.arrayForEach(modelObject.filterddlSectionListOptions(), function(outeritem) {
sltModel.setSectionAttributeOriginalStateName(outeritem,-1);
if(outeritem.sectionList().length > 0)
{
ko.utils.arrayForEach(outeritem.sectionList(),function(item){
item.isSectionSelected(false);
});
}
});
}
else
{
ko.utils.arrayForEach(extendedAttributeModel.filterListOptions(), function(item) {
item.isSelected(false);
if(item.childObjects()!=null){
ko.utils.arrayForEach(item.childObjects(), function(item) {
item.isSelected(false);
});
}
});
}
extendedAttributeModel.OptionsCountLabel("");
extendedAttributeModel.OptionsSelectedCount(0);
extendedAttributeModel.isOptionsSelected(false);
};
return extendedAttributeModel;
}
};
};
In short,it replicates the drag one option.Kindly tell me what I am missing??
It looks like you're making use of the knockout-sortable library. I've had problems in the past where the templates had whitespace outside of any elements - it's noted on the project's page also:
Note2: When using named templates, you will have the best results across browsers, if you ensure that there is only a single top-level node inside your template with no surrounding text nodes. Inside of the top-level nodes, you can freely use whitespace/text nodes. So, you will want:
<!-- good - no text nodes surrounding template root node -->
<script id="goodTmpl" type="text/html"><li data-bind="text: name">
<span data-bind="text: name"></span>
</li></script>
<!-- bad -->
<script id="badTmpl" type="text/html">
<li>
<span data-bind="text: name"></span>
</li>
</script>
Currently I am working on a card voting system. I can get it to work properly in a jsfiddle but when i use microsoft visual studious and debug it, the cards do not display.
Here is my fiddle
http://jsfiddle.net/grahamwalsh/6RnXM/
and here is my code
Voting (Html)
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Voting</title>
<script src="Scripts/jquery-2.1.1.js"></script>
<script src="Scripts/knockout-3.1.0.js"></script>
<script src="Voting.js"></script>
<script src="Voting.css"></script>
</head>
<body>
<div data-bind="foreach: items">
<div class="profile" data-bind="text: name, click: $parent.clicked, enable: active, css:{highlight: active()}"></div>
</div>
<hr>
<h2>Selected Card</h2>
<div data-bind="foreach: selectedItems">
<div data-bind="text: name"></div>
</div>
<input type="button" data-bind="click: save" value="Save">
</body>
</html>
Voting (CSS)
body {.profile {
width: 50px;
height: 80px;
color: black;
background-color:silver;
border: 1px solid black;
float: left;
line-height:80px;
text-align: center;
margin: 2px;
}
.highlight {
background: yellow !important;
border:1px solid #000;
color: black;
}
}
function Card(number) {
this.active = ko.observable(false);
this.name = ko.observable(number);
}
var model = function () {
var cards = [1, 2, 3, 5, 8, 13, 20, 40, 100];
for (var i = 0 ; i < cards.length ; i++)
cards[i] = new Card(cards[i]);
var items = ko.observableArray(cards)
var selectedItems = ko.computed(function () {
return _.filter(items(), function (item) {
return item.active();
});
})
var clicked = function (item) {
items().forEach(function (item) { item.active(false) });
item.active(!this.active());
};
var save = function () {
alert("sending items \n" + ko.toJSON(selectedItems()));
}
return {
items: items,
selectedItems: selectedItems,
save: save,
clicked: clicked
}
}
ko.applyBindings(model);
Voting (Js)
function Card(number) {
this.active = ko.observable(false);
this.name = ko.observable(number);
}
var model = function () {
var cards = [1, 2, 3, 5, 8, 13, 20, 40, 100];
for (var i = 0 ; i < cards.length ; i++)
cards[i] = new Card(cards[i]);
var items = ko.observableArray(cards)
var selectedItems = ko.computed(function () {
return _.filter(items(), function (item) {
return item.active();
});
})
var clicked = function (item) {
items().forEach(function (item) { item.active(false) });
item.active(!this.active());
};
var save = function () {
alert("sending items \n" + ko.toJSON(selectedItems()));
}
return {
items: items,
selectedItems: selectedItems,
save: save,
clicked: clicked
}
}
ko.applyBindings(model);
This code implements a 60 cell spreadsheet, 6x10 rows,columns. At the end of each row are two labels, one for row total and for running total. The main issue here is how to update the row and running total labels when the calc button is pressed.
I would also like to move the button from the bottom of the scrollview to the bottom of the window where it's always visible. Another view for buttons at the bottom?
Ti.include("toast.js"); // 3rd-party code; displays notifications
var i=0;
var r=0;
var rows = 10;
var columns = 6;
left = ["0%","12%","24%","36%","48%","60%"];
Titanium.UI.setBackgroundColor('#000');
var win1 = Titanium.UI.createWindow({
title:'Target 1',
exitOnClose: true,
backgroundColor:'#fff'
});
win1.addEventListener('androidback' , function(e){
win1.close();
var activity = Titanium.Android.currentActivity;
activity.finish();
});
var scrollView1 = Ti.UI.createScrollView({
bottom:120,
contentHeight: 'auto',
layout: 'vertical'
});
if (Ti.UI.Android){
win1.windowSoftInputMode = Ti.UI.Android.SOFT_INPUT_ADJUST_PAN;
}
var buttonCalc = Titanium.UI.createButton({
title: 'Calc',
top: 10,
width: 100,
height: 50,
left: "10%"
});
var lbAttrs1 = {
text: "000",
left: "74%",
color:'#000',width:'auto',height:'auto',textAlign:'left',
font:{fontSize:24,fontWeight:'regular'}
};
var lbAttrs2 = {
text: "000",
left: "88%",
color:'#000',width:'auto',height:'auto',textAlign:'left',
font:{fontSize:24,fontWeight:'regular'}
};
var baseAttrs = {
borderStyle : Titanium.UI.INPUT_BORDERSTYLE_ROUNDED,
keyboardType: Titanium.UI.KEYBOARD_NUMBERS_PUNCTUATION,
maxLength: 2,
top: 10,
height: 60,
value: "",
width: '12%',
color : '#000000'
};
var tfields = [];
var labels1 = [];
var labels2 = [];
buttonCalc.addEventListener('click',function(e)
{
var a = 0;
var b = 0;
for (j=0;j<rows;j++)
{
a = 0;
for (i=0;i<columns;i++)
a = parseInt(tfields[j][i].value) + a;
b = b + a;
labels1[j] = a.toString();
labels2[j] = b.toString();
}
for (j=0;j<rows;j++)
alert( labels1[j]+' '+labels2[j]+ ' ' + j.toString());
});
function createRow1(i) // start create row
{
row1 = Ti.UI.createView({
backgroundColor: 'white',
borderColor: '#bbb',
borderWidth: 1,
width:'100%', height: 70,
top: 0, left: 0 });
var tfield1 = [];
var label1 = [];
var label2 = [];
for (i=0;i<columns;i++)
{
tfield1[i] = Ti.UI.createTextField(baseAttrs);
label1[i] = Ti.UI.createLabel(lbAttrs1);
label2[i] = Ti.UI.createLabel(lbAttrs2);
}
tfield1[0].addEventListener('change', function()
{
if (tfield1[0].value > 10)
{
tfield1[0].value = "";
showMessageTimeout("More than 10.",15);
}
});
tfield1[1].addEventListener('change', function()
{
if (tfield1[1].value > 10)
{
tfield1[1].value = "";
showMessageTimeout("More than 10.",15);
}
});
tfield1[2].addEventListener('change', function()
{
if (tfield1[2].value > 10)
{
tfield1[2].value = "";
showMessageTimeout("More than 10.",15);
}
});
tfield1[3].addEventListener('change', function()
{
if (tfield1[3].value > 10)
{
tfield1[3].value = "";
showMessageTimeout("More than 10.",15);
}
});
tfield1[4].addEventListener('change', function()
{
if (tfield1[4].value > 10)
{
tfield1[4].value = "";
showMessageTimeout("More than 10.",15);
}
});
tfield1[5].addEventListener('change', function()
{
if (tfield1[5].value > 10)
{
tfield1[5].value = "";
showMessageTimeout("More than 10.",15);
}
});
tfield1[0].left = left[0];
tfield1[1].left = left[1];
tfield1[2].left = left[2];
tfield1[3].left = left[3];
tfield1[4].left = left[4];
tfield1[5].left = left[5];
for (i=0;i<columns;i++)
{
row1.add(tfield1[i]);
row1.add(label1[i]);
row1.add(label2[i]);
}
;
tfields.push(tfield1);
labels1.push(label1);
labels2.push(label2);
return row1;
} /// end of createrow1
for(i = 0; i < rows; i++){
row1 = createRow1(i);
scrollView1.add(row1);
}
win1.add(scrollView1);
scrollView1.add(buttonCalc);
// win1.add(buttonCalc);
win1.open();
Point 1.
That is a typo, you should substitute each occurrence of labels1[j] and labels2[j] to labels1[j].text and labels2[j].text respectively:
buttonCalc.addEventListener('click',function(e)
{
var a = 0;
var b = 0;
for (j=0;j<rows;j++)
{
a = 0;
for (i=0;i<columns;i++){
var newValue = parseInt(tfields[j][i].value);
if(!isNaN(newValue) && typeof(newValue) === 'number')
a = newValue + a;
}
b = b + a;
labels1[j].text = a.toString();
labels2[j].text = b.toString();
}
for (j=0;j<rows;j++)
Ti.API.info( labels1[j].text +' '+labels2[j].text + ' ' + j.toString());
});
And also it must be changed these parts:
function createRow1(i) // start create row
{
...
for (i=0;i<columns;i++)
{
tfield1[i] = Ti.UI.createTextField(baseAttrs);
}
label1 = Ti.UI.createLabel(lbAttrs1); //there is only one per row
label2 = Ti.UI.createLabel(lbAttrs2); //there is only one per row
...
for (i=0;i<columns;i++)
{
row1.add(tfield1[i]);
}
row1.add(label1); //there is only one per row
row1.add(label2); //there is only one per row
tfields.push(tfield1);
labels1.push(label1);
labels2.push(label2);
return row1;
} /// end of createrow1
Point 2.
you can do it this way:
var scrollView1 = Ti.UI.createScrollView({
top:0,
height:'60%', //Put your desired value here
contentHeight: 'auto',
layout: 'vertical'
});
...
var buttonCalc = Titanium.UI.createButton({
title: 'Calc',
top: '70%', // Put your desired value here greater than scrollView1.height
width: 100,
height: 50,
left: "10%"
});
...
win1.add(scrollView1);
//scrollView1.add(buttonCalc);
win1.add(buttonCalc);
Extra point.
you need to set softKeyboardOnFocus property in your baseAttrs:
var baseAttrs = {
borderStyle : Titanium.UI.INPUT_BORDERSTYLE_ROUNDED,
keyboardType: Titanium.UI.KEYBOARD_NUMBERS_PUNCTUATION,
softKeyboardOnFocus: Titanium.UI.Android.SOFT_KEYBOARD_SHOW_ON_FOCUS,
maxLength: 2,
top: 10,
height: 60,
value: "",
width: '12%',
color : '#000000'
};
this way, softKeyboard will be shown on focus the first time.
Finally, good luck with your app :-)