Tightened up how we manage input focus.

master
Pacman Ghost 4 years ago
parent 34b23bc867
commit bf8549f861
  1. 36
      web/src/App.js
  2. 14
      web/src/ArticleSearchResult2.js
  3. 12
      web/src/PublicationSearchResult2.js
  4. 4
      web/src/PublisherSearchResult2.js
  5. 6
      web/src/SearchForm.js
  6. 20
      web/src/utils.js

@ -44,6 +44,7 @@ export default class App extends React.Component
// initialize
this._searchFormRef = React.createRef() ;
this._modalFormRef = React.createRef() ;
this._setFocusTo = null ;
// figure out the base URL of the Flask backend server
// NOTE: We allow the caller to do this since the test suite will usually spin up
@ -137,6 +138,17 @@ export default class App extends React.Component
window.addEventListener( "keydown", this.onKeyDown.bind( this ) ) ;
}
componentDidUpdate() {
// we've finished rendering the page, check if we should set focus
if ( this._setFocusTo ) {
if ( this._setFocusTo.current )
this._setFocusTo.current.focus() ;
else
this._setFocusTo.focus() ;
}
this._setFocusTo = null ;
}
componentWillUnmount() {
// clean up
window.removeEventListener( "keydown", this.onKeyDown ) ;
@ -145,9 +157,9 @@ export default class App extends React.Component
onSearch( query ) {
// run the search
query = query.trim() ;
const queryStringRef = this._searchFormRef.current.queryStringRef.current ;
if ( query.length === 0 ) {
this.focusQueryString() ;
this.showErrorMsg( "Please enter something to search for." )
this.showErrorMsg( "Please enter something to search for.", queryStringRef )
return ;
}
axios.post( this.makeFlaskUrl( "/search" ), {
@ -155,8 +167,8 @@ export default class App extends React.Component
no_hilite: this._disableSearchResultHighlighting,
} )
.then( resp => {
this._setFocusTo = queryStringRef ;
this.setState( { searchResults: resp.data, searchSeqNo: this.state.searchSeqNo+1 } ) ;
this.focusQueryString() ;
} )
.catch( err => {
this.showErrorResponse( "The search query failed", err ) ;
@ -198,8 +210,8 @@ export default class App extends React.Component
}
closeModalForm() {
this._setFocusTo = this._searchFormRef.current.queryStringRef ;
this.setState( { modalForm: null } ) ;
setTimeout( () => { this.focusQueryString() ; }, 100 ) ;
}
showInfoToast( msg ) { this._doShowToast( "info", msg, 5*1000 ) ; }
@ -229,17 +241,19 @@ export default class App extends React.Component
else
content = <div className="monospace"> {err.response.data} </div> ;
}
const msg = err.response ? err.response.statusText : err ;
const buttons = { Close: () => this.closeModalForm() } ;
this.showModalForm( "error-response", err.response.statusText, "red",
this.showModalForm( "error-response", msg, "red",
<div> {caption}: {content} </div>,
buttons
) ;
}
showErrorMsg( content ) {
showErrorMsg( content, setFocusTo ) {
// show the error message in a modal dialog
this.ask( content, "error",
{ "OK": null }
{ "OK": null },
setFocusTo
) ;
}
@ -247,7 +261,7 @@ export default class App extends React.Component
this.showWarningToast( makeSmartBulletList( caption, warnings ) ) ;
}
ask( content, iconType, buttons ) {
ask( content, iconType, buttons, setFocusTo ) {
// prepare the buttons
let buttons2 = [] ;
for ( let b in buttons ) {
@ -257,6 +271,7 @@ export default class App extends React.Component
if ( notify )
notify() ;
// dismiss the dialog
this._setFocusTo = setFocusTo ? setFocusTo : this._searchFormRef.current.queryStringRef ;
this.setState( { askDialog: null } ) ;
} ;
}
@ -337,11 +352,6 @@ export default class App extends React.Component
this.setState( { startupTasks: this.state.startupTasks } ) ;
}
focusQueryString() {
if ( this._searchFormRef.current )
this._searchFormRef.current.focusQueryString() ;
}
isTestMode() { return process.env.REACT_APP_TEST_MODE ; }
isDisableConstraints() { return this._disableConstraints ; }
isFakeUploads() { return this._fakeUploads ; }

@ -192,15 +192,15 @@ export class ArticleSearchResult2
}
// check the new values
const required = [
[ () => newVals.article_title === "", "Please give it a title." ],
[ () => newVals.article_title === "", "Please give it a title.", refs.article_title ],
] ;
const optional = [
[ () => newVals.pub_id === null, "No publication was specified." ],
[ () => newVals.article_pageno === "" && newVals.pub_id !== null, "No page number was specified." ],
[ () => newVals.article_pageno !== "" && newVals.pub_id === null, "A page number was specified but no publication." ],
[ () => newVals.article_pageno !== "" && !isNumeric(newVals.article_pageno), "The page number is not numeric." ],
[ () => newVals.article_snippet === "", "No snippet was provided." ],
[ () => newVals.article_authors.length === 0, "No authors were specified." ],
[ () => newVals.pub_id === null, "No publication was specified.", refs.pub_id ],
[ () => newVals.article_pageno === "" && newVals.pub_id !== null, "No page number was specified.", refs.article_pageno ],
[ () => newVals.article_pageno !== "" && newVals.pub_id === null, "A page number was specified but no publication.", refs.pub_id ],
[ () => newVals.article_pageno !== "" && !isNumeric(newVals.article_pageno), "The page number is not numeric.", refs.article_pageno ],
[ () => newVals.article_snippet === "", "No snippet was provided.", refs.article_snippet ],
[ () => newVals.article_authors.length === 0, "No authors were specified.", refs.article_authors ],
] ;
const verb = isNew ? "create" : "update" ;
checkConstraints(

@ -209,14 +209,14 @@ export class PublicationSearchResult2
newVals.imageFilename = imageFilename ;
}
const required = [
[ () => newVals.pub_name === undefined, "Please give it a name." ],
[ () => isNew && checkForDupe(newVals), "There is already a publication with this name/edition." ],
[ () => newVals.pub_name === undefined, "Please give it a name.", refs.pub_name ],
[ () => isNew && checkForDupe(newVals), "There is already a publication with this name/edition.", refs.pub_edition ],
] ;
const optional = [
[ () => newVals.pub_name !== undefined && newVals.pub_edition === "", "The publication's edition was not specified." ],
[ () => newVals.pub_date === "", "The publication date was not specified." ],
[ () => newVals.publ_id === null, "A publisher was not specified." ],
[ () => checkArticlePageNumbers(articles), "Some article page numbers are out of order." ],
[ () => newVals.pub_name !== undefined && newVals.pub_edition === "", "The publication's edition was not specified.", refs.pub_edition ],
[ () => newVals.pub_date === "", "The publication date was not specified.", refs.pub_date ],
[ () => newVals.publ_id === null, "A publisher was not specified.", refs.publ_id ],
[ () => checkArticlePageNumbers(articles), "Some article page numbers are out of order.", null ],
] ;
const verb = isNew ? "create" : "update" ;
checkConstraints(

@ -82,8 +82,8 @@ export class PublisherSearchResult2
}
// check the new values
const required = [
[ () => newVals.publ_name === "", "Please give them a name." ],
[ () => isNew && checkForDupe(newVals.publ_name), "There is already a publisher with this name." ],
[ () => newVals.publ_name === "", "Please give them a name.", refs.publ_name ],
[ () => isNew && checkForDupe(newVals.publ_name), "There is already a publisher with this name.", refs.publ_name ],
] ;
const verb = isNew ? "create" : "update" ;
checkConstraints(

@ -14,7 +14,7 @@ export default class SearchForm extends React.Component
} ;
// initialize
this._queryStringRef = React.createRef() ;
this.queryStringRef = React.createRef() ;
}
render() {
@ -24,7 +24,7 @@ export default class SearchForm extends React.Component
<input type="text" className="query"
value = {this.state.queryString}
onChange = { e => this.setState( { queryString: e.target.value } ) }
ref = {this._queryStringRef}
ref = {this.queryStringRef}
autoFocus
/>
<button type="submit" title="Search the database." />
@ -37,6 +37,4 @@ export default class SearchForm extends React.Component
this.props.onSearch( this.state.queryString ) ;
}
focusQueryString() { this._queryStringRef.current.focus() ; }
}

@ -13,24 +13,32 @@ export function checkConstraints( required, requiredCaption, optional, optionalC
}
// check the required constraints
let msgs = [] ;
let msgs=[], setFocusTo=null ;
if ( required ) {
for ( let constraint of required ) {
if ( constraint[0]() )
if ( constraint[0]() ) {
msgs.push( constraint[1] ) ;
if ( constraint[2] && !setFocusTo )
setFocusTo = constraint[2] ;
}
}
}
if ( msgs.length > 0 ) {
gAppRef.showErrorMsg( makeSmartBulletList( requiredCaption, msgs, "constraint" ) ) ;
gAppRef.showErrorMsg(
makeSmartBulletList( requiredCaption, msgs, "constraint" ),
setFocusTo
) ;
return ;
}
// check the optional constraints
msgs = [] ;
if ( optional ) {
for ( let constraint of optional ) {
if ( constraint[0]() )
if ( constraint[0]() ) {
msgs.push( constraint[1] ) ;
if ( constraint[2] && !setFocusTo )
setFocusTo = constraint[2] ;
}
}
}
if ( msgs.length > 0 ) {
@ -39,7 +47,7 @@ export function checkConstraints( required, requiredCaption, optional, optionalC
gAppRef.ask( content, "ask", {
OK: () => { accept() },
Cancel: null
} ) ;
}, setFocusTo ) ;
return ;
}

Loading…
Cancel
Save