Make index change by click on button - javascript

Logic of my page in a nutshell:
.csv file uploads (I have an array in code so I don't need to upload it every time)
code receives array from this .csv file that consists of [array, headers], where array - csv file data and headers - the first line of csv file and then it converts into object[]
after clicking on submit button selection table appears where you can choose which columns will be static, and others will be dynamic, so if f.e. you have 20 columns in file, you choose 3 static columns, and the rest is building with arrows that will change 'table page' (Everything can be seen in result and console)
I've separated columns so I can call data by index from array:
I have array: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
After it is being separated: [['h1', 'h2'], ['h3', 'h4'], ['h5', 'h6]]
My logic of building a table is creating two arrows and set initial index to 0. Right arrow will do index++ and left arrow will do index--.
When building a table, I can do like:
const headers = [['h1', 'h2'], ['h3', 'h4'], ['h5', 'h6]]
let tableIndex = 0
/* code with index++ and index-- */
arr.forEach(obj => {
const dynamicTableRow = document.createElement('tr')
headers[tableIndex].forEach(header => {
const dynamicTableDataCell = document.createElement('td')
dynamicTableDataCell.innerHTML = obj[header]
dynamicTableRow.appendChild(dynamicTableDataCell)
})
dynamicTableBody.appendChild(dynamicTableRow)
})
So the array that I actually need will be called by index - headers[tableIndex]
Full working code is here:
Updated codepen - here
My question is: how to make index change by clicking on buttons?
I had 2 variants how to make it:
Add onclick functions to buttons, but I don't really understand how to make it correctly and workable
Import buttons to js code, and do like
buttonPrevious.onclick = () => {
tableIndex--
/* render table with updated index */
}
buttonNext.onclick = () => {
tableIndex++
/* render table with updated index */
}
But this method is really not good, because I will render table 3 times - after headers selection and after click on one of the buttons

So the answer is much easier than I thought. I placed my dynamic table render into a function, and then created 2 eventListeners for each button.
Full working code is on codepen in the question
buttonPrevious.onclick = () => {
const copy = [...dynamicTableKeys]
const headers = getSplicedHeaders(copy)
console.log(dynamicTableKeys)
console.log(headers)
if (tableIndex === 0 && tableIndex > -1)
tableIndex = 0
else
tableIndex--
renderDynamicTable(tableIndex, dynamicTableKeys, headers)
}
buttonNext.onclick = () => {
const copy = [...dynamicTableKeys]
const headers = getSplicedHeaders(copy)
console.log(dynamicTableKeys)
console.log(headers)
console.log(tableIndex)
if (tableIndex === headers.length - 1)
tableIndex === headers.length - 1
else
tableIndex++
console.log(tableIndex)
renderDynamicTable(tableIndex, dynamicTableKeys, headers)
}

Related

Slice An Array Of Files In A Change Event Listener, When The Total Allowed Number Of Files Is Added In More Than One Go - JavaScript

I have a form that takes file uploads and it currently has a limit of 10 files per upload. There are PHP validations in the backend for this too.
When more than 10 files are attached, I currently have a JavaScript slice(0, 10) method inside a change event for the file input element, which removes any files (and their preview image thumbnails) when the number attached is more than 10 files.
// For each added file, add it to submitData (the DataTransfer Object), if not already present
[...e.target.files].slice(0,10).forEach((file) => {
if (currentSubmitData.every((currFile) => currFile.name !== file.name)) {
submitData.items.add(file);
}
});
The Issue
What I can’t seem to do though is work out a way to slice() the files array in a compound attachment situation, i.e. if 8 files are attached initially, and then the user decides to add another 4 prior to submitting the form, taking the total to 12. The current slice only happens when more than 10 are added in one go.
I have a decode() method that runs inside a loop (for every image attached) that carries out frontend validations, and a promiseAllSettled() method that waits for the last image to be attached prior to outputting a main error message telling the user to check the specific errors on the page.
Question
How do I slice the array on the total number of files appended, if the user has initially attached a file count less than 10, then attaches further files taking it more than 10 prior to form submission?
const attachFiles = document.getElementById('attach-files'), // file input element
dropZone = document.getElementById('dropzone'),
submitData = new DataTransfer();
dropZone.addEventListener('click', () => {
// assigns the dropzone to the hidden 'files' input element/file picker
attachFiles.click();
});
attachFiles.addEventListener('change', (e) => {
const currentSubmitData = Array.from(submitData.files);
console.log(e.target.files.length);
// For each added file, add it to 'submitData' if not already present (maximum of 10 files with slice(0, 10)
[...e.target.files].slice(0,10).forEach((file) => {
if (currentSubmitData.every((currFile) => currFile.name !== file.name)) {
submitData.items.add(file);
}
});
// Sync attachFiles FileList with submitData FileList
attachFiles.files = submitData.files;
// Clear the previewWrapper before generating new previews
previewWrapper.replaceChildren();
// the 'decode()' function inside the 'showFiles()' function is returned
// we wait for all of the promises for each image to settle
Promise.allSettled([...submitData.files].map(showFiles)).then((results) => {
// output main error message at top of page alerting user to error messages attached to images
});
}); // end of 'change' event listener
function showFiles(file) {
// code to generate image previews and append them to the 'previewWrapper'
// then use the decode() method that returns a promise and do JS validations on the preview images
return previewImage.decode().then(() => {
// preform JS validations and append
}).catch((error) => {
console.log(error)
});
} // end of showfiles(file)
Instead of looking into your whole code, Here I came up with the solution/Suggestion as per looking into a specific piece of code.
Once you spliced the initial set of files, After that you can check how many files are remaining and then you can pass the second parameter in the splice dynamically based on the required number of files.
Steps :
Create a separate empty array and insert first selection of files into that array on change event.
Now check for this newly created array length, if there is any number of elements then you can update the 2nd splice method parameter like this :
secondParameter = secondParameter - newArr.length
Live Demo :
let splicedArrCount = 10;
let resArr = [];
function spliceArr() {
const inputVal = Number(document.getElementById('number').value);
if (inputVal) {
const arr = Array(inputVal).fill(inputVal);
if (!resArr.length) {
resArr = [...arr];
} else if (resArr.length) {
splicedArrCount = splicedArrCount - resArr.length
resArr.push(...arr.splice(0, splicedArrCount));
}
}
console.log(resArr);
}
<input type="number" id="number" onChange="spliceArr()"/>
In the above demo, You can do a testing by inserting a number into a input box, which will convert the number into an array elements. For ex. If you will enter 8, it will create an array with 8 elements and then if you pass 4, it will update an array. i.e. [8, 8, 8, 8, 8, 8, 8, 8, 4, 4]
We can keep the count of the number of files already added.
Instead of [...e.target.files].slice(0,10).forEach((file) => {}, we can do something like:
var numberOfFilesToAdd = 10 - currentSubmitData.length;
[...e.target.files].slice(0,numberOfFilesToAdd).forEach((file) => {}
Keep an array (below, called allSelectedFiles) of all of the files selected so far, and keep adding to that array as user selects more.
Keep another array (below, called filesForUpload) that's a subset of the first array, filtered for uniqueness and sliced to the max length. Present this subset array in the DOM to give user feedback, and use it to drive the actual upload on submit.
let allSelectedFiles = [];
let filesForUpload = [];
const totalAllowed = 4; // 10 for the OP, but 3 is simpler to demo
const attachFiles = document.getElementById('attach-files');
attachFiles.addEventListener('change', e => {
allSelectedFiles = [... allSelectedFiles, ...e.target.files];
let filenames = new Set();
filesForUpload = allSelectedFiles.filter(f => {
let has = filenames.has(f.name);
filenames.add(f.name);
return !has;
});
filesForUpload = filesForUpload.slice(0, totalAllowed);
showFiles(filesForUpload);
});
// unlike the OP, just to demo: fill a <ul> with filename <li>'s
function showFiles(array) {
let list = document.getElementById('file-list');
while(list.firstChild) {
list.removeChild( list.firstChild );
}
for (let file of array) {
let item = document.createElement('li');
item.appendChild(document.createTextNode(file.name));
list.appendChild(item);
}
}
// on form submit, not called in the above, trim the selected files
// using the same approach: someSelectedFiles()
function submit() {
/*
let submitData = new DataTransfer();
for (let file of filesForUpload) {
submitData.add(file)
}
// and so on
*/
}
<h3>Input some files</h3>
<p>To see the logic, choose > 4 files, choose some overlapping names</p>
<input type="file" id="attach-files" multiple />
<ul id="file-list"></ul>
Slicing before checking duplicates can possibly drop valid values.
I would go for something like this:
for (const file of e.target.files) {
if (currentSubmitData.every((currFile) => currFile.name !== file.name)) {
submitData.items.add(file);
}
if (submitData.items.size >= 10) break; // assuming submitData.items is a Set
}

JSON Parse error undefined variable u in the beggning / can not read subSubItems${i} while repeater data has values

I am kinda new to the Velo coding / javascript in general and I am trying to re-create this tutorial --> https://support.wix.com/en/article/velo-tutorial-creating-an-expanding-mega-menu
Without the second menu (only a main menu and a strip with the items like attached screenshot).
I have a problem with javascript and wix coding. Even though I have created the collections correctly for some reason I can not open the site menu correctly. I am also attaching a wix forum link: https://www.wix.com/velo/forum/coding-with-velo/typeerror-cannot-read-properties-of-undefined-reading-filter.
Can not read the data subSubItems{i} from repeaterData, while it has all the necessary data in it
I am also attaching a screenshot of the database inself for review. (Json format on subSubItems) and the wix site itself as of right now: https://giannisliko.wixsite.com/my-site-1
The global page coding is this:
// The code in this file will load on every page of your site
//-------------Imports-------------//
import wixData from 'wix-data';
//-------------Global Variables-------------//
//Number of Submenu 2 repeaters.
const subLevel2RepeaterCount = 5;
//Object containing all menu data from subTitles database collection.
let menuData;
$w.onReady(async () => {
//Get the menu data from the collection.
menuData = await wixData.query("SubTitlesCollection").find().then(result => result.items);
//console.log(menuData);
//Set up each Submenu 2 repeater as it is loaded.
for (let i = 1; i <= subLevel2RepeaterCount; i++) {
$w(`#repeaterSubSub${i}`).onItemReady(($item, itemData, index) => {
//Get the repeater button from its ID.
const repeaterButton = $item(`#buttonSubLevelTwo${i}`)
//Set the item label.
repeaterButton.label = itemData.label;
//Set the item link.
repeaterButton.link = itemData.url;
});
}
});
export function buttonMainMenu_mouseIn(event) {
//Get the ID of the Submenu 1 button the mouse hovers over.
const selectedRootId = event.context.itemId;
//Get all the data of the Submenu 2 related to Submenu 1.
const repeaterData = menuData.filter(item => item.menu === selectedRootId);
const repeaterData2 = menuData.filter(item => item._id === selectedRootId);
console.log(repeaterData2);
//Set up the box element corresponding to the selected button in Submenu 2.
setSubSubMenu(repeaterData);
//Show the Submenu 2 box.
$w('#megaMenuStrip').expand();
}
export function repeaterMainMenu_mouseOut(event) {
}
function createUniqueId() {
//Creating a Unique Id for each of the menu sub-items by getting the current millisecond and adding a random number from 1 to 1000
let id = String(+new Date() + Math.floor(Math.random() * 1000))
return id;
}
function setSubSubMenu(repeaterData) {
//Set the image of the Submenu 1
//$w('#rangeMenuImage').src = repeaterData.img1;
for (let i = 1; i <= subLevel2RepeaterCount; i++) {
//Convert the Submenu 2 string to a Javascript object.
console.log(repeaterData);
console.log(repeaterData[`subSubItems1`]);
const dataSubSub = JSON.parse(repeaterData[`subSubItems${i}`]);
//Set a unique ID for each item.
console.log(dataSubSub);
dataSubSub.forEach(subSubItem => {
subSubItem._id = createUniqueId();
})
//Set the Submenu 2 data in the repeater.
$w(`#repeaterSubSub${i}`).data = dataSubSub;
}
}
export function megaMenuStrip_mouseOut(event) {
$w('#megaMenuStrip').collapse();
}
/**
* Adds an event handler that runs when the mouse pointer is moved
onto the element.
You can also [define an event handler using the Properties and Events panel](https://support.wix.com/en/article/velo-reacting-to-user-actions-using-events).
[Read more](https://www.wix.com/corvid/reference/$w.Element.html#onMouseIn)
* #param {MouseEvent} event
*/
/**
* Sets the function that runs when a new repeated item is created.
[Read more](https://www.wix.com/corvid/reference/$w.Repeater.html#onItemReady)
* #param {$w} $item
*/
Thank you very much in advance
I can't say for sure what's going on, but I can try to point you in the right direction.
If repeaterData is undefined, that means something is probably going wrong on the following line:
const repeaterData = menuData.filter(item => item._id === selectedRootId)[0]
The problem there could be that menuData is undefined, which would mean your query is no good.
Or it could be that none of the item IDs match the selectedRootId. I'm guessing that is the case. Looks like in the tutorial they aren't try to match with item._id like you are doing. It's really hard to tell exactly what's wrong there, but I'm pretty sure that's where your issue is. It could be a problem with your repeater IDs or it could be with the data coming from the collection. Either way, you're not getting any matches there.

How to sort the checked and unchecked columns in alphabetical order in ag grid sidebar panel

I am using ag-grid to have the tool panel on the sidebar which has column checkboxes.I am having issue with sorting the unchecked columns in the alphabetical order.
I am trying to achieve some sort of functionality like shown the ag grid example.
I am using the below function to sort the columns by checked and unchecked order but not able to achieve alphabetical sorting on checked and unchecked columns.
const sortColumns = (columnDefs: gridColDef[]): void => {
columnDefs.sort((cd1,cd2) => +cd1.hide - +cd2.hide);
};
sortColumns(gridColumns);
Assuming that your array contains object (because you read the .hide value on them), are you referring to the correct variable when sorting? Is the name on the object under .hide?:
const sortColumns = (columnDefs: gridColDef[]): void => {
columnDefs.sort((cd1,cd2) => cd1.name - cd2.name); //Sort by name field
};
sortColumns(gridColumns);
but it would seem simpler to just call it on the array you want directly:
gridColumns.sort((cd1,cd2) => cd1.name - cd2.name);
You can sort it then by checked or not afterwards to group checked and un-checked together:
gridColumns.sort((cd1,cd2) => cd1.hide - cd2.hide);
It would be helpful to see the structure of the data your are trying to sort too.
Option 1
If you want to sort the columns in the sidebar only, use the handler onGridReady() and the api argument to apply a Custom Column Layout:
<AgGridReact
onGridReady={({api}) => {
const columnsToolPanel = api.getToolPanelInstance("columns");
const sortedColumnDefs = [...columnDefs].sort((cd1, cd2) => {
if (cd1.field < cd2.field) return -1;
if (cd1.field > cd2.field) return 1;
return 0;
});
// set custom Columns Tool Panel layout
columnsToolPanel.setColumnLayout(sortedColumnDefs);
}}
//...other props
/>
Option 2
If you want to sort the columns in the table and the sidebar - it's a bit simpler - sort the column defs first, then pass it into the component:
const sortedColumnDefs = [...columnDefs].sort((cd1, cd2) => {
if (cd1.field < cd2.field) return -1;
if (cd1.field > cd2.field) return 1;
return 0;
});
return (
<AgGridReact
columnDefs={sortedColumnDefs}
// ...other props
/>
);
--EDIT--
Option 3
If you want to display the columns in the sidebar like so:
Visible columns in default order
Hidden columns in alphabetical order
Solution is a bit more complex, use the onColumnVisible() handler and its columnApi argument to access a list of the columns. Separate visible from hidden using key visible, and sort accordingly.
<AgGridReact
onColumnVisible={({ api, columnApi }) => {
const columnsToolPanel = api.getToolPanelInstance("columns");
const columns = columnApi.getAllColumns();
const visibleColumns = columns.filter(
({ visible }) => visible === true
);
const hiddenColumns = columns.filter(
({ visible }) => visible === false
);
const sortedHiddenColumns = [...hiddenColumns].sort(
(cd1, cd2) => {
if (cd1.colDef.field < cd2.colDef.field) return -1;
if (cd1.colDef.field > cd2.colDef.field) return 1;
return 0;
}
);
const newColumns = [...visibleColumns, ...sortedHiddenColumns];
const newColDefs = newColumns.map(({ colDef }) => colDef);
columnsToolPanel.setColumnLayout(newColDefs);
}}
// ...other props
/>
Live Demo

Getting previous/next row node in a sorted grid

I have a grid with a default sorted on a column and I'm having problems getting the next/previous rows by adding or substracting from the currently selected row ID.
Here's the column with the default sort
{
headerName: "Created",
field: "createdOn",
cellRenderer: (params) => {
return WebModule.Utils.dateFormat(params.value);
},
sort: "desc",
width: 125
},
And here's my logic to get previous/next row
class ResultModal {
constructor(params) {
this.params = params
let rowIndex = params.rowIndex;
this.previousRow = params.api.getRowNode(rowIndex - 1);
this.nextRow = params.api.getRowNode(rowIndex + 1);
this.result = params.data;
}
}
I pass the whole ag-grid params object to a modal so I can navigate the grid records from buttons in the modal.
The issue is that if I run the above logic with the 2nd row selected, params.rowIndex is 1, I get nextRow with (1+1) but the actual rowIndex of nextRow will be something like 2245 (I have lots of data in the grid).
So I end up selecting a row burried deep down in the grid instead of the actual 3rd row displayed.
Do I need to use something else than getRowNode when the grid is sorted/filtered ?
I ended up using api.getDisplayedRowAtIndex()
let node = params.api.getSelectedNodes()[0];
let rowIndex = node.rowIndex;
this.previousRow = params.api.getDisplayedRowAtIndex(rowIndex - 1);
this.nextRow = params.api.getDisplayedRowAtIndex(rowIndex + 1);
This really gets the previous and next row regardless of the sort/filter options.
One possible solution might be to use
gridOptions.api.forEachNodeAfterFilterAndSort
which iterates over the rows as they are displayed. Note that the callback passes the RowNode itself and the row-index in the grid.
function forEachNodeAfterFilterAndSort(
callback: (rowNode: RowNode, index: number) => void
): void;
https://www.ag-grid.com/javascript-data-grid/grid-api/#reference-rowNodes

How to remove value from array using index (Ant Design specific)?

I am creating a questionnaire type form using ReactJs and Ant Design. It is a follow up question of How to create a questionnaire type form using Ant Design?
Now I am succeeded in adding new questions and their respective answers but not in removing them. Let's suppose I have added three questions and when I am trying to remove any one of them, its always removing the last one. The related code for removing is as follows:
remove = k => {
console.log(k);
const { form } = this.props;
// can use data-binding to get
const keys = form.getFieldValue("keys");
// We need at least one passenger
if (keys.length === 1) {
return;
}
keys.splice(k, 1);
// can use data-binding to set
form.setFieldsValue({
keys: keys
});
console.log(keys);
};
The complete code can be found as a demo on codesandbox.io.
I have done something similar in the past. Got rid of the boilerplate of antd's remove and replaced with this. Every time I add a row I push that row (object) to formRows array then removing like this:
remove = key => {
const newRows = this.state.formRows.filter(r => r.key !== key)
this.setState(
prev => ({
formRows: newRows
})
)
}

Categories