new objects created in Vuejs getting updated to last value - vue.js

I am new to Vuejs but am having an issue with some code.
I am trying to 'flatten' a series of line_items of orders into a list of items and then create meta information for each object. The first part works fine but it appears as if the reactive nature of Vuejs is causing the last value of these newly created objects to extend across all previous incarnations.
I have created a fiddle here. http://jsfiddle.net/trestles/ruj7hzcf/3/
I presume that the problem is in creating my item (~ line 70 in fiddle).
// here's the problem - why is quantity being updated to the last value?
var item = {item_id: line_item.menu_item_id, header: line_item.menu_item_header, quantity: line_item.quantity, locations: locations }
this.items.push(item);
Why in this case would item be updated to the last value?
In the table, the results are:
test item 3 5 8
spiced shrimp with horseradish cocktail sauce (per dozen) 9 5 8
dates with bacon & parmesan (per dozen) 5 5 8
marcona almonds (serves 8) 6 5 8
marinated olives (serves 8) 8 5 8
and should be
test item 3 3 0
spiced shrimp with horseradish cocktail sauce (per dozen) 9 2 7
dates with bacon & parmesan (per dozen) 5 5 0
marcona almonds (serves 8) 6 0 6
marinated olives (serves 8) 8 0 8
What am I doing wrong?

The root of most problems in your existing code is reusing the same objects - elements of this.locations and this.orderPickupTimes arrays - again and again as you pass those through createNewItem. Augment those lines in your code:
// instead of var location = this.locations[j];
var location = Object.assign({}, this.locations[j]);
// and within the internal loop
// instead of var order_pickup_time = this.orderPickupTimes[k];
var order_pickup_time = Object.assign({}, this.orderPickupTimes[k]);
... and see the difference it makes.
Still, it's only part of the problem. The key idea of your code is storing -alongside each 'item' object - the whole list of locations and pickup times, having the ones with some orders set their 'quantity' attribute changed.
But to do this, you need to differentiate between the items already processed (with their structures already filled) - and fresh items. It's cumbersome, yes, but might work with something like this:
var locations = [];
var item = this.items.find(it => it.item_id === line_item.menu_item_id);
if (item) {
locations = item.locations;
}
else {
item = {item_id: line_item.menu_item_id, header: line_item.menu_item_header, quantity: 0, locations: locations };
this.items.push(item);
}
... and have this repeated all the time you need to choose between creating a new location or orderPickupTime - or reusing the existing ones.
Here's the demo illustrating this approach.
Still, I'd change two major parts here.
First, I'd create a dedicated - private - function to group the order items by their locations and order pickup times. Something like this:
function _groupItemsByOrders(orders) {
return orders.reduce(function(groupedItems, order) {
var locationId = order.location_id;
var orderPickupTimeKey = order.order_pickup_time_short;
order.line_items.forEach(function(lineItem) {
if (!groupedItems.hasOwnProperty(lineItem.menu_item_id)) {
groupedItems[lineItem.menu_item_id] = {
header: lineItem.menu_item_header,
quantity: 0,
locations: {}
};
}
var groupedItem = groupedItems[lineItem.menu_item_id];
if (!groupedItem.locations.hasOwnProperty(locationId)) {
groupedItem.locations[locationId] = {};
}
var groupedItemLocation = groupedItem.locations[locationId];
if (!groupedItemLocation.hasOwnProperty(orderPickupTimeKey)) {
groupedItemLocation[orderPickupTimeKey] = 0;
}
groupedItemLocation[orderPickupTimeKey] += lineItem.quantity;
groupedItem.quantity += lineItem.quantity;
});
return groupedItems;
}, {});
}
Second, I'd rearranged that template so that it takes the order of locations and orderPickupTimes from header arrays, then maps it to groupedData. Something like this:
<tr v-for="(item, item_id) in groupedItems">
<td>{{item_id}}</td>
<td>{{item.header}}</td>
<td>{{item.quantity}}</td>
<template v-for="location in locations">
<td v-for="opt in orderPickupTimes">
{{item.locations[location.id][opt.short]}}
</td>
</template>
</tr>
Now, your 'mounted' hook would look like this:
mounted(){
axios.get('http://www.mocky.io/v2/59f8ceb52d00004d1dad41ec')
.then(response => {
this.locations = response.data.locations;
this.orderPickupTimes = response.data.order_pickup_times;
this.groupedItems = _groupItemsByOrders(response.data.orders);
});
}
Here's the demo.

Related

Shopify: how to retrieve shop level metafield in the cartpage?

At a shop level, I created a metafield for disabled dates as shown in the image below, that I want to retrieve and assign to a LIQUID variable in the code following the image.
Code in cart page:
window.addEventListener("load", function() {
// Don't add 0 before month and date to make it two digit.
//var disabledDays = ["2022-5-30","2022-7-4","2022-9-5","2022-11-24","2022-12-23","2022-12-24","2022-12-25","2022-12-30","2022-12-31","2023-1-1","2023-1-2"];
disabledDays = {{ shop.metafields.disabledDays.value }};
var minDate = new Date();
var maxDate = new Date();
maxDate.setDate((maxDate.getDate()) + 60);
minDaysToShip = 2; // Default minimum days
if (minDate.getDay() == 5) {
// Friday. Set min day to Tuesday. 4 days from now.
minDaysToShip = 4;
} else if (minDate.getDay() == 6) {
I see that {{ shop.metafields.disabledDates.value }}; is not reading the metadata content. Please show me the right way to do it.
If you are in a .liquid file, the following syntax should work for string type metafields:
{{ shop.metafields.disabledDays.disabledDays }}
Shopify added more metafield types and the new ones can indeed be accessed through the .value key, see this thread for more details:
https://community.shopify.com/c/technical-q-a/how-to-access-json-data-from-the-new-shopify-native-metafields/td-p/1258088

Filter data from arrays

What I need is to sort data I get from an API into different arrays but add '0' value where there is no value at 1 type but is at the other type/s. Now is this possible with array.filter since its faster then a bunch of for and if loops ?
So let's say I get following data from SQL to the API:
Day Type Amount
-----------------------
12.1.2022 1 11
12.1.2022 2 4
13.1.2022 1 5
14.1.2022 2 9
16.1.2022 2 30
If I run this code :
this.data = result.Data;
let date = [];
const data = { 'dataType1': [], 'dataType2': [], 'dataType3': [], 'dataType4': [] }
/*only writing example for 2 types since for 4 it would be too long but i desire
answer that works for any amount of types or for 4 types */
this.data.forEach(x => {
var lastAddress = date[date.length - 1]
if (x.type == 1) {dataType1.push(x.Amount) }
if (x.type == 2) {dataType2.push(x.Amount) }}
lastAddress != x.Day ? date.push(x.Day) : '';
The array I get for type1 is [11,5]
and for type2 I get [4,9,30].
And for dates i get all the unique dates.
But the data I would like is: [11,5,0,0] and [4,0,9,30]
The size of array also has to match the size of Day array at the end.
which would be unique dates.. in this case:
[12.1.2022, 13.1.2022, 14.1.2022, 16.1.2022]
I have already tried to solve this with some for, if and while loops but it gets way too messy, so I'm looking for an alternative.
Also i have 4 types but for reference i only wrote sample for 2.
you can
first get the uniq values
loop over the data to create an array of object with
{date:string,values:number[]}
//create a function:
matrix(data:any[])
{
const uniqTypes=data.reduce((a,b)=>a.indexOf(b.type)>=0?a:
[...a,b.type],[])
const result=[]
this.data.forEach((x:any)=>{
let index=result.findIndex(r=>r.date==x.date)
if (index<0)
{
result.push({date:x.date,values:[...uniqTypes].fill(0)})
index=result.length-1;
}
result[index].values[uniqTypes.indexOf(x.type)]=x.amount
})
return result
}
//and use like
result=this.matrix(this.data);
NOTE: You can create the uniqType outside the function as variable and pass as argument to the function
stackblitz
const type1 = [];
const type2 = [];
data.forEach(item=>{
if(item.type==1){
type1.push(item.amount)
type2.push(0)
}else{
type1.push(0)
type2.push(item.amount)
}
})
console.log(type1)
console.log(type2)

How to add more default empty rows in list view?

I need to add more default empty rows in the list view.
Right now, it only gives 4 empty rows on a new record. However, I need to add, say, 10 more default empty rows.
This is because I increased the height of the table using css.
.o_list_view .o_list_table {
height: 800px;
}
The result is that it still has 4 rows on default but every row has their height as 25% of the table height. Therefore, I need to add some rows into it so that their height will be fit to the new table height again.
Or another solution, if possible, remove the auto height adjustment for the rows in the table so that it won't be scaled according to the table height.
Either solution is acceptable.
As long as the length of the lines is less than 4, Odoo will try to add an empty row.
To add more default empty rows, you can alter ListRenderer _renderBody:
_renderBody: function () {
var self = this;
var $rows = this._renderRows();
while ($rows.length < 14) {
$rows.push(self._renderEmptyRow());
}
return $('<tbody>').append($rows);
},
You can also set a empty_rows attribute in the tree tag to define the number of empty rows:
XML:
<tree string="" editable="bottom" empty_rows="4">
JavaScript:
var ListRenderer = require('web.ListRenderer');
ListRenderer.include({
_renderBody: function () {
var self = this;
var empty_rows = 4;
if (self.arch && self.arch.attrs.empty_rows) {
empty_rows = self.arch.attrs.empty_rows;
}
var $rows = this._renderRows();
while ($rows.length < empty_rows) {
$rows.push(this._renderEmptyRow());
}
return $('<tbody>').append($rows);
},
});
Check Assets Management on how to add the above code to web.assets_backend bundle and Javascript Module System to create a JavaScript module

Sitecore Lucene search - hit count differs from result count

I have the following method to return search results based on a supplied query
private List<Item> GetResults(QueryBase qBase)
{
using (IndexSearchContext sc = SearchManager.GetIndex("story").CreateSearchContext())
{
var hits = sc.Search(qBase, int.MaxValue);
var h1 = hits.FetchResults(0, 25);
var h2 = h1.Select(r => r.GetObject<Item>());
var h3 = h2.Where(item => item != null);
return h3.ToList();
}
}
The index being searched indexes web and master content. If I pass in a query that I know matches a single published item and break at the line beginning var h2 = then I see the variable hits has 2 items. This I expect because actually the items are both the same item, one from web and one from master.
However, the variable h1 only has a single result. The result from web has been omitted.
This is the case whether I'm debugging in the context of web or master. Can anyone explain?
When fetching the items using FetchResults method, Sitecore groups the items from lucene by the id of the item. First of the items becomes a SearchResult in the resulting SearchResultCollection object, and other items become Subresults for this results.
For example if you have a home item with id {110D559F-DEA5-42EA-9C1C-8A5DF7E70EF9} with one published version and 4 versions in different languages for the home item, what you'll get from lucene is a single result and 4 subresults for this result:
using (IndexSearchContext sc = SearchManager.GetIndex("story").CreateSearchContext())
{
var hits = sc.Search(qBase, int.MaxValue);
var h1 = hits.FetchResults(0, 25);
foreach (SearchResult result in h1)
{
var url = result.Url;
foreach (SearchResult subresult in result.Subresults)
{
var subUrl = subresult.Url; // other versions of this item
}
}
}
The urls for results and subresults in my case would be:
sitecore://web/{110D559F-DEA5-42EA-9C1C-8A5DF7E70EF9}?lang=en&ver=1
sitecore://master/{110D559F-DEA5-42EA-9C1C-8A5DF7E70EF9}?lang=en&ver=1 (subresult)
sitecore://master/{110D559F-DEA5-42EA-9C1C-8A5DF7E70EF9}?lang=ja-JP&ver=1 (subresult)
sitecore://master/{110D559F-DEA5-42EA-9C1C-8A5DF7E70EF9}?lang=de-DE&ver=1 (subresult)
sitecore://master/{110D559F-DEA5-42EA-9C1C-8A5DF7E70EF9}?lang=da&ver=1 (subresult)
so for retrieving the all the items with their versions you can use this code:
private List<Item> GetResults(QueryBase qBase)
{
using (IndexSearchContext sc = SearchManager.GetIndex("story").CreateSearchContext())
{
var hits = sc.Search(qBase, int.MaxValue);
var h1 = hits.FetchResults(0, 25);
var h2 = h1.Select(r => r.GetObject<Item>()).ToList();
// add other versions of item to the resulting list
foreach (IEnumerable<SearchResult> subresults in h1.Select(sr => sr.Subresults))
{
h2.AddRange(subresults.Select(r => r.GetObject<Item>()));
}
var h3 = h2.Where(item => item != null);
return h3.ToList();
}
}
You can not assume with item will be returned as the first one from the lucene and which items will be returned as subresults. If you want to get any specific item you need to pass version number, language and database to the query.

Object collection optimisation

I'm struggling with something I'm sure I should be able to do quite quick with Linq-to-Obj
Have 27 flowers, need a collection of Flower[] containing 5 items split over, roughly 6 records;
List<Flowers[]> should contain 6 Flowers[] entities, and each Flowers[] array item should contain 5 flower objects.
I currently have something like:
List<Flowers[]> flowers;
int counter = 0;
List<Flowers>.ForEach(delegate (Flower item) {
if (counter <= 5){
// add flowers to array, add array to list
}
});
I'm trying to optimise this as it's bulky.
[Update]
I can probably to an array push on objects, removing the items I'v already run through, but is there not an easier way?
var ITEMS_IN_GROUP = 5;
var result = list.Select((Item, Index) => new { Item, Index })
.GroupBy(x => x.Index / ITEMS_IN_GROUP)
.Select(g => g.Select(x=>x.Item).ToArray())
.ToList();
You may also want to try morelinq's Batch