Related
someone have any idea how i should modify the payment-lines in the POS,I want to add a type of credit card(like a many2one, I did it) but every time I add a line my option change to the first and also when the order is finished not save the value in pos.order -> statement_id.
enter image description here
here is my code:
function POS_CashRegister (instance, local) {
var pos = instance.point_of_sale;
var _t = instance.web._t;
var QWeb = instance.web.qweb;
var round_pr = instance.web.round_precision
const ParentOrder = pos.Order;
pos.PosModel.prototype.models.push({ //loaded model
model: 'pos.credit.card',
fields: ['id', 'name'],
domain: [['pos_active','=',true]],
loaded: function(self,credit_cards){ //pass parameters
self.credit_cards = credit_cards;
},
});
pos.PaymentScreenWidget = pos.PaymentScreenWidget.extend({
validate_order: function(options) {
var self = this;
var currentOrder = self.pos.get('selectedOrder');
var plines = currentOrder.get('paymentLines').models;
for (var i = 0; i < plines.length; i++) {
if(plines[i].cashregister.journal_id[1] === 'Tarjeta de Credito (PEN)')
{
var value = plines[i].node.firstElementChild.nextElementSibling.nextElementSibling.firstElementChild.value;
plines[i].set_credit_card(parseInt(value));
//console.log(plines[i].node.firstElementChild.nextElementSibling.nextElementSibling.firstElementChild.value);
//plines[i].node
}
}
console.log(currentOrder);
self._super(options);
},
render_paymentline: function (line) {
var self = this;
if(line.cashregister.journal_id[1] !== 'Tarjeta de Credito (PEN)'){
if (line.cashregister.currency[1] !== 'USD') {
return this._super(line);
} else {
var el_html = openerp.qweb.render('Paymentline', {widget: this, line: line});
el_html = _.str.trim(el_html);
var el_node = document.createElement('tbody');
el_node.innerHTML = el_html;
el_node = el_node.childNodes[0];
el_node.line = line;
el_node.querySelector('.paymentline-delete')
.addEventListener('click', this.line_delete_handler);
el_node.addEventListener('click', this.line_click_handler);
var sourceInput = el_node.querySelector('.source-input');
var convertedInput = el_node.querySelector('.converted-input');
sourceInput.addEventListener('keyup', function (event) {
el_node.line.set_usd_amount(event.target.value);
convertedInput.value = el_node.line.get_amount_str();
});
line.node = el_node;
return el_node;
}
}else {
return this._super(line);
}
},
});
pos.Paymentline = pos.Paymentline.extend({
initialize: function(attributes, options) {
this.amount = 0;
this.cashregister = options.cashregister;
this.name = this.cashregister.journal_id[1];
this.selected = false;
this.credit_card = false;
this.pos = options.pos;
},
set_credit_card: function(value){
this.credit_card = value;
this.trigger('change:credit_card',this);
},
get_credit_card: function(){
return this.credit_card;
},
export_as_JSON: function(){
return {
name: instance.web.datetime_to_str(new Date()),
statement_id: this.cashregister.id,
account_id: this.cashregister.account_id[0],
journal_id: this.cashregister.journal_id[0],
amount: this.get_amount(),
credit_card_id: this.get_credit_card(),
};
},
});
}
any suggestions?
You can create 2 journals here too. One for visa and another for master If you don't want that drop down there. Another way is you have to store selected option in a variable and then print that variable in front.
To store selected option initially assigned ids to each values of option and after then while validating order you can get that id of that field and from that id you can get your value. By this way also you can do that.
I fork the code from here:
http://kindohm.github.io/knockout-query-builder/
The code works nice on the client side.
But when I try to save the viewModel as JSON and then retrieve the data from the server the UI never refresh at all.
This is the original viewModel:
window.QueryBuilder = (function(exports, ko){
var Group = exports.Group;
function ViewModel() {
var self = this;
self.group = ko.observable(new Group());
// the text() function is just an example to show output
self.text = ko.computed(function(){
return self.group().text();
});
}
exports.ViewModel = ViewModel;
return exports;
})(window.QueryBuilder || {}, window.ko);
I be added the next method to the viewModel
self.Save = function () {
console.log(ko.toJSON(self));
}
Added this button to the view
<input type="submit" value="Save" data-bind="click: Save"/>
This is the Group viewModel:
window.QueryBuilder = (function(exports, ko){
var Condition = exports.Condition;
function Group(data){
var self = this;
self.templateName = data.templateName;
self.children = ko.observableArray(data.children);
self.logicalOperators = ko.observableArray(data.logicalOperators);
self.selectedLogicalOperator = ko.observable(data.selectedLogicalOperator);
// 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);
};
// the text() function is just an example to show output
self.text = ko.computed(function(){
var result = '(';
var op = '';
for (var i = 0; i < self.children().length; i++){
var child = self.children()[i];
console.log(child);
result += op + child.text();
op = ' ' + self.selectedLogicalOperator() + ' ';
}
return result += ')';
});
}
exports.Group = Group;
return exports;
})(window.QueryBuilder || {}, window.ko);
So when I press the "save" button the console show the JSON from this viewModel, everything fine here.
This is the JSON returned:
{"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"}],"logicalOperators":["AND","OR"],"selectedLogicalOperator":"AND","text":"(Points = 0 AND Points = 0 AND Points = 0)"},"text":"(Points = 0 AND Points = 0 AND Points = 0)"}
I make a simple hack to avoid the connection to the server, so I take that json copy and paste on the load event and send to the constructor of the viewModel:
var vm;
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"}],"logicalOperators":["AND","OR"],"selectedLogicalOperator":"AND","text":"(Points = 0 AND Points = 0 AND Points = 0)"},"text":"(Points = 0 AND Points = 0 AND Points = 0)"};
vm = new QueryBuilder.ViewModel(json);
ko.applyBindings(vm);
}, true);
Then I modify the viewModel to recibe the json parameter
window.QueryBuilder = (function(exports, ko){
var Group = exports.Group;
function ViewModel(json) {
var self = this;
self.group = ko.observable(json.group);
// the text() function is just an example to show output
self.text = ko.computed(function(){
return self.group().text();
});
self.Save = function () {
console.log(ko.toJSON(self));
}
}
exports.ViewModel = ViewModel;
return exports;
})(window.QueryBuilder || {}, window.ko);
When I refresh the index.html the view is never loaded correctly and show this error on the JS console:
TypeError: self.group(...).text is not a function
return self.group().text();
Someone knows where is my mistake?
The last problem I had was related to the text() function on the child.
I fix this with the use of try/catch. So when the viewModel are new it have the text() function, but when this is loadad the text() does not exist, so I take the value directly from the "text" field.
try {
result += op + child.text();
}
catch(err) {
result += op + child.text;
}
The problem was on the Group class and Condition class.
This is the current and working code:
window.QueryBuilder = (function(exports, ko){
var Condition = exports.Condition;
function Group(data){
var self = this;
self.templateName = data.templateName;
self.children = ko.observableArray(data.children);
self.logicalOperators = ko.observableArray(data.logicalOperators);
self.selectedLogicalOperator = ko.observable(data.selectedLogicalOperator);
// give the group a single default condition
self.children.push(new Condition(data.children[0]));
self.addCondition = function(){
self.children.push(new Condition());
};
self.addGroup = function(){
self.children.push(new Group());
};
self.removeChild = function(child){
self.children.remove(child);
};
// the text() function is just an example to show output
self.text = ko.computed(function(){
var result = '(';
var op = '';
for (var i = 0; i < self.children().length; i++){
var child = self.children()[i];
try {
result += op + child.text();
}
catch(err) {
result += op + child.text;
}
op = ' ' + self.selectedLogicalOperator() + ' ';
}
return result += ')';
});
}
exports.Group = Group;
return exports;
})(window.QueryBuilder || {}, window.ko);
window.QueryBuilder = (function(exports, ko){
function Condition(data){
var self = this;
self.templateName = data.templateName;
self.fields = ko.observableArray(data.fields);
self.selectedField = ko.observable(data.selectedField);
self.comparisons = ko.observableArray(data.comparisons);
self.selectedComparison = ko.observable(data.selectedComparison);
self.value = ko.observable(data.value);
// the text() function is just an example to show output
self.text = ko.computed(function(){
return self.selectedField() +
' ' +
self.selectedComparison() +
' ' +
self.value();
});
}
exports.Condition = Condition;
return exports;
})(window.QueryBuilder || {}, window.ko);
Instead of self.group = ko.observable(json.group);, you should take a similar approach as you did on load self.group = ko.observable(new Group());, but this time pass the json.group data in Group
self.group = ko.observable(new Group(json.group));
I don't see where Group is defined, but you should make sure that it is able to handle and convert the JSON you now pass in, into observables.
I have an observableArray which is displayed in a table using foreach binding where values are displayed inside textboxes. Now what I want to do is to add an edit link on each row which enables/disables the readonly state of the corresponding textbox in its row. I can do it, but the way I did it messed up my add new line (push) functionality.
Here is a fiddle of my code.
Try to delete a line then add it again by selecting it in the dropdown list, the edit link disappears as well as the value.
Any help will be greatly appreciated! Thank you.
So here's my HTML:
<table class="input-group" >
<tbody data-bind="foreach: loanDeductions">
<tr>
<td><strong data-bind='text: deductionName'></strong></td>
<td><input class="deductionCode form-control" style="text-align: right" data-bind='value: amount, valueUpdate: "afterkeydown", attr: { "readonly": getreadonlyState() }' /></td>
<td><a href='#' data-bind='click: $parent.removeLine'>Delete</a></td>
<td><span data-bind="text: linkText"></span></td>
</tr>
</tbody>
</table>
<table>
<tr>
<td colspan="3"><select data-bind="options: loanDeductionsList(), optionsText: 'deductionName', optionsCaption: 'Choose a deduction..', value: selectedDeduction"></select></td>
</tr>
</table>
Now here is my script:
var deductionLine = function (deductionID, deductionName, amount) {
self = this;
self.deductionID = ko.observable(deductionID);
self.deductionName = ko.observable(deductionName);
self.amount = ko.observable(formatCurrency(amount));
self.getreadonlyState = ko.observable('readonly');
self.linkText = ko.computed(function () {
return this.getreadonlyState() == 'readonly' ? "Edit" : "Stop Edit";
}, self);
};
var deductionList = function (deductionID, deductionName, amount) {
self = this;
self.deductionID = ko.observable(deductionID);
self.deductionName = ko.observable(deductionName);
self.amount = ko.observable(formatCurrency(amount));
};
function LoanDeductions(deductions) {
var self = this;
self.loanDeductions = ko.observableArray(ko.utils.arrayMap(deductions, function (deduction) {
return new deductionLine(deduction.deductionID, deduction.deductionName, deduction.amount)
}));
self.loanDeductionsList = ko.observableArray(ko.utils.arrayMap(deductions, function (deduction) {
return new deductionList(deduction.deductionID, deduction.deductionName, deduction.amount)
}));
self.selectedDeduction = ko.observable();
//edit link
self.readonly = function () {
if (BossBaU) {
if (this.getreadonlyState()) {
this.getreadonlyState(undefined);
}
else {
this.getreadonlyState('readonly');
}
}
else alert('Access denied!');
}
// adds deduction
self.selectedDeduction.subscribe(function (data) {
var match = ko.utils.arrayFirst(self.loanDeductions(), function (deduction) {
return deduction.deductionID() === data.deductionID();
});
if (match) {
alert(data.deductionName() + ' already exists!');
self.showAddDeduc(false);
} else {
self.loanDeductions.push({
deductionID: data.deductionID,
deductionName: data.deductionName,
amount: data.amount,
});
self.showAddDeduc(false);
}
});
//delete deduction
self.removeLine = function (line) { self.loanDeductions.remove(line) };
};
var viewModel = new LoanDeductions(#Html.Raw(Model.CRefLoansDeductions2.ToJson()));
$(document).ready(function () {
ko.applyBindings(viewModel);
});
In the subscribe handler, self.selectedDeduction.subscribe, you're adding an object to the list of loanDeductions when you should be adding a new instance of deductionLine just as you do when you declare self.loanDeductions.
Or to put it another way, self.loadDeductions is an observableArray of deductionLine instances, to which you then add an object with three properties.
Change that subscribe handler to push a new deductionLine(...) and you'll see the difference.
I found the cause of the problem, I had to mirror every changes I made with my observableArray to my list.
var deductionLine = function (deductionID, deductionName, amount) {
self = this;
self.deductionID = ko.observable(deductionID);
self.deductionName = ko.observable(deductionName);
self.amount = ko.observable(amount);
self.getreadonlyState = ko.observable('readonly');
self.linkText = ko.computed(function () {
return this.getreadonlyState() == 'readonly' ? "Edit" : "Stop Edit";
}, self);
};
var deductionList = function (deductionID, deductionName, amount) {
self = this;
self.deductionID = ko.observable(deductionID);
self.deductionName = ko.observable(deductionName);
self.amount = ko.observable(amount);
self.getreadonlyState = ko.observable('readonly');
self.linkText = ko.computed(function () {
return this.getreadonlyState() == 'readonly' ? "Edit" : "Stop Edit";
}, self);
};
Here's the fiddle in case anyone bump into a similar issue.
So here it is... I am attempting to build a data-grid with Knockout.js. I want to build it from scratch (skill building exercise), so I don't want to use KoGrid or SimpleGrid.
The issue I am having is that I want to be able to filter the results based on a text input. This filter has to go through each object and filter on ONLY the keys that match a column's value property.
Example
Filtering with the value '1' will return both of data's objects with properties that contain 1. (ID 1 and ID 3).
JSFIDDLE
HTML
<div data-bind="foreach: filteredItems">
<p data-bind="text: LastName"></p>
</div>
<p>Filter:
<input data-bind="value: filter, valueUpdate: 'keyup'" />
</p>
<p>Filter Value: <span data-bind="text: filter"></span></p>
JavaScript
var data = [{
Id: 1,
LastName: "Franklin"
}, {
Id: 2,
LastName: "Green"
}, {
Id: 3,
LastName: "1"
}];
var columns = [{
value: 'Id'
}, {
value: 'LastName'
}];
var Filtering = function (data, columns) {
var self = this;
self.items = ko.observableArray(data);
self.columns = ko.observableArray(columns);
self.filter = ko.observable();
self.filteredItems = ko.computed(function () {
var filter = self.filter();
console.log(filter);
if (!filter) {
return self.items();
} else {
return ko.utils.arrayFilter(self.items(), function (item) {
console.log('Filtering on Item');
ko.utils.arrayForEach(self.columns(), function (c) {
var val = item[c.value];
if (typeof val === 'number') {
val = val.toString();
}
console.log('Filtering on Column');
return val.toLowerCase().indexOf(filter.toLowerCase()) > -1;
});
});
}
});
};
ko.applyBindings(new Filtering(data, columns));
It works great statically setting the c.value in item[c.value], but when I try to loop through the array of self.columns() I do not get results returned.
Recs
I have jQuery, Knockout.js 3.0, and underscore.js at my disposal.
Any help is greatly appreciated.
Just few problems in your code :
a Boolean needs to be returned as output of ko.utils.arrayFilter
you need a sort of sum up for arrayForEach as your filter is an OR
I changed your code to address those details :
var Filtering = function (data, columns) {
var self = this;
self.items = ko.observableArray(data);
self.columns = ko.observableArray(columns);
self.filter = ko.observable();
self.filteredItems = ko.computed(function () {
var filter = self.filter();
console.log(filter);
if (!filter) {
return self.items();
} else {
return ko.utils.arrayFilter(self.items(), function (item) {
console.log('Filtering on Item');
var matching = -1;
ko.utils.arrayForEach(self.columns(), function (c) {
var val = item[c.value];
if (typeof val === 'number') {
val = val.toString();
}
console.log('Filtering on Column');
matching+= val.toLowerCase().indexOf(filter.toLowerCase())+1;
});
console.log(matching);
return matching>=0;
});
}
});
};
Worked for me there : http://jsfiddle.net/Lzud7fjr/1/
Your filtering case isn't returning anything. ko.utils.arrayFilter() should return a truthy value if the item should be included in the result. However since it doesn't return anything, nothing is included in the result. You'll need to rewrite it so it will return something.
I suppose you could change the inner ko.utils.arrayForEach() call to a filter and return true if the result is non-empty.
self.filteredItems = ko.computed(function () {
var filter = self.filter();
console.log(filter);
if (!filter) {
return self.items();
} else {
return ko.utils.arrayFilter(self.items(), function (item) {
console.log('Filtering on Item');
var result = ko.utils.arrayFilter(self.columns(), function (c) {
var val = item[c.value];
if (typeof val === 'number') {
val = val.toString();
}
console.log('Filtering on Column');
return val.toLowerCase().indexOf(filter.toLowerCase()) > -1;
});
return !!result.length;
});
}
});
The code below is simplified, see the fiddle: http://jsfiddle.net/QTUqD/7/
Basically I'm setting the device name under the data-bind, but I also need to specify the optionsValue for sending off to the database, but when I set it, the display data-bind is blank.
<script id="extItems" type="text/html">
<tr>
<td data-bind="text: device() && device().name"></td>
</tr>
</script>
<script id="editExts" type="text/html">
<tr>
<td>
<select data-bind="options: $root.devicesForItem($data), optionsText: 'name', value: device, optionsValue: 'id'"></select>
</td>
</tr>
</script>
window.ExtListViewModel = new function () {
var self = this;
window.viewModel = self;
self.list = ko.observableArray();
self.pageSize = ko.observable(10);
self.pageIndex = ko.observable(0);
self.selectedItem = ko.observable();
self.extQty = ko.observable();
self.devices = ko.observableArray();
self.addressList = ko.observableArray(['addressList']);
self.availableDevices = ko.computed(function() {
var usedQuantities = {}; // for each device id, store the used quantity
self.list().forEach(function(item) {
var device = item.device();
if (device) {
usedQuantities[device.id] = 1 + (usedQuantities[device.id] || 0);
}
});
return self.devices().filter(function(device) {
var usedQuantity = usedQuantities[device.id] || 0;
return device.qty > usedQuantity;
});
});
// need this to add back item's selected device to its device-options,
// and to maintain original order of devices
self.devicesForItem = function(item) {
var availableDevices = self.availableDevices();
return self.devices().filter(function(device) {
return device === item.device() || availableDevices.indexOf(device) !== -1;
});
}
self.edit = function (item) {
if($('#extMngForm').valid()) {
self.selectedItem(item);
}
};
self.cancel = function () {
self.selectedItem(null);
};
self.add = function () {
if($('#extMngForm').valid()) {
var newItem = new Extension();
self.list.push(newItem);
self.selectedItem(newItem);
self.moveToPage(self.maxPageIndex());
}
};
self.remove = function (item) {
if (confirm('Are you sure you wish to delete this item?')) {
self.list.remove(item);
if (self.pageIndex() > self.maxPageIndex()) {
self.moveToPage(self.maxPageIndex());
}
}
$('.error').hide();
};
self.save = function () {
if($('#extMngForm').valid()) {
self.selectedItem(null);
};
};
self.templateToUse = function (item) {
return self.selectedItem() === item ? 'editExts' : 'extItems';
};
self.pagedList = ko.dependentObservable(function () {
var size = self.pageSize();
var start = self.pageIndex() * size;
return self.list.slice(start, start + size);
});
self.maxPageIndex = ko.dependentObservable(function () {
return Math.ceil(self.list().length / self.pageSize()) - 1;
});
self.previousPage = function () {
if (self.pageIndex() > 0) {
self.pageIndex(self.pageIndex() - 1);
}
};
self.nextPage = function () {
if (self.pageIndex() < self.maxPageIndex()) {
self.pageIndex(self.pageIndex() + 1);
}
};
self.allPages = ko.dependentObservable(function () {
var pages = [];
for (i = 0; i <= self.maxPageIndex() ; i++) {
pages.push({ pageNumber: (i + 1) });
}
return pages;
});
self.moveToPage = function (index) {
self.pageIndex(index);
};
};
ko.applyBindings(ExtListViewModel, document.getElementById('extMngForm'));
function Extension(extension, name, email, vmpin, device, macAddress, shipTo){
this.extension = ko.observable(extension);
this.name = ko.observable(name);
this.email = ko.observable(email);
this.vmpin = ko.observable(vmpin);
this.device = ko.observable(device);
this.macAddress = ko.observable(macAddress);
this.shipTo = ko.observable(shipTo);
}
When you use optionsValue, KO writes the property value to whatever you have bound against value. So, it would now populate value with the id rather than the object.
There are a couple of ways to tackle this scenario where you want both the value (for sending to the DB) and the object (for binding other parts of the UI against).
A pretty typical solution is to create a computed observable on your object that takes the currently selected object and returns the id.
So, in your Extension you would do something like:
this.device = ko.computed({
read: function() {
var device = this.device.asObject();
return device && device.id;
},
deferEvaluation: true, //deferring evaluation, as device.asObject has not been created yet
}, this);
//create this as a sub-observable, so it just disappears when we turn this into JSON and we are just left with the id to send to the DB
this.device.asObject = ko.observable(device);
Then remove the optionsValue and bind value against device.asObject
In this case, I added the asObject sub-observable, so it will just drop off when you turn this into JSON (ko.toJSON) to send to the server. The only tricky part about this technique is that if you are loading existing data from the server, then you would need to populate asObject with the appropriate choice from your options.
Here is a sample: http://jsfiddle.net/rniemeyer/Q3PEv/
Another option that I have used is to continue to use optionsValue, but then to create a custom binding that tracks the object in a separate observable. Here is a custom binding that creates an asObject sub-observable for whatever is bound against value. This way you really don't need to mess with it at all in your view model.
//when using optionsValue, still track the select object in a different observable
ko.bindingHandlers.valueAsObject = {
init: function(element, valueAccessor, allBindingsAccessor) {
var value = allBindingsAccessor().value,
prop = valueAccessor() || 'asObject';
//add an "asObject" sub-observable to the observable bound against "value"
if (ko.isObservable(value) && !value[prop]) {
value[prop] = ko.observable();
}
},
//whenever the value or options are updated, populated the "asObject" observable
update: function(element, valueAccessor, allBindingsAccessor) {
var prop = valueAccessor(),
all = allBindingsAccessor(),
options = ko.utils.unwrapObservable(all.options),
value = all.value,
key = ko.utils.unwrapObservable(value),
keyProp = all.optionsValue,
//loop through the options, find a match based on the current "value"
match = ko.utils.arrayFirst(options, function(option) {
return option[keyProp] === key;
});
//set the "asObject" observable to our match
value[prop](match);
}
};
Sample here: http://jsfiddle.net/rniemeyer/E2kvM/