SyncFusion Drag and Drop Angular Tabs - javascript

I'm trying to get the Angular Drag and Drop tabs working with the SyncFusion component library. I have tried everything I can from the documentation but they still don't reorder.
Here's my HTML:
<div id="tab-container">
<div class="col-lg-8 content-wrapper control-section">
<ejs-tab id='draggableTab' #tabObj [allowDragAndDrop]='allowDragAndDrop' dragArea='#tab-container'
(created)='onTabCreate()' (onDragStart)='onTabDragStart($event)' (dragged)='onDraggedTab($event)'>
<e-tabitems>
<e-tabitem *ngFor="let item of headerText" [header]="item" [content]="contentTemplate"></e-tabitem>
</e-tabitems>
</ejs-tab>
</div>
</div>
And here are the relevant parts of the TypeScript file:
onTabCreate(): void {
const tabElement = document.getElementById("#draggableTab");
if (!isNullOrUndefined(tabElement)) {
tabElement.querySelector(".e-tab-header").classList.add("e-droppable");
tabElement.querySelector(".e-content").classList.add("tab-content");
}
}
onTabDragStart(args: DragEventArgs): void {
this.draggedItemHeader = <string> this.tabObj.items[args.index].header.text;
}
onDraggedTab(args: DragEventArgs): void {
const dragTabIndex: number = Array.prototype.indexOf.call(this.tabObj.element.querySelectorAll(".e-toolbar-item"), args.draggedItem);
const dropContainer: HTMLElement = <HTMLElement> args.target.closest("#TabContainer .content-wrapper .e-toolbar-item");
const dropNode: HTMLElement = <HTMLElement> args.target.closest("#TabContainer .content-wrapper .e-toolbar-item");
if (dropNode != null) {
args.cancel = true;
const dropIndex: number = Array.prototype.indexOf.call(dropContainer, dropNode);
console.log(dropIndex);
}
}
In the onDraggedTab method I am trying to get the index of where to place the tab, but can't figure it out. The dropNode is valid, and defined, but getting its index to swap it with the target (or the dragged tab) is not working.

You can get the dropped item index in dragged event using below customization. We have prepared a sample in which we have logged the dropped item index in the console which can be referred through the below link.
Sample: https://stackblitz.com/edit/angular-tab-drag-and-drop?file=app.component.ts
public onDraggedTab(args: DragEventArgs): void {
const dragTabIndex: number = Array.prototype.indexOf.call(
this.tabObj.element.querySelectorAll('.e-toolbar-item'),
args.draggedItem
);
const dropItem: HTMLElement = <HTMLElement>(
args.target.closest('#draggableTab .e-toolbar-item')
);
if (dropItem != null) {
//args.cancel = true;
let dropItemContainer: Element = args.target.closest('.e-toolbar-items');
const dropIndex: number =
dropItemContainer != null
? Array.prototype.slice
.call(dropItemContainer.querySelectorAll('.e-toolbar-item'))
.indexOf(dropItem)
: '';
console.log(dropIndex);
}
}

Related

ReactJS - Print specific elements in the DOM

I am using ReactJS on an App and currently need to be able to print some elements from the page on user's request (click on a button).
I chose to use the CSS media-query type print (#media print) to be able to check if an element should be printed, based on a selector that could be from a class or attribute on an Element. The strategy would be to hide everything but those "printable" elements with a stylesheet looking like:
#media print {
*:not([data-print]) {
display: none;
}
}
However, for this to work I need to also add the chosen print selector (here the attribute data-print) on every parent element each printable element has.
To do that here's what I've tried so far:
export default function PrintButton() {
useEffect(() => {
const handleBeforePrint = () => {
printNodeSelectors.forEach((selector) => {
const printableElement = document.querySelector(selector);
if (printableElement != null) {
let element = printableElement;
while (element.parentElement) {
element.setAttribute("data-print", "");
element = element.parentElement;
}
element.setAttribute("data-print", "");
}
});
};
const handleAfterPrint = () => {
printNodeSelectors.forEach((selector) => {
const printableElement = document.querySelector(selector);
if (printableElement != null) {
let element = printableElement;
while (element.parentElement) {
element.removeAttribute("data-print");
element = element.parentElement;
}
element.removeAttribute("data-print");
}
});
};
window.addEventListener("beforeprint", handleBeforePrint);
window.addEventListener("afterprint", handleAfterPrint);
return () => {
window.removeEventListener("beforeprint", handleBeforePrint);
window.removeEventListener("afterprint", handleAfterPrint);
};
}, []);
return <button onClick={() => window.print()}>Print</button>;
}
With printNodeSelectors being a const Array of string selectors.
Unfortunately it seems that React ditch out all my dirty DOM modification right after I do them 😭
I'd like to find a way to achieve this without having to manually put everywhere in the app who should be printable, while working on a React App, would someone knows how to do that? 🙏🏼
Just CSS should be enough to hide all Elements which do not have the data-print attribute AND which do not have such Element in their descendants.
Use the :has CSS pseudo-class (in combination with :not one) to express that 2nd condition (selector on descendants):
#media print {
*:not([data-print]):not(:has([data-print])) {
display: none;
}
}
Caution: ancestors of Elements with data-print attribute would not match, hence their text nodes (not wrapped by a tag) would not be hidden when printing:
<div>
<span>should not print</span>
<span data-print>but this should</span>
Caution: text node without tag may be printed...
</div>
Demo: https://jsfiddle.net/6x34ad50/1/ (you can launch the print preview browser feature to see the effect, or rely on the coloring)
Similar but just coloring to directly see the effect:
*:not([data-print]):not(:has([data-print])) {
color: red;
}
<div>
<span>should not print (be colored in red)</span>
<span data-print>but this should</span>
Caution: text node without tag may be printed...
</div>
After some thoughts, tries and errors it appears that even though I managed to put the attribute selector on the parents I completely missed the children of the elements I wanted to print! (React wasn't at all removing the attributes from a mysterious render cycle in the end)
Here's a now functioning Component:
export default function PrintButton() {
useEffect(() => {
const handleBeforePrint = () => {
printNodeSelectors.forEach((selector) => {
const printableElement = document.querySelector(selector);
if (printableElement != null) {
const elements: Element[] = [];
// we need to give all parents and children a data-print attribute for them to be displayed on print
const addParents = (element: Element) => {
if (element.parentElement) {
elements.push(element.parentElement);
addParents(element.parentElement);
}
};
addParents(printableElement);
const addChildrens = (element: Element) => {
elements.push(element);
Array.from(element.children).forEach(addChildrens);
};
addChildrens(printableElement);
elements.forEach((element) => element.setAttribute("data-print", ""));
}
});
};
const handleAfterPrint = () => {
document.querySelectorAll("[data-print]").forEach((element) => element.removeAttribute("data-print"));
};
window.addEventListener("beforeprint", handleBeforePrint);
window.addEventListener("afterprint", handleAfterPrint);
return () => {
window.removeEventListener("beforeprint", handleBeforePrint);
window.removeEventListener("afterprint", handleAfterPrint);
};
}, []);
return <button onClick={() => window.print()}>Print</button>;
}
I usually don't like messing with the DOM while using React but here it allows me to keep everything in the component without having to modify anything else around (though I'd agree that those printNodeSelectors need to be chosen from outside and aren't dynamic at the moment)

svelte-table row select/de-select from Javascript

I am trying to implement row selection functionalty to svelte component I am developing. I am creating component using svelte-table component where I list items from database and the component should allow selection of only two items and remove first row when third is added. Row id's are then recorded in svelte store and passed to an other component. This all works in program level, that is not a problem. The problem is to highlight correct rows in the table so that user is on map which rows are selected. I can get the highlighting working using classNameRowSelected property on svelte-table but the problem is removing the highlighting from the first selected row when that third row is selected. I seem to fail find any example or reference how to do this from Javasctipt...
Here is my SvelteTable element:
<SvelteTable
columns="{COLUMNS}"
rows="{rows}"
rowKey="key"
selectOnClick="{true}"
on:clickRow="{rowSelected}"
classNameTable={['table table-striped']}
classNameThead={['table-primary']}
classNameRowSelected="row-selected"
/>
<style>
:global(.row-selected) {
background-color: #f8c;
}
</style>
and the rowSelected function then row is clicked:
function rowSelected(event)
{
let found = false;
compare_tests.forEach((test,index, compare_tests) => {
console.log(test);
if (test == event.detail.row.key)
{
found = true;
event.detail.row.selected = false;
compare_tests[index] = "";
}
});
if (!found)
{
compare_tests.shift();
compare_tests.push(event.detail.row.key);
event.detail.row.selected = true;
found = false;
}
$testids = compare_tests;
}
Ideally there is some function/property on "row" parameter that I can use to de-select the row based on row.key before I shift it away from the array.
I am relatively new to JavaScript and Svelte so any help where to find information on how to accomplish this would be appreciated.
By using selected (‡ optional array of key values of selected rows) instead of selectOnClick this would be a way >> REPL
<script>
import SvelteTable from "svelte-table";
import {rows, columns} from './data'
let selectedRowIds = []
function handleRowClick(event) {
const rowId = event.detail.row.id
if(selectedRowIds.includes(rowId)) {
selectedRowIds = selectedRowIds.filter(id => id !== rowId)
} else {
selectedRowIds = [rowId, ...selectedRowIds].slice(0,2)
}
}
</script>
<SvelteTable columns="{columns}"
rows="{rows}"
rowKey="id"
on:clickRow={handleRowClick}
selected={selectedRowIds}
classNameRowSelected="row-selected"
>
</SvelteTable>
<style>
:global(.row-selected) {
background-color: #f8c;
}
</style>

Selecting newly added dom elements in javascript

So in vanilla JS I made a class with a function that is suppose to add elements to the document but after those elements are added I also wanted to add a function that will remove selected elements. My problem is that when I try to query select the newly added elements it returns an empty array or node. can anyone give me a reason why and how to fix? Another side not if I got to the developer console I can select the new elements without a problem
class Stock_List {
constructor() {
// fetching the json file and building
this.getData();
}
/**
* this will read the json file, get the data needed
* and then build up the initial list on startup that was
* saved in the json file
*/
async getData() {
const response = await fetch('../stocks.json');
const data = await response.json();
for (const stock of data.stock_info) {
this.add_stock(stock);
}
}
/**
* this will add a stock to the front end
* #param {*} jsObject - A JavaScript Object of stock info
*/
add_stock(jsObject) {
let big_container = document.querySelector('.background');
// this statement fixes the background once a stock is added
big_container.style.position = 'sticky';
let stock_container = document.createElement('div');
stock_container.className = 'stock_container';
stock_container.id = jsObject['stock_ticker'];
// stock header being built
stock_container.innerHTML = `
<div class="stock_header">
<h2 class="stock_ticker">${jsObject['stock_ticker']}</h2>
<h2 class="price">${jsObject['price']}</h2>
<h2 class="percent_change">${jsObject['percent_change']}</h2>
<button>
<div class="line"></div>
</button>
</div>`;
// articles being built
for (let i = 0; i < jsObject['headers'].length; i++) {
stock_container.innerHTML += `
<div class="articles">
<h3>${jsObject['headers'][i]}</h3>
<p>
${jsObject['articles'][i]}
</p>`;
}
//closing off the div of the stock container
stock_container.innerHTML += `
</div>`;
big_container.appendChild(stock_container);
}
/*
removes a stock from the front end
and calls a function to remove it from the back end
*/
remove_stock(ticker) {
let removed_stock = document.querySelector(`#${ticker}`);
console.log(removed_stock);
}
}
You can fix this using event delegation. You need to add an event listener to the parent (in your case, big_container).
big_container.addEventListener('click', function(e) {
// Guard clause
if (!e.target.classList.contains('classname-of-the-ticker')) return;
clickedItem = e.target;
clickedItem.remove();
})

JavaScript classes, how does one apply "Separation of Concerns" and "Don't repeat Yourself" (DRY) in practice

I'm just in the process of learning how JavaScript classes work and I'm just looking for some advice on how to achieve something quite simple I hope regarding animating some elements.
I have created a class named myAnimation, the constructor takes in 1 argument which is an element. All its doing is fading a heading out and in, all very simple. It works fine when there is just one heading element on the page, I'm just not to sure how I go about getting it to work with more than one heading.
Please excuse my naivety with this; it's all very new to me, this is just a basic example I have managed to make myself to try and help myself understand how it works.
class myAnimation {
constructor(element) {
this.element = document.querySelector(element);
}
fadeOut(time) {
if (this.element.classList.contains('fadeout-active')) {
this.element.style.opacity = 1;
this.element.classList.remove('fadeout-active');
button.textContent = 'Hide Heading';
} else {
this.element.style.opacity = 0;
this.element.style.transition = `all ${time}s ease`;
this.element.classList.add('fadeout-active');
button.textContent = 'Show Heading';
}
}
}
const heading = new myAnimation('.heading');
const button = document.querySelector('.button');
button.addEventListener('click', () => {
heading.fadeOut(1);
});
<div class="intro">
<h1 class="heading">Intro Heading</h1>
<p>This is the intro section</p>
<button class="button">Hide Heading</button>
</div>
<div class="main">
<h1 class="heading">Main Heading</h1>
<p>This is the main section</p>
<button class="button">Hide Heading</button>
</div>
After my comment I wanted to make the script run in a way I thought it might have been intended by the OP.
Even though it demonstrates what needs to be done in order to run properly, the entire base design proofs to be not fitting to what the OP really might need to achieve.
The class is called Animation but from the beginning it was intermingling element-animation and changing state of a single somehow globally scoped button.
Even though running now, the design does not proof to be a real fit because one now passes the element that is going to be animated and the button it shall interact with altogether into the constructor.
The functionality is grouped correctly, just the place and the naming doesn't really fit.
The OP might think about a next iteration step of the provided code ...
class Animation {
constructor(elementNode, buttonNode) {
this.element = elementNode;
this.button = buttonNode;
// only in case both elements were passed ...
if (elementNode && buttonNode) {
// couple them by event listening/handling.
buttonNode.addEventListener('click', () => {
// - accessing the `Animation` instance's `this` context
// gets assured by making use of an arrow function.
this.fadeOut(1);
});
}
}
fadeOut(time) {
if (this.element.classList.contains('fadeout-active')) {
this.element.style.opacity = 1;
this.element.classList.remove('fadeout-active');
this.button.textContent = 'Hide Heading';
} else {
this.element.style.opacity = 0;
this.element.style.transition = `all ${time}s ease`;
this.element.classList.add('fadeout-active');
this.button.textContent = 'Show Heading';
}
}
}
function initializeAnimations() {
// get list of all elements that have a `heading` class name.
const headingList = document.querySelectorAll('.heading');
// for each heading element do ...
headingList.forEach(function (headingNode) {
// ... access its parent element and query again for a single button.
const buttonNode = headingNode.parentElement.querySelector('.button');
// if the related button element exists ...
if (buttonNode) {
// ... create a new `Animation` instance.
new Animation(headingNode, buttonNode);
}
});
}
initializeAnimations();
.as-console-wrapper { max-height: 100%!important; top: 0; }
<div class="intro">
<h1 class="heading">Intro Heading</h1>
<p>This is the intro section</p>
<button class="button">Hide Heading</button>
</div>
<div class="main">
<h1 class="heading">Main Heading</h1>
<p>This is the main section</p>
<button class="button">Hide Heading</button>
</div>
... new day, next possible iteration step ...
The 2nd iteration separates concerns.
It does so by renaming the class and implementing only class specific behavior. Thus a FadeToggle class provides just toggle specific functionality.
The code then gets split into two functions that handle initialization. For better reuse the initializing code and the html structure need to be refactored into something more generic. The data attribute of each container that features a trigger-element for fading a target element will be used as a configuration storage that provides all necessary information for the initializing process. (One even can provide individual transition duration values.)
Last there is a handler function that is implemented in a way that it can be reused by bind in order to generate a closure which provides all the necessary data for each trigger-target couple.
class FadeToggle {
// a clean fade-toggle implementation.
constructor(elementNode, duration) {
duration = parseFloat(duration, 10);
duration = Number.isFinite(duration) ? duration : 1;
elementNode.style.opacity = 1;
elementNode.style.transition = `all ${ duration }s ease`;
this.element = elementNode;
}
isFadeoutActive() {
return this.element.classList.contains('fadeout-active');
}
toggleFade(duration) {
duration = parseFloat(duration, 10);
if (Number.isFinite(duration)) {
this.element.style.transitionDuration = `${ duration }s`;
}
if (this.isFadeoutActive()) {
this.element.style.opacity = 1;
this.element.classList.remove('fadeout-active');
} else {
this.element.style.opacity = 0;
this.element.classList.add('fadeout-active');
}
}
}
function handleFadeToggleWithBoundContext(/* evt */) {
const { trigger, target } = this;
if (target.isFadeoutActive()) {
trigger.textContent = 'Hide Heading';
} else {
trigger.textContent = 'Show Heading';
}
target.toggleFade();
}
function initializeFadeToggle(elmNode) {
// parse an element node's fade-toggle configuration.
const config = JSON.parse(elmNode.dataset.fadeToggleConfig || null);
const selectors = (config && config.selectors);
if (selectors) {
try {
// query both the triggering and the target element
const trigger = elmNode.querySelector(selectors.trigger || null);
let target = elmNode.querySelector(selectors.target || null);
if (trigger && target) {
// create a `FadeToggle` target type.
target = new FadeToggle(target, config.duration);
// couple trigger and target by event listening/handling ...
trigger.addEventListener(
'click',
handleFadeToggleWithBoundContext.bind({
// ... and binding both as context properties to the handler.
trigger,
target
})
);
}
} catch (exception) {
console.warn(exception.message, exception);
}
}
}
function initializeEveryFadeToggle() {
// get list of all elements that contain a fade-toggle configuration
const configContainerList = document.querySelectorAll('[data-fade-toggle-config]');
// do initialization for each container separately.
configContainerList.forEach(initializeFadeToggle);
}
initializeEveryFadeToggle();
.as-console-wrapper { max-height: 100%!important; top: 0; }
<div class="intro" data-fade-toggle-config='{"selectors":{"trigger":".button","target":".heading"},"duration":3}'>
<h1 class="heading">Intro Heading</h1>
<p>This is the intro section</p>
<button class="button">Hide Heading</button>
</div>
<div class="main" data-fade-toggle-config='{"selectors":{"trigger":".button","target":".heading"}}'>
<h1 class="heading">Main Heading</h1>
<p>This is the main section</p>
<button class="button">Hide Heading</button>
</div>
... afternoon, improve the handling of state changes ...
There is still hard wired data, written directly into the code. In order to get rid of string-values that will be (re)rendered every time a toggle-change takes place one might give the data-based configuration-approach another chance.
This time each triggering element might feature a configuration that provides state depended values. Thus the initialization process needs to take care of retrieving this data and also of rendering it according to the initial state of a fade-toggle target.
This goal directly brings up the necessity of a render function for a trigger element because one needs to change a trigger's state not only initially but also with every fade-toggle.
And this again will change the handler function in a way that in addition it features bound state values too in order to delegate such data to the render process ...
class FadeToggle {
// a clean fade-toggle implementation.
constructor(elementNode, duration) {
duration = parseFloat(duration, 10);
duration = Number.isFinite(duration) ? duration : 1;
elementNode.style.opacity = 1;
elementNode.style.transition = `all ${ duration }s ease`;
this.element = elementNode;
}
isFadeoutActive() {
return this.element.classList.contains('fadeout-active');
}
toggleFade(duration) {
duration = parseFloat(duration, 10);
if (Number.isFinite(duration)) {
this.element.style.transitionDuration = `${ duration }s`;
}
if (this.isFadeoutActive()) {
this.element.style.opacity = 1;
this.element.classList.remove('fadeout-active');
} else {
this.element.style.opacity = 0;
this.element.classList.add('fadeout-active');
}
}
}
function renderTargetStateDependedTriggerText(target, trigger, fadeinText, fadeoutText) {
if ((fadeinText !== null) && (fadeoutText !== null)) {
if (target.isFadeoutActive()) {
trigger.textContent = fadeinText;
} else {
trigger.textContent = fadeoutText;
}
}
}
function handleFadeToggleWithBoundContext(/* evt */) {
// retrieve context data.
const { target, trigger, fadeinText, fadeoutText } = this;
target.toggleFade();
renderTargetStateDependedTriggerText(
target,
trigger,
fadeinText,
fadeoutText
);
}
function initializeFadeToggle(elmNode) {
// parse an element node's fade-toggle configuration.
let config = JSON.parse(elmNode.dataset.fadeToggleConfig || null);
const selectors = (config && config.selectors);
if (selectors) {
try {
// query both the triggering and the target element
const trigger = elmNode.querySelector(selectors.trigger || null);
let target = elmNode.querySelector(selectors.target || null);
if (trigger && target) {
// create a `FadeToggle` target type.
target = new FadeToggle(target, config.duration);
// parse a trigger node's fade-toggle configuration and state.
const triggerStates = ((
JSON.parse(trigger.dataset.fadeToggleTriggerConfig || null)
|| {}
).states || {});
// get a trigger node's state change values.
const fadeinStateValues = (triggerStates.fadein || {});
const fadeoutStateValues = (triggerStates.fadeout || {});
// get a trigger node's state change text contents.
const fadeinText = fadeinStateValues.textContent || null;
const fadeoutText = fadeoutStateValues.textContent || null;
// rerender trigger node's initial text value.
renderTargetStateDependedTriggerText(
target,
trigger,
fadeinText,
fadeoutText
);
// couple trigger and target by event listening/handling ...
trigger.addEventListener(
'click',
handleFadeToggleWithBoundContext.bind({
// ... and by binding both and some text values
// that are sensitive to state changes
// as context properties to the handler.
target,
trigger,
fadeinText,
fadeoutText
})
);
}
} catch (exception) {
console.warn(exception.message, exception);
}
}
}
function initializeEveryFadeToggle() {
// get list of all elements that contain a fade-toggle configuration
const configContainerList = document.querySelectorAll('[data-fade-toggle-config]');
// do initialization for each container separately.
configContainerList.forEach(initializeFadeToggle);
}
initializeEveryFadeToggle();
.as-console-wrapper { max-height: 100%!important; top: 0; }
<div class="intro" data-fade-toggle-config='{"selectors":{"trigger":".button","target":".heading"},"duration":3}'>
<h1 class="heading">Intro Heading</h1>
<p>This is the intro section</p>
<button class="button" data-fade-toggle-trigger-config='{"states":{"fadeout":{"textContent":"Hide Heading"},"fadein":{"textContent":"Show Heading"}}}'>Toggle Heading</button>
</div>
<div class="main" data-fade-toggle-config='{"selectors":{"trigger":".button","target":".heading"}}'>
<h1 class="heading">Main Heading</h1>
<p>This is the main section</p>
<button class="button">Toggle Heading</button>
</div>
This is happening because document.querySelector(".button") only returns the first element with class .button (reference).
You might want to try document.querySelectorAll(".button") (reference) to add your event listeners.
(Though this will only toggle your first heading - for the very same reason. ;))

Automatically scroll down to next listgroup item and display the content in center in mobile view (React)

1) I am trying to Auto scroll to the next item in listgroup. For example if user answer the first question it should auto scroll to the second question. (React) and onSubmit it should scroll to the first not answered question
2) When user view this list in mobile view the YES or NO Radio button should display in center and also SUBMIT AND CLEAR BUTTON (BOOTSTRAP)
3) How to know which item is selected from the drop down and display it in console.
Code
There are a number of ways this can be achieved. One way would be to add a method that scrolls to an item in your form, via "vanilla js", and then use that in both your onInputChanged on onSubmut methods.
You could defined this function in your component as:
// Scrolls the list to a list item by list item index
scrollToItemByIndex = (index) => {
// Find the list item element (by index), and scroll wrapper element
const scrollItem = document.querySelector(`[scrollIndex="${ (index) }"]`)
const scrollWrapper = document.querySelector(`[scrollWrapper="scrollWrapper"]`)
if(scrollItem && scrollWrapper) {
// If list item found in DOM, get the top offset
const itemRect = scrollItem.offsetTop // [UPDATED]
const wrapperRect = scrollWrapper.offsetTop // [UPDATED]
// Scroll the wrapper to the offset of the list item we're scrolling to
scrollWrapper.scrollTo(0, itemRect - wrapperRect)
}
}
You onInputChange function could then be updated as follows:
onInputChange = ({ target }) => {
const { cards } = this.state;
const { options } = this.state;
const nexState = cards.map((card, index) => {
if (card.cardName !== target.name) return card;
const options = card.options.map(opt => {
const checked = opt.radioName === target.value;
return {
...opt,
selected: checked
}
})
// [ADD] When input changes (ie something is set), scroll to next item
this.scrollToItemByIndex( index + 1 )
const style = options.every(option => !option.selected) ? 'danger' : 'info'
return {
...card,
style,
options
}
});
this.setState({ cards: nexState })
}
Also, your onSubmit would be updated to scroll to any form items that are not valid:
onSubmit = () => {
this.state.cards.forEach((card, index) => {
var invalid = card.options.every(option => !option.selected)
if (invalid) {
card.style = 'danger'
// [ADD] this item has invalid input, so scroll to it
this.scrollToItemByIndex(index)
}
else {
card.style = 'info'
}
});
...
}
Finally, you'd need to update your component's render method with the following, to ensure that the query selectors above function correctly:
<ul class="nav nav-pills nav-stacked anyClass" scrollWrapper="scrollWrapper">
and:
{cards.map((card, idx) => (<ListGroup bsStyle="custom" scrollIndex={idx}>
...
</ ListGroup >)}
[UPDATED] A full working sample can be found here:
https://stackblitz.com/edit/react-z7nhgd?file=index.js
Hope this helps!

Categories