Not allowing items in array to have the same value - arrays

On what I'm working right now is a little bit complex for my brain. I've got some data, and that data has a field 'position', and based on that position they are going to be displayed on the client side in that order(for example, for the last item user has added, he can change its position to be 1 and on client side it will be displayed first, then comes rest of the data), and he can always change the position(from 1 to 8, and then it will be displayed the last because that number of data is limited to maximum of 8).
But the problem is when user changes the position for example from 4 to 1, and there already is a data with position 1, so then we have to items with the same position, which should not happen. Is there a solution to go over the array, and check for same values and then replace them?
Example:
There are 2 items, item 1 has position 1 and item 2 has position 2. If we change item 2 position to 1, then both of them will have one, but that item 1 should automatically auto increment to 2.
What I tried so far was do forEach on the array, and check values with conditions but its not working the best. Is there some algorithm to accomplish this?
this.items.forEach((itemRes) => {
let itemDash = result;
if (itemRes.position === result.ordinal) {
if(itemRes.position !== result) {
itemRes.ordinal++;
}
} else if (itemRes.position === this.items.length && itemRes.ordinal >= 8) {
itemRes.position--;
}
})
Here is my code for checking and changing array items and their positions.

Kudos to this gist: https://gist.github.com/albertein/4496103
If I am understanding you correctly, you would want something like presented in the link, so when "TypeScriptifying" it and making it applicable to your (simplified) case:
array = [1, 2, 3, 4, 5];
move(array, element, delta) {
let index = array.indexOf(element);
let newIndex = index + delta;
//Already at the top or bottom.
if (newIndex < 0 || newIndex == array.length) return;
let indexes = [index, newIndex].sort(); //Sort the indexes
//Replace from lowest index, two elements, reverting the order
array.splice(indexes[0], 2, array[indexes[1]], array[indexes[0]]);
}
moveUp(element) {
this.move(this.array, element, -1);
}
moveDown(element) {
this.move(this.array, element, 1);
}
And the corresponding HTML:
<div *ngFor="let a of array">
{{a}}
<button (click)="moveUp(a)">Move Up</button>
<button (click)="moveDown(a)">Move Down</button>
</div>
StackBlitz
This can also give you some inspiration: Move an array element from one array position to another :)

Related

reactjs pagination showing undesired element when selecting backwards

I have created a pagination form in my reactjs app but I am having some problems.
The form should work in that way that if I have around 20 elements the array which displays the elements should be like "1, 2, 3, ... ,18 ,19, 20", and if the current selected element is 10, the array should be like" 1,2,3, ..., 9,10,11,...,18,19,20".
The problem is that when I go and select the elements forward from 1 to n, it works all good, but when I go backwards from 20 to 1 it just keeps creating the "..." element multiple times. Even stranger is that when I console.log the array, the elements look as they should, but the ... html element is still there!
Below are the pictures of the results I have now.
The result I get when I select the elements from n to 1 (the wrong result)
The result I get when I select elements from 1 to n (the desired result)
What I want to do is when I select backwards the 4th element, I want to have the elements "1, 2, 3, 4, 5, ..., 14, 15, 16" but I still get the "..." element appeared unnecessarily.
My code:
var pagearray=[]
if(nPages>10)
{
pagearray.push(1);
pagearray.push(2);
if(currentPage == 3)
{
pagearray.push(3);
pagearray.push(4);
pagearray.push("...");
}
if(currentPage==4)
{
pagearray.push(4);
pagearray.push(5);
pagearray.push("...");
}
if(currentPage == 5)
{
pagearray.push(4);
pagearray.push(5);
pagearray.push(6);
pagearray.push("...");
console.log("1");
}
if(currentPage > 5 && currentPage<parseInt(pageNumbers[pageNumbers.length-5]))
{
pagearray.push("...");
pagearray.push(currentPage-1);
pagearray.push(currentPage);
pagearray.push(currentPage+1);
pagearray.push("...");
}
setPageArray(pagearray);
And then in the end I use .map function to iterate and display all elements in the desired way:
{pageArray1.map(pgNumber => (
<li key={pgNumber}
className= {`page-item ${currentPage == pgNumber && pgNumber != "..." ? 'active' : ''} `} >
<a onClick={() => setCurrentPage(pgNumber)}
className='page-link'
>
{pgNumber}
</a>
</li>
}
Can you please help me and tell me what am I missing here?
Thank you in advance.
It may be because of the key={pgNumber}, because several elements will share the same key ('...'). I would go with key={`${pgNumber}-${index}`} in that case.

Apps Script - compare strings in two arrays

I am a stuck on this piece of code. I have an Example sheet with two tabs. The first tab is new items. A new item is comprised of two pieces, a attribute code (string), and an item ID (number). In the other tab "Locations" there are a bunch of empty locations. Each location has primary attribute (string), and a set of secondary attribute codes in a longer string.
I have assigned these two ranges to two unique arrays.
function matchcodes() {
var locss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Locations');
var lastlocRow = locss.getLastRow();
var newitems = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('New Items');
var lastNIRow = newitems.getLastRow();
var itemcodes = newitems.getRange("A1:B" + lastNIRow).getValues();
var locations = locationssheet.getRange("A2:D" + lastlocationRow).getValues();
Logger.log(itemcodes)
Logger.log(locations)}
What I am attempting to do is compare itemcodes[i][0] to locations[j][2] (match item attribute with location primary attribute). If the strings match I want to copy itemcodes[i][1] (ItemID) and set it as the value of locations[j][1]. If the strings do not match check the next iteration of locations[j][2].
If no matching attributes are found in locations[j][2], I would like to see if it is contained as a substring in locations[j][3] (starting back at the top and iterating through the whole list of secondary attributes. If the substring code is contained in loactions[j][3] I would like take the same action in the first IF condition.
Once a new item is matched, the loop can break, and the next item can be located itemcodes[i+1][0]. If no match is found in the primary or secondary search, also iterate to the next new item.
Where I'm struggling is writing the condition statements to compare both strings and substrings within strings.
//for (var i = 0; i < itemcodes.length; i++) {
//for (var j = 0; j < locations.length; j++) {
//if (itemcodes[i][0] == locations[j][2]) {
// I want set the value of locations[j][1] with itemcodes[i][1]
}
// if no match is found in entire [j][2] column, search for substring in locations[j][3] column
//if item match is found, or no match is found in all of [j][2] or [j][3] break loop and iterate to [i+1][0] and start the next loop
Input (3 iterations)
Results
Any help would be much appreciated. Or if you can point me to a similar thread. (I've not had any success finding a similar example) Thanks in advance!
Try:
function matchCodes() {
const newItems = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(`New Items`)
const locations = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(`Locations`)
const newItemsValues = newItems.getDataRange().getValues()
const locationsValues = locations.getDataRange().offset(1, 0).getValues()
newItemsValues.forEach(([attribute, id]) => {
const primaryTarget = locationsValues.findIndex(row => row[2] === attribute && row[1] === ``)
if (primaryTarget !== -1) return locationsValues[primaryTarget][1] = id
const secondaryTarget = locationsValues.findIndex(row => row[3].includes(attribute) && row[1] === ``)
if (secondaryTarget !== -1) return locationsValues[secondaryTarget][1] = id
})
locations.getDataRange().offset(1, 0).setValues(locationsValues)
}
Learn More:
Array.findIndex()
Destructuring Assignment

Google App Script: Dynamically add new rows in Google Sheets

I'm working on a sheet to build a list of products to import into Shopify.
For this, I have a pdf of some basic data (that is irrelevant here) out of which I build a string to crawl the product supplier's website and format the data in a way suitable for import in Shopify.
The products have a varying number of images (1 - 8), so I'm trying to build my script in a way that if a product has more than one image, I am trying to add additional rows under it and add every image past the first into a new row.
Here is my code:
function iterateThroughRows() {
// get spreadsheet
const sheet = SpreadsheetApp.getActive().getSheetByName("MySheet");
const data = sheet.getDataRange().getValues();
// Loop over rows
data.forEach( (row, rowIndex) => {
const imageSrcArray = [ /* list of image URLs, fetched from remote server */ ]
imageSrcArray.forEach( (img, arrayIndex) => {
if(arrayIndex == 0) { // for the first array item, just add it to the current row
const imageCell = sheet.getRange(rowIndex + 1, 24)
imageCell.setValue( imageSrcArray[arrayIndex] )
} else { // for each array item past the first one, add a new row and enter the value there
sheet.insertRows(rowIndex)
const imageCell = sheet.getRange(rowIndex + arrayIndex + 1, 24)
imageCell.setValue( imageSrcArray[arrayIndex] )
}
})
// adding some more values to other cells
});
}
As is this doesn't really work.
I worked on this all day yesterday and had a version using insertRowAfter() that did add additional rows, but added them all lumped together (i.e. there would be 15 rows after the first product, but none after any of the others). But since Google App Script doesn't have version control I lost that version.
I think the problem was that the forEach seems to move on to the newly created rows and keeps adding things from there rather than moving on to the initial next row.
So I'm more or less at the end of my wit with this. Any advise on how to properly do this would be highly appreciated.
I can understand your frustration, it is indeed because you care calculating the row based on the sheet in its version before you added new rows to it.
So my proposal would be to do this, as the currentRow allows you to track the current row you are working on. I also updated the insertRowAfter(), as I assume this is what you actually wanted to do.
let currentRow = 1;
data.forEach( (row, rowIndex) => {
const imageSrcArray = [ "img1URL", "img2URL"]
if( !imageSrcArray.length ) return
imageSrcArray.forEach( (img, arrayIndex) => {
if( arrayIndex == 0 ){
sheet.getRange(currentRow, 24).setValue( img )
} else {
sheet.insertRowAfter(currentRow)
sheet.getRange(currentRow+1, 24).setValue( img )
}
// New rows in between were created
currentRow++
})
});

Google Apps Script: how to create an array of values for a given value by reading from a two column list?

I have a set of data in a Google spreadsheet in two columns. One column is a list of article titles and the other is the ID of a hotel that is in that article. Call it list1.
Example data
I would like returned a new list with article titles in one column, and an array of the hotel IDs in that article in the other column. Call it list2.
Example data
There are thousands of lines that this needs to be done for, and so my hope was to use Google Apps Script to help perform this task. My original thinking was to
Create column 1 of list2 which has the unique article titles (no script here, just the G-sheets =unique() formula.
Iterate through the titles in list2, looking for a match in first column of the list1
If there is a match:
retrieve its corresponding value in column 2
push it to an empty array in column two of list2
move onto next row in list1
if no longer a match, loop back to step 2.
I've written the following code. I am currently getting a type error (TypeError: Cannot read property '0' of undefined (line 13, file "Code")), however, I wanted to ask whether this is even a valid approach to the problem?
function getHotelIds() {
var outputSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('list2');
var lastRow = outputSheet.getLastRow();
var data = outputSheet.getRange(2,1,lastRow,2).getValues();
var workingSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('list1');
var lastActiveRow = workingSheet.getLastRow();
var itemIDS = [];
for (var i=1; i<=data.length; i++) {
var currentArticle = data[i][0];
var lookupArticle = workingSheet[i][0];
if (currentArticle === lookupArticle) {
var tempValue = [workingSheet[i][1]];
itemIDS.push(tempValue);
}
}
}
Use a simple google sheets formula:
You can use a very simple formula to achieve your goal instead of using long and complicated scripts.
Use =unique(list1!A2:A) in cell A2 of list2 sheet to get the unique hotels.
and then use this formula to all the unique hotels by dragging it down in column B.
=JOIN(",",filter(list1!B:B,list1!A:A=A2))
You got the idea right, but the logic needed some tweaking. The "undefined" error is caused by the workingSheet[i][0]. WorkingSheet is a Sheet object, not an array of data. Also, is not necessary to get the data from list2 (output), it is rather the opposite. You have to get the data from the list1 (source) sheet instead, and iterate over it.
I added a new variable, oldHotel, which will be used to compare each line with the current hotel. If it's different, it means we have reached a different Hotel and the data should be written in list2.
function getHotelIds() {
var outputSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('list2');
var outLastRow = outputSheet.getLastRow();
var workingSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('list1');
var lastActiveRow = workingSheet.getLastRow();
var sourceValues = workingSheet.getRange("A2:B" + lastActiveRow).getValues();
var itemIDS = [];
var oldHotel = sourceValues[0][0]; //first hotel of the list
for (var i = 0; i < sourceValues.length; i++) {
if (sourceValues[i][0] == oldHotel) {
itemIDS.push(sourceValues[i][1]);
/*When we reach the end of the list, the oldHotel variable will never be different. So the next if condition is needed. Otherwise it wouldn't write down the last Hotel.
*/
if (i == sourceValues.length - 1) {
outputSheet.getRange(outLastRow + 1, 1, 1, 2).setValues([
[sourceValues[i][0], itemIDS.toString()]
]);
}
} else {
outputSheet.getRange(outLastRow + 1, 1, 1, 2).setValues([
[sourceValues[i - 1][0], itemIDS.toString()]
]);
oldHotel = sourceValues[i][0]; //new Hotel will be compared
outLastRow = outputSheet.getLastRow(); //lastrow has updated
itemIDS = []; //clears the array to include the next codes
}
}
}
I also converted the itemIDS array to a String each time, so it's written down in a single cell without issues.
Make sure each column of the Sheet is set to "Plain text" from Format > Number > Plain Text
References
getRange
setValues
toString()

ui-scroll buffer space at the top of viewport

I'm working with ui-scroll from angular-ui (great job, the component is awesome!).
Everything works well for the most part. Data is loaded from custom datasource as I scroll down. However, when I scroll back to the top, I end up with a lot of whitespace within the viewport.
Upon inspection, it looks like ui-scroll is adding a sub-div and setting the height dynamically, but for some reason this isn't getting set back to 0 when I scroll to the top of the viewport.
I'm guessing this has to do with the way my datasource is serving data. I'm also not wrapping my head around the negative indexing. Could someone explain how I should be accounting for a negative index in the datasource.get() function when I'm mapping to a standard pagination service (index + offset, etc)?
Alright, I think I found the solution... Hopefully this will help someone else because I couldn't really find a working example in the demos provided by the angular-ui team.
Essentially, when you receive a negative index, you have to 1) clamp the index to 0 and then adjust the count variable by the difference.
Here's some sample code:
dataSource.get = function (index, count, successCallback)
{
// Index provided is 1-based, we'll convert to 0-based and clamp at 0
var realIndex = Math.max(1, index) - 1;
if (index < 0) {
count -= 1 - index;
}
if (realIndex < 0 ||
count == 0 ||
realIndex >= vm.searchResultCount)
{
successCallback([]);
return;
}
...
Make a pagination call to a service that expects the first
page of results to be at index 0.
Ui-scroll datasource implementation in case of limited data array may look like this
var min = 1, max = 100;
dataSource.get = function(index, count, success) {
var start = Math.max(min, index);
var end = Math.min(index + count - 1, max);
if (start <= end) {
getDataAsync(start, start + end + 1).then(success);
}
else {
success([]);
}
};
This allows you to deal with data arrays starting with min index. Also you may easily remove the limitation on bottom via var end = index + count - 1.
But I also would like to say, that it may be a back end responsibility. Look at this sample: https://rawgit.com/angular-ui/ui-scroll/master/demo/append/append.html – data array is limited from 1 to 50 while the datasource (skipping details) is just
get: function (index, count, success) {
Server.request(index, count).then(success);
}

Resources