Validate only parts of a form - forms

I have a rather large form, that I've broken up into individual sections with "Next" and "Back" buttons for the user to navigate each step.
Here is how I set it up to work for now:
Each section is within its own xp:panel
Each xp:panel is hidden with "display:none". The transition happens when the user clicks on "Next" or "Back" by using JQuery's fade animation
What I am trying to do is, if the user clicks on "Next" I would like to validate only the fields in the current, visible section. If the validation fails, don't transition to the next step. If the validation passes, transition to the next step.
Right now, when I click on the "Next" button, every field is being validated and the transition doesn't happen.
Is there a way, that I can only validate the fields in a certain section or would I have to use something like Don Mottolo's code snippet: http://openntf.org/XSnippets.nsf/snippet.xsp?id=ssjs-form-validation-that-triggers-errormessage-controls
Thank you for your help!
P.S.: I know I could use the CSJS portion of the onClick event of the button to run some validation, but I'd like to use the "Display Error" controls.

You could look at computing the required attribute of each control that is to be validated and have that check which button is submitting the form. Tommy Vaaland has a function that does this:
// Used to check which if a component triggered an update
function submittedBy( componentId ){
try {
var eventHandlerClientId = param.get( '$$xspsubmitid' );
var eventHandlerId = #RightBack( eventHandlerClientId, ':' );
var eventHandler = getComponent( eventHandlerId );
if( !eventHandler ){ return false; }
var parentComponent = eventHandler.getParent();
if( !parentComponent ){ return false; }
return ( parentComponent.getId() === componentId );
} catch( e ){ /*Debug.logException( e );*/ }
}
Link: http://dontpanic82.blogspot.co.uk/2010/03/xpages-making-validation-behave.html

You can look at using a client side form validation framework such as Parsley: http://parsleyjs.org
This can of course be combined with server side validation for at least the final submission.

Related

How to access control from the popup fragment by ID

I want my text area to be empty after I press OK button.
I have try this line this.byId("id").setValue("")
onWorkInProgress: function (oEvent) {
if (!this._oOnWorkInProgressDialog) {
this._oOnWorkInProgressDialog = sap.ui.xmlfragment("WIPworklist", "com.sap.FinalAssestments.view.WorkInProgress", this);
//this.byId("WIP").value = "";
//this.byId("WIP").setValue();
this.getView().addDependent(this._oOnWorkInProgressDialog);
}
var bindingPath = oEvent.getSource().getBindingContext().getPath();
this._oOnWorkInProgressDialog.bindElement(bindingPath);
this._oOnWorkInProgressDialog.open();
},
//function when cancel button inside the fragments is triggered
onCancelApproval: function() {
this._oOnWorkInProgressDialog.close();
},
//function when approval button inside the fragments is triggered
onWIPApproval: function() {
this._oOnWorkInProgressDialog.close();
var message = this.getView().getModel("i18n").getResourceBundle().getText("wipSuccess");
MessageToast.show(message);
},
The text area will be in popup in the fragment. I am expecting the text area to be empty.
If you instantiate your fragment like this:
sap.ui.xmlfragment("WIPworklist", "com.sap.FinalAssestments.view.WorkInProgress", this);
You can access its controls like this:
Fragment.byId("WIPworklist", "WIP").setValue(""); // Fragment required from "sap/ui/core/Fragment"
Source: How to Access Elements from XML Fragment by ID
The better approach would be to use a view model. The model should have a property textAreaValue or something like that.
Then bind that property to your TextArea (<TextArea value="{view>/textAreaValue}" />). If you change the value using code (e.g. this.getView().getModel("view").setProperty("/textAreaValue", "")), it will automatically show the new value in your popup.
And it works both ways: if a user changes the text, it will be automatically updated in the view model, so you can access the new value using this.getView().getModel("view").getProperty("/textAreaValue");.
You almost have it, I think. Just put the
this.byId("WIP").setValue("") line after the if() block. Since you are adding the fragment as a dependent of your view, this.byId("WIP") will find the control with id "WIP" every time you open the WIP fragment and set its value to blank.
You are likely not achieving it now because A. it is not yet a dependent of your view and B. it is only getting fired on the first go-around.

How to keep focus within modal dialog?

I'm developing an app with Angular and Semantic-UI. The app should be accessible, this means it should be compliant with WCAG 2.0.
To reach this purpose the modals should keep focus within the dialog and prevents users from going outside or move with "tabs" between elements of the page that lays under the modal.
I have found some working examples, like the following:
JQuery dialog: https://jqueryui.com/dialog/#modal-confirmation
dialog HTML 5.1 element: https://demo.agektmr.com/dialog
ARIA modal dialog example:
http://w3c.github.io/aria-practices/examples/dialog-modal/dialog.html
(that I have reproduced on Plunker)
Here is my try to create an accessible modal with Semantic-UI: https://plnkr.co/edit/HjhkZg
As you can see I used the following attributes:
role="dialog"
aria-labelledby="modal-title"
aria-modal="true"
But they don't solve my issue. Do you know any way to make my modal keeping focus and lose it only when user click on cancel/confirm buttons?
There is currently no easy way to achieve this. The inert attribute was proposed to try to solve this problem by making any element with the attribute and all of it's children inaccessible. However, adoption has been slow and only recently did it land in Chrome Canary behind a flag.
Another proposed solution is making a native API that would keep track of the modal stack, essentially making everything not currently the top of the stack inert. I'm not sure the status of the proposal, but it doesn't look like it will be implemented any time soon.
So where does that leave us?
Unfortunately without a good solution. One solution that is popular is to create a query selector of all known focusable elements and then trap focus to the modal by adding a keydown event to the last and first elements in the modal. However, with the rise of web components and shadow DOM, this solution can no longer find all focusable elements.
If you always control all the elements within the dialog (and you're not creating a generic dialog library), then probably the easiest way to go is to add an event listener for keydown on the first and last focusable elements, check if tab or shift tab was used, and then focus the first or last element to trap focus.
If you're creating a generic dialog library, the only thing I have found that works reasonably well is to either use the inert polyfill or make everything outside of the modal have a tabindex=-1.
var nonModalNodes;
function openDialog() {
var modalNodes = Array.from( document.querySelectorAll('dialog *') );
// by only finding elements that do not have tabindex="-1" we ensure we don't
// corrupt the previous state of the element if a modal was already open
nonModalNodes = document.querySelectorAll('body *:not(dialog):not([tabindex="-1"])');
for (var i = 0; i < nonModalNodes.length; i++) {
var node = nonModalNodes[i];
if (!modalNodes.includes(node)) {
// save the previous tabindex state so we can restore it on close
node._prevTabindex = node.getAttribute('tabindex');
node.setAttribute('tabindex', -1);
// tabindex=-1 does not prevent the mouse from focusing the node (which
// would show a focus outline around the element). prevent this by disabling
// outline styles while the modal is open
// #see https://www.sitepoint.com/when-do-elements-take-the-focus/
node.style.outline = 'none';
}
}
}
function closeDialog() {
// close the modal and restore tabindex
if (this.type === 'modal') {
document.body.style.overflow = null;
// restore or remove tabindex from nodes
for (var i = 0; i < nonModalNodes.length; i++) {
var node = nonModalNodes[i];
if (node._prevTabindex) {
node.setAttribute('tabindex', node._prevTabindex);
node._prevTabindex = null;
}
else {
node.removeAttribute('tabindex');
}
node.style.outline = null;
}
}
}
The different "working examples" do not work as expected with a screenreader.
They do not trap the screenreader visual focus inside the modal.
For this to work, you have to :
Set the aria-hidden attribute on any other nodes
disable keyboard focusable elements inside those trees (links using tabindex=-1, controls using disabled, ...)
The jQuery :focusable pseudo selector can be useful to find focusable elements.
add a transparent layer over the page to disable mouse selection.
or you can use the css pointer-events: none property when the browser handles it with non SVG elements, not in IE
This focus-trap plugin is excellent at making sure that focus stays trapped inside of dialogue elements.
It sounds like your problem can be broken down into 2 categories:
focus on dialog box
Add a tabindex of -1 to the main container which is the DOM element that has role="dialog". Set the focus to the container.
wrapping the tab key
I found no other way of doing this except by getting the tabbable elements within the dialog box and listening it on keydown. When I know the element in focus (document.activeElement) is the last one on the list, I make it wrap
"focus" events can be intercepted in the capture phase, so you can listen for them at the document.body level, squelch them before they reach the target element, and redirect focus back to a control in your modal dialog. This example assumes a modal dialog with an input element gets displayed and assigned to the variable currDialog:
document.body.addEventListener("focus", (event) => {
if (currDialog && !currDialog.contains(event.target)) {
event.preventDefault();
event.stopPropagation();
currDialog.querySelector("input").focus();
}
}, {capture: true});
You may also want to contain such a dialog in a fixed-position, clear (or low-opacity) backdrop element that takes up the full screen in order to capture and suppress mouse/pointer events, so that no browser feedback (hover, etc.) occurs that could give the user the impression that the background is active.
Don't use any solution requiring you to look up "tabbable" elements. Instead, use keydown and either click events or a backdrop in an effective manor.
(Angular1)
See Asheesh Kumar's answer at https://stackoverflow.com/a/31292097/1754995 for something similar to what I am going for below.
(Angular2-x, I haven't done Angular1 in a while)
Say you have 3 components: BackdropComponent, ModalComponent (has an input), and AppComponent (has an input, the BackdropComponent, and the ModalComponent). You display BackdropComponent and ModalComponent with the correct z-index, both are currently displayed/visible.
What you need to do is have a general window.keydown event with preventDefault() to stop all tabbing when the backdrop/modal component is displayed. I recommend you put that on a BackdropComponent. Then you need a keydown.tab event with stopPropagation() to handle tabbing for the ModalComponent. Both the window.keydown and keydown.tab could probably be in the ModalComponent but there is purpose in a BackdropComponent further than just modals.
This should prevent clicking and tabbing to the AppComponent input and only click or tab to the ModalComponent input [and browser stuffs] when the modal is shown.
If you don't want to use a backdrop to prevent clicking, you can use use click events similarly to the keydown events described above.
Backdrop Component:
#Component({
selector: 'my-backdrop',
host: {
'tabindex': '-1',
'(window:keydown)': 'preventTabbing($event)'
},
...
})
export class BackdropComponent {
...
private preventTabbing(event: KeyboardEvent) {
if (event.keyCode === 9) { // && backdrop shown?
event.preventDefault();
}
}
...
}
Modal Component:
#Component({
selector: 'my-modal',
host: {
'tabindex': '-1',
'(keydown.tab)': 'onTab($event)'
},
...
})
export class ModalComponent {
...
private onTab(event: KeyboardEvent) {
event.stopPropagation();
}
...
}
Here's my solution. It traps Tab or Shift+Tab as necessary on first/last element of modal dialog (in my case found with role="dialog"). Eligible elements being checked are all visible input controls whose HTML may be input,select,textarea,button.
$(document).on('keydown', function(e) {
var target = e.target;
var shiftPressed = e.shiftKey;
// If TAB key pressed
if (e.keyCode == 9) {
// If inside a Modal dialog (determined by attribute role="dialog")
if ($(target).parents('[role=dialog]').length) {
// Find first or last input element in the dialog parent (depending on whether Shift was pressed).
// Input elements must be visible, and can be Input/Select/Button/Textarea.
var borderElem = shiftPressed ?
$(target).closest('[role=dialog]').find('input:visible,select:visible,button:visible,textarea:visible').first()
:
$(target).closest('[role=dialog]').find('input:visible,select:visible,button:visible,textarea:visible').last();
if ($(borderElem).length) {
if ($(target).is($(borderElem))) {
return false;
} else {
return true;
}
}
}
}
return true;
});
we can use the focus trap npm package.
npm i focus-trap
This might help someone who is looking for solution in Angular.
Step 1: Add keydown event on dialog component
#HostListener('document:keydown', ['$event'])
handleTabKeyWInModel(event: any) {
this.sharedService.handleTabKeyWInModel(event, '#modal_id', this.elementRef.nativeElement, 'input,button,select,textarea,a,[tabindex]:not([tabindex="-1"])');
}
This will filters the elements which are preseneted in the Modal dialog.
Step 2: Add common method to handle focus in shared service (or you can add it in your component as well)
handleTabKeyWInModel(e, modelId: string, nativeElement, tagsList: string) {
if (e.keyCode === 9) {
const focusable = nativeElement.querySelector(modelId).querySelectorAll(tagsList);
if (focusable.length) {
const first = focusable[0];
const last = focusable[focusable.length - 1];
const shift = e.shiftKey;
if (shift) {
if (e.target === first) { // shift-tab pressed on first input in dialog
last.focus();
e.preventDefault();
}
} else {
if (e.target === last) { // tab pressed on last input in dialog
first.focus();
e.preventDefault();
}
}
}
}
}
Now this method will take the modal dialog native element and start evaluate on every tab key. Finally we will filter the event on first and last so that we can focus on appropriate elements (on first after last element tab click and on last shift+tab event on first element).
Happy Coding.. :)
I used one of the methods suggested by Steven Lambert, namely, listening to keydown events and intercepting "tab" and "shift+tab" keys. Here's my sample code (Angular 5):
import { Directive, ElementRef, Attribute, HostListener, OnInit } from '#angular/core';
/**
* This directive allows to override default tab order for page controls.
* Particularly useful for working around the modal dialog TAB issue
* (when tab key allows to move focus outside of dialog).
*
* Usage: add "custom-taborder" and "tab-next='next_control'"/"tab-prev='prev_control'" attributes
* to the first and last controls of the dialog.
*
* For example, the first control is <input type="text" name="ctlName">
* and the last one is <button type="submit" name="btnOk">
*
* You should modify the above declarations as follows:
* <input type="text" name="ctlName" custom-taborder tab-prev="btnOk">
* <button type="submit" name="btnOk" custom-taborder tab-next="ctlName">
*/
#Directive({
selector: '[custom-taborder]'
})
export class CustomTabOrderDirective {
private elem: HTMLInputElement;
private nextElemName: string;
private prevElemName: string;
private nextElem: HTMLElement;
private prevElem: HTMLElement;
constructor(
private elemRef: ElementRef
, #Attribute('tab-next') public tabNext: string
, #Attribute('tab-prev') public tabPrev: string
) {
this.elem = this.elemRef.nativeElement;
this.nextElemName = tabNext;
this.prevElemName = tabPrev;
}
ngOnInit() {
if (this.nextElemName) {
var elems = document.getElementsByName(this.nextElemName);
if (elems && elems.length && elems.length > 0)
this.nextElem = elems[0];
}
if (this.prevElemName) {
var elems = document.getElementsByName(this.prevElemName);
if (elems && elems.length && elems.length > 0)
this.prevElem = elems[0];
}
}
#HostListener('keydown', ['$event'])
onKeyDown(event: KeyboardEvent) {
if (event.key !== "Tab")
return;
if (!event.shiftKey && this.nextElem) {
this.nextElem.focus();
event.preventDefault();
}
if (event.shiftKey && this.prevElem) {
this.prevElem.focus();
event.preventDefault();
}
}
}
To use this directive, just import it to your module and add to Declarations section.
I've been successful using Angular Material's A11yModule.
Using your favorite package manager install these to packages into your Angular app.
**"#angular/material": "^10.1.2"**
**"#angular/cdk": "^10.1.2"**
In your Angular module where you import the Angular Material modules add this:
**import {A11yModule} from '#angular/cdk/a11y';**
In your component HTML apply the cdkTrapFocus directive to any parent element, example: div, form, etc.
Run the app, tabbing will now be contained within the decorated parent element.
For jquery users:
Assign role="dialog" to your modal
Find first and last interactive element inside the dialog modal.
Check if current target is one of them(depending on shift key is
pressed or not).
If target element is one of first or last interactive element of the
dialog, return false
Working code sample:
//on keydown inside dialog
$('.modal[role=dialog]').on('keydown', e => {
let target = e.target;
let shiftPressed = e.shiftKey;
// If TAB is pressed
if (e.keyCode === 9) {
// Find first and last element in the ,modal-dialog parent.
// Elements must be interactive i.e. visible, and can be Input/Select/Button/Textarea.
let first = $(target).closest('[role=dialog]').find('input:visible,select:visible,button:visible,textarea:visible').first();
let last = $(target).closest('[role=dialog]').find('input:visible,select:visible,button:visible,textarea:visible').last();
let borderElem = shiftPressed ? first : last //border element on the basis of shift key pressed
if ($(borderElem).length) {
return !$(target).is($(borderElem)); //if target is border element , return false
}
}
return true;
});
I read through most of the answers, while the package focus-trap seems like a good option. #BenVida shared a very simple VanillaJS solution here in another Stack Overflow post.
Here is the code:
const container=document.querySelector("_selector_for_the_container_")
//optional: needed only if the container element is not focusable already
container.setAttribute("tabindex","0")
container.addEventListener("focusout", (ev)=>{
if (!container.contains(ev.relatedTarget)) container.focus()
})

Where do i change the label on clicking a button QT

i made a GUI for a project, it has multiple forms and i don't know how to connect them.....the thing is that the navigation between these forms is not linear.
The user's choice on the first page (radiobuttons) decides what the next form will be, i developed a basic algorithm using if statements, but i don't know the navigation action.
here's the if statement
void InputChoice::on_commandLinkButton_clicked()
{
if (ui->radioButton->isChecked()){
//Go to state vector input;
}
if (ui->radioButton_2->isChecked()){
//Go to orbital element input
}
if (ui->radioButton_3->isChecked()){
//Go to TLE input
}
So i want to know
1- the syntax and/or the principal that we use to link the forms together.
2- How can i put it in a conditional statement.
3- i read in some place that it would be a good option to use the command link button
Thanks alot :))
void InputChoice::on_commandLinkButton_clicked() {
if (ui->radioButton->isChecked()){
//Go to state vector input;
vectorInput = new VectorInput(); // show the form
vectorInput->exec(); // Don't let anything else happen until choice is made on form
//or you could also just use show(); but the user can do other things while form is showing
vectorInput = new VectorInput();// show the form
vectorInput->show();
}
else if (ui->radioButton_2->isChecked()){
//Go to orbital element input
//Same as above
}
else if (ui->radioButton_3->isChecked()){
//Go to TLE input`enter code here`
//same as above
}
else{
//Do something.
}
//Should be something like this. If not, can you elaborate a little more or where can I see the code?

Symfony 2 Forms - add new item to Collection server-side, depending on button clicked

I have a form which contains a Collection of an unspecified number of subforms. I want to have functionality allowing the user to add a new, blank item to the Collection for them to fill in. The Symfony docs tell us how to do this using Javascript clientside to add new blank form controls, which are then submitted and persisted as normal, but I'd like to do it serverside in the controller, without Javascript.
The problem I'm encountering is to do with the way Symfony Forms work. I have an "Add" button added to my main form, and I intend to detect whether it is that button which has been clicked, so that I can add the blank item to the Collection and re-render the form. But to detect the click I need to call $this->createForm and at that point the form is fixed with the original set of items, it's too late to add an extra one.
//Symfony Action
//A Person has many Selections
$person = $this->getPerson($id)
//All fields are frozen at this point, according to data in $person!
$form = $this->createForm(new SelectionsType($lookups), $person);
$form->handleRequest($request);
//Ideally I'd somehow do this test earlier, but I need $form to do it...
if ($form->get('add')->isClicked() )
{
//TOO LATE!
$person->getSelections()->add(new Selection() );
}
if ($form->isValid())
{
if ($form->get('save')->isClicked() )
{
//Persist
}
}
//Render page etc
Things I've thought about:
Putting the Add button in a completely different form on the same page, which submits to a different Action which can then do some preparatory work before forwarding to the main Action above
Inspecting submitted HTTP data directly to note that Add has been clicked (shame not to use the standard Symfony method)
Give up and use Javascript as suggested (it might work in this example, but I'd like to have the option of carrying out server-side activity (without AJAX...) as part of adding the new blank item)
How can I best achieve this in a proper Symfony way?
EDIT Just seen this: https://github.com/symfony/symfony/issues/5231, which is essentially a feature request to allow what I'm after. One suggestion a commenter makes is to add a blank item to the Collection and then remove it if it's not needed - I don't know how one would do that, but it sounds promising.
ANOTHER EDIT It occurs to me that, because I need two different aspects of the $form I'm creating, I could maybe just make the $form, use it to handle the request, detect the button click, and then throw that $form away, before altering my model and creating another $form. I don't know if that would somehow fall foul of some rules about handling the submission twice.
I'm not 100% but I think you could do the following...
//Symfony Action with (Request $request, ...)
//A Person has many Selections
$person = $this->getPerson($id)
//All fields are frozen at this point, according to data in $person!
$form = $this->createForm(new SelectionsType($lookups), $person);
if ($request->isMethod('POST')) {
$form->submit($request);
if ($form->get('add')->isClicked()) {
// Add thing
} elseif ($form->isValid()) {
// or
// } elseif ($form->get('save')->isClicked() && $form->isValid()) {
// Persist and what not
}
}
//Render page etc
I haven't tested it so I don't know whether it will trigger the form errors (or if it will actually work) so if it does (or it doesn't) I apologise.
What I did in the end was have my Add button hit a separate Action, which then delegates to the main action with a flag to say "add a new Selection", as below:
public function selectionsAddAction(Request $request, $id)
{
return $this->selectionsAction($request, $id, true);
}
public function selectionsAction(Request $request, $id, $addNew = false)
{
$person = $this->getPerson($id);
//Also use "add mode" if we just deleted the last one!
if (!$person->getSelections()->count())
{
$addNew = true;
}
//$addNew is set by a separate action, hit by a different form with the Add button in
if ($addNew)
{
$person->getSelections()->add(new Selection() );
}
//We now have the right number of items, and can build the form!
$form = $this->createForm(new SelectionsType($lookups), $person);
//...
}

Ajax Auto Suggest v.2 suggestion depends on radio button?

I am using auto suggest v.2.1.3 from brandspankingnew.
I have a form with two radio button and a text field and would like to know how to make the auto suggest script pointing to a different php file if one of the radio button is checked.
I tried this but it doesnt work, its always point to the same php file even if second button is checked
Could you please assist?
Many thanks in advance.
My code is as follows:
function targetvalue()
{
for (i=0;i
/>Business Street
var options = {
script:"autosuggest.php?json=true&limit=6&",
varname:"input",
json:true,
shownoresults:false,
maxresults:10,
callback: function (obj) { document.getElementById('name').value = obj.id; }
};
var as_json = new bsn.AutoSuggest('business', options);
var options_xml = {
script: function (input) { return "autosuggest.php?input="+input+"&testid="+document.getElementById('testid').value; },
varname:"input"
};
var as_xml = new bsn.AutoSuggest('business', options_xml);
As for me, the easiest solution is to pass the the button state to the one script eg only one script but can return different results depending on button state. Otherwise you need to rewrite options each time someone clicks on the radio button. The second solution an lead to unpredictable behavior of auto suggest component.
Sample script:
var selectedValue = getRadioSelectedValue("radioGroupName");
var options_xml = { script: function (input) { return "autosuggest.php?input="+input+"&testid="+document.getElementById('testid').value+"&mode="+selectedValue; },
Write getRadioSelectedValue by yourself to get selected radio button value or set some flag on click. Mode param in GET request will indicates the state of the button, so you can return proper response.