I won't even pretend I know what I am doing but I am going to ask as this is one of the most frustrating features of VueJS, well, at least for me.
So this is the situation. I have a number of sections creating their own totals and a final total made up of those calculated totals.
I'll give you the basics:
Parking
Transport
Mileage*
Subsistence
Each of the sections above creates a total from a number of lines of items which works fine at the moment. I'll get to mileage in a minute.
Each of these totals is then calculated into a grand total and also displayed.
At the moment, I am doing something like this:
computed: {
totalAmount: function() {
return this.calculateAmount(this.total);
},
transportAmount: function () {
if(this.isLoading === true) return 0;
return this.total.find(total => total.name == "transport").amount = (this.isLoading === false) ? this.calculateAmount(this.transport) : 0;
},
mileageAmount: function () {
if(this.isLoading === true) return 0;
return this.total.find(total => total.name == "mileage").amount = (this.isLoading === false) ? this.calculateAmount(this.mileage) : 0;
},
parkingAmount: function () {
if(this.isLoading === true) return 0;
return this.total.find(total => total.name == "parking").amount = (this.isLoading === false) ? this.calculateAmount(this.parking) : 0;
},
subsistenceAmount: function () {
if(this.isLoading === true) return 0;
return this.total.find(total => total.name == "subsistence").amount = (this.isLoading === false) ? this.calculateAmount(this.subsistence) : 0;
}
},
I am sure this is not the most efficient and almost certainly the wrong way to approach this (please correct me, I am self taught).
So my problem is with mileage.
I currently run a calculation for the total of each line based on a cost per mile AT INITIAL RUNTIME (i.e. when it first loads).
calculateMileage(ppm) {
this.mileage.forEach(e => {
e.amount = e.mileage * ppm;
});
},
Nothing too complex but it works.
Here is my problem. The amount for mileage is only created here. I am attempting to update the amount to then automatically update all the rest of the totals.
So I have the following setup:
Main component displaying all items
Sub components for adding or updating (pop up boxes)
So adding and updating works great for all other sections as they have a dedicated amount column stored in the database but the mileage one is calculated based on a pence per mile cost and dynamically displayed.
What should I use/try to calculate this figure on adding and updating. I tried using calculated methods but it appears you can't calculate from another calculated method.
Any ideas would be helpful. I can provide more code if necessary.
EDIT:
This is how the data is pulled from a database into the this.mileage variable
await axios.get(API_BASE_URL+'/claims/travel/mileage').then(response => {
this.mileage = response.data.data;
this.calculateMileage(0.55);
let totalModel = {};
totalModel.name = 'mileage';
totalModel.amount = (this.mileage.length > 0) ? this.calculateAmount(this.mileage) : 0;
this.total.push(totalModel);
});
mileage contains the following fields:
create table mileages
(
id bigint unsigned auto_increment
primary key,
claim int not null,
vehicle text not null,
mileageType int not null,
mileageDate date not null,
mileageTime time not null,
mileage double not null,
reason text not null,
origin text not null,
destination text not null,
created_at timestamp null,
updated_at timestamp null
)
collate=utf8mb4_unicode_ci;
EDIT 2:
I have added this watcher in place but it only fires at the initial load
watch: {
deep: true,
mileage: {
handler(){
console.log("Fired");
this.calculateMileage();
}
}
}
I suspect I need to make it watch the object items specifically somehow but I am at a loss at the moment.
Related
I have event table in Corvid Database, it has Event_Id column for every new event which is not asked from the user in the form.
I used the following code to get the number of rows and generate id from that which works good now:
let count = 0;
$w("#collection").onReady( () => {
count = $w("#collection").getTotalCount(); // 23
count++;
} );
$w('#btnSub').onClick( ()=>{
const newRequest = {
eventid:('event_'+count),
title: $w('#title').value
}
wixData.insert('event_instance', newRequest);
but this can lead to duplication of event id as I delete one of the row from collection
Can you please find a solution for this?
Thanks
You need to use dropdowns in #myElementID
This similar to this in SQL.
if not exists (select * from Delegates d where d.FromYr = #FromYr and d.MemNo = #MemNo)
INSERT INTO Delegates ([MemNo],[FromYr],[ToYr]) values(#MemNo, #FromYr,#ToYr)
Else refer this
Make sense get count before each insert
$w('#collection').onReady( () => {
$w('#btnSub').onClick( () => {
const count = $w('#collection').getTotalCount() + 1; // here
wixData.insert('event_instance', {
eventid: ('event_' + count),
title: $w('#title').value
});
});
});
I'm working on this project that someone else already started and I'm unsure how this part of code works, it's actually doing what I don't want it to do.
Currently when I use a select multiple and press the button it adds to the array the ones that I DIDN'T select, when I want it to add the ones I did select to the array, this array is then used as the data for a table so it's obvious it's selecting the wrong stuff.
This is the method when the button is pressed. package_courses is the final array that the table data is populated with.
addCourses() {
const currentCourses = this.packageForm.package_courses.map((item) => item.course_id);
const courses = this.courses.filter((item) => {
return this.selectedCourses.indexOf(item.id) && currentCourses.indexOf(item.id) < 0
});
courses.forEach((course) => {
this.packageForm.package_courses.push({
course_id: course.id,
course: course,
price: 0
});
});
this.selectedCourses = [];
},
In the second line the filter method loops all items in this.courses and returns only those items where the statement inside returns true. indexOf is an array method that searches an array for the specified item and returns the position of the item in the array or -1 if the items isn't found. so I guess what you want to do is filter for courses where indexOf is greater or equals than/to 0, instead of less, here is your code modified.
addCourses() {
const currentCourses = this.packageForm.package_courses.map((item) => item.course_id);
const courses = this.courses.filter((item) => {
return this.selectedCourses.indexOf(item.id) && currentCourses.indexOf(item.id) >= 0
});
courses.forEach((course) => {
this.packageForm.package_courses.push({
course_id: course.id,
course: course,
price: 0
});
});
this.selectedCourses = [];
},
Well the title says it all, details following.
I have two related models, User & Role.
User has roles defined as:
Ext.define('App.model.security.User', {
extend: 'App.model.Base',
entityName: 'User',
fields: [
{ name: 'id' },
{ name: 'email'},
{ name: 'name'},
{ name: 'enabled', type: 'bool'}
],
manyToMany: 'Role'
});
Then I have a grid of users and a form to edit user's data including his roles.
The thing is, when I try to add or delete a role from the user a later call to session.getSaveBatch() returns undefined and then I cannot start the batch to send the modifications to the server.
How can I solve this?
Well after reading a lot I found that Ext won't save the changed relationships between two models at least on 5.1.1.
I've had to workaround this by placing an aditional field on the left model (I named it isDirty) with a default value of false and set it true to force the session to send the update to the server with getSaveBatch.
Later I'll dig into the code to write an override to BatchVisitor or a custom BatchVisitor class that allow to save just associations automatically.
Note that this only occurs when you want to save just the association between the two models and if you also modify one of the involved entities then the association will be sent on the save batch.
Well this was interesting, I've learned a lot about Ext by solving this simple problem.
The solution I came across is to override the BatchVisitor class to make use of an event handler for the event onCleanRecord raised from the private method visitData of the Session class.
So for each record I look for left side entities in the matrix and if there is a change then I call the handler for onDirtyRecord which is defined on the BatchVisitor original class.
The code:
Ext.define('Ext.overrides.data.session.BatchVisitor', {
override: 'Ext.data.session.BatchVisitor',
onCleanRecord: function (record) {
var matrices = record.session.matrices
bucket = null,
ops = [],
recordId = record.id,
className = record.$className;
// Before anything I check that the record does not exists in the bucket
// If it exists then any change on matrices will be considered (so leave)
try {
bucket = this.map[record.$className];
ops.concat(bucket.create || [], bucket.destroy || [], bucket.update || []);
var found = ops.findIndex(function (element, index, array) {
if (element.id === recordId) {
return true;
}
});
if (found != -1) {
return;
}
}
catch (e) {
// Do nothing
}
// Now I look for changes on matrices
for (name in matrices) {
matrix = matrices[name].left;
if (className === matrix.role.cls.$className) {
slices = matrix.slices;
for (id in slices) {
slice = slices[id];
members = slice.members;
for (id2 in members) {
id1 = members[id2][0]; // This is left side id, right side is index 1
state = members[id2][2];
if (id1 !== recordId) { // Not left side => leave
break;
}
if (state) { // Association changed
this.onDirtyRecord(record);
// Same case as above now it exists in the bucket (so leave)
return;
}
}
}
}
}
}
});
It works very well for my needs, probably it wont be the best solution for others but can be a starting point anyways.
Finally, if it's not clear yet, what this does is give the method getSaveBatch the ability to detect changes on relationships.
I am using dexie.js to interface with IndexedDB. I am wondering if it is possible to orderby or sortby using more than one index at once (eg. db.people.orderBy( index1, desc : index2, asc )...
If it is possible, what is the correct syntax?
Either use compound indexes, or use Collection.and().
If you can live with only targeting Chrome, Firefox or Opera, you can use compound indexes. If it must work on Safari, IndexedDBShim, Edge or IE, you cannot use compound indexes today. There's a shim that enables it for IE/Edge though, but it is still in beta, so I would recommend to instead use Collection.and() for those cases.
Let' say you have a form where users can fill in various attributes of friends:
<form>
<input name="name"/>
<input name="age"/>
<input name="shoeSize" />
</form>
Using Collection.and()
First, pick the most probably index to start your search on. In this case, "name" would be a perfect index that wouldn't match so many items, while age or shoeSize would probably match more friends.
Schema:
db.version(X).stores({
friends: "id, name, age, shoeSize"
});
Query:
function prepareQuery () {
// Pick a good index. The picked index will filter out with IndexedDB's built-in keyrange
var query;
if (form.name.value) {
query = db.friends.where('name').equals(form.name.value);
} else if (form.age.value) {
query = db.friends.where('age').equals(parseInt(form.age.value));
} else if (form.shoeSize.value) {
query = db.friends.where('shoeSize').equals(parseInt(form.shoeSize.value));
} else {
query = db.friends.toCollection();
}
// Then manually filter the result. May filter a field that the DB has already filtered out,
// but the time that takes is negligible.
return query.and (function (friend) {
return (
(!form.name.value || friend.name === form.name.value) &&
(!form.age.value || friend.age == form.age.value) &&
(!form.shoeSize.value || friend.shoeSize == form.shoeSize.value));
});
}
// Run the query:
form.onsubmit = function () {
prepareQuery() // Returns a Collection
.limit(25) // Optionally add a limit onto the Collection
.toArray(function (result) { // Execute query
alert (JSON.stringify(result, null, 4));
})
.catch (function (e) {
alert ("Oops: " + e);
});
}
Using compound indexes
As written above, compound indexes code will only work on mozilla- and chromium based browsers.
db.version(x).stores({
friends: "id, name, age, shoeSize," +
"[name+age+shoeSize]," +
"[name+shoeSize]," +
"[name+age]," +
"[age+shoeSize]"
});
The prepareQuery() function when using compound indexes:
function prepareQuery() {
var indexes = []; // Array of Array[index, key]
if (form.name.value)
indexes.push(["name", form.name.value]);
if (form.age.value)
indexes.push(["age", parseInt(form.age.value)]);
if (form.shoeSize.value)
indexes.push(["shoeSize", parseInt(form.shoeSize.value)]);
var index = indexes.map(x => x[0]).join('+'),
keys = indexes.map(x => x[1]);
if (indexes.length === 0) {
// No field filled in. Return unfiltered Collection
return db.friends.toCollection();
} else if (indexes.length === 1) {
// Single field filled in. Use simple index:
return db.friends.where(index).equals(keys[0]);
} else {
// Multiple fields filled in. Use compound index:
return db.friends.where("[" + index + "]").equals(keys);
}
}
// Run the query:
form.onsubmit = function () {
prepareQuery() // Returns a Collection
.limit(25) // Optionally add a limit onto the Collection
.toArray(function (result) { // Execute query
alert (JSON.stringify(result, null, 4));
})
.catch (function (e) {
alert ("Oops: " + e);
});
}
Using arrow functions here to make it more readable. Also, you're targeting chromium or firefox and they support it already.
I'm displaying a table with multiple rows and columns. I'm using a JQUERY plugin called uiTableFilter which uses a text field input and filters (shows/hides) the table rows based on the input you provide. All you do is specify a column you want to filter on, and it will display only rows that have the text field input in that column. Simple and works fine.
I want to add a SECOND text input field that will help me narrow the results down even further. So, for instance if I had a PETS table and one column was petType and one was petColor -- I could type in CAT into the first text field, to show ALL cats, and then in the 2nd text field, I could type black, and the resulting table would display only rows where BLACK CATS were found. Basically, a subset.
Here is the JQUERY I'm using:
$("#typeFilter").live('keyup', function() {
if ($(this).val().length > 2 || $(this).val().length == 0)
{
var newTable = $('#pets');
$.uiTableFilter( theTable, this.value, "petType" );
}
}) // end typefilter
$("#colorFilter").live('keyup', function() {
if ($(this).val().length > 2 || $(this).val().length == 0)
{
var newTable = $('#pets');
$.uiTableFilter( newTable, this.value, "petColor" );
}
}) // end colorfilter
Problem is, I can use one filter, and it will display the correct subset of table rows, but when I provide input for the other filter, it doesn't seem to recognize the visible table rows that are remaining from the previous column, but instead it appears that it does an entirely new filtering of the original table. If 10 rows are returned after applying one filter, the 2nd filter should only apply to THOSE 10 rows. I've tried LIVE and BIND, but not working.
Can anyone shed some light on where I'm going wrong? Thanks!
The uiTableFilter plugin doesn't support what you're trying to do. A quick look at the source reveals this:
elems.each(function(){
var elem = jQuery(this);
jQuery.uiTableFilter.has_words(getText(elem), words, false)
? matches(elem)
: noMatch(elem);
});
and that expands to (essentially) this:
elems.each(function(){
var elem = jQuery(this);
jQuery.uiTableFilter.has_words(getText(elem), words, false)
? elem.show()
: elem.hide();
});
So all it does is spin through all the rows, .show() those that match, and .hide() those that don't; uiTableSorter doesn't pay attention to the current shown/hidden state of the rows and there's no way to tell it to filter on multiple columns.
If you really need your desired functionality then you can modify the plugin's behavior (the code is pretty small and simple) or just write your own. Here's a stripped down and simplified version that supports multiple filters and is a more conventional jQuery plugin than uiTableFilter:
(function($) {
$.fn.multiFilter = function(filters) {
var $table = $(this);
return $table.find('tbody > tr').each(function() {
var tr = $(this);
// Make it an array to avoid special cases later.
if(!$.isArray(filters))
filters = [ filters ];
howMany = 0;
for(i = 0, f = filters[0]; i < filters.length; f = filters[++i]) {
var index = 0;
$table.find('thead > tr > th').each(function(i) {
if($(this).text() == f.column) {
index = i;
return false;
}
});
var text = tr.find('td:eq(' + index + ')').text();
if(text.toLowerCase().indexOf(f.word.toLowerCase()) != -1)
++howMany;
}
if(howMany == filters.length)
tr.show();
else
tr.hide();
});
};
})(jQuery);
I'll leave error handling and performance as an exercise for the reader, this is just an illustrative example and I wouldn't want to get in the way of your learning. You could wire it up something like this:
$('#type').keyup(function() {
$('#leeLooDallas').multiFilter({ column: 'petType', word: this.value });
});
$('#color').keyup(function() {
$('#leeLooDallas').multiFilter([
{ column: 'petType', word: $('#type').val() },
{ column: 'petColor', word: this.value }
]);
});
And here's a live example (which assumes that you're going to enter something in "type" before "color"): http://jsfiddle.net/ambiguous/hdFDt/1/