Using an Async Iterator in Typescript

I have been experimenting with async iterators in Typescript. One area these could be useful is in simplifying the code for forms, specifically handling button presses.

Currently, I create forms programmatically using my form class, which creates Bootstrap modal forms. "Action buttons" are the buttons that sit in the modal-footer area. When creating "ActionButton"s to add to the form an onClick handler is specified and the form takes care of calling onClick() when the button is clicked.

Here's a trivial example - an About box with an Ok button and second button just for testing.

export function about(): void
{
  let aboutBox = new forms.Form( `About ${app.productName}` );
  aboutBox.addButton(
  {
    name: 'Ok',
    onClick: ()=>
    {
      aboutBox.close();
    }
  } );
  aboutBox.addButton(
  {
    name: 'Test',
    tooltip: 'Test some stuff',
    onClick: ()=>
    {
      // do some stuff
    }
  });

  aboutBox.show();
}


Note that onClick() is actually called with the following parameters - form, button, and event - which I've just ignored for now.

Ok, here's the new version - which uses the form's show2() function, which not only shows the form but returns an async iterator. This iterator returns a tuple for each button press consisting of the button name, a map of the values of each field in the form (obviously *real* forms usually have input fields which have values - currently supported are TextInput, TextArea, RadioInput, CheckBox, Tree, Select, and Button), and the event.

export async function about( e: UnionEvent ): Promise
{
  let aboutBox = new forms.Form( `About ${app.productName}` );
  aboutBox.addButton({ name: 'Ok' } );
  aboutBox.addButton({ name: 'Test', tooltip: 'Test some stuff' });

  let buttonIterator = aboutBox.show2();

  // process button clicks
  for await ( const [ button, fieldValues, e ] of buttonIterator() )
  {
    switch( button )
    {
    case 'Ok':
      aboutBox.close();

      break;
    case 'Test':
      // do some stuff
      break;
    }
  }
}


Hopefully that makes sense?

Here's the show2 function in the form class.

class Form
{
  //[snip ... most of the class removed ]


  show2(): ()=> AsyncIterableIterator<[ string, ValueMap, JQueryEventObject ]>
  {
    let _resolve:( val: [ string, ValueMap, JQueryEventObject ] )=>void = null;

    // when form.close() is called, resolved is set to true

    this.resolved = false;

    // the form's onClose() is called whenever the form is hidden  

    this.onClose = ()=>
    {
      if ( !this.resolved && _resolve )
      {


        // send the Dismiss text as the resolution
        _resolve([ this.dismiss, this.getInputMap(), null ]);
      }
    };


    // each button gets the same handler - this one

    let onClick = ( f: Form, b: ActionButton, e: JQueryEventObject ) =>
    {
      if ( _resolve )
      {
        _resolve([ b.name, this.getInputMap(), e ]);
      }
    };

    for( const button of this.buttons )
    {
      // exclude the help button, all other buttons get the specified click handler
      if ( button.name !== '?' )
      {
        button.onClick = onClick;
      }
    }


    // show the form

    this.show();

    return async function* (): AsyncIterableIterator<[ string, ValueMap, JQueryEventObject ]>
    {
      while( true )
      {
        yield new Promise( resolve =>
        {
          _resolve = ( ...args )=>{ resolve( ...args ); }
        }) as Promise<[ string, ValueMap, JQueryEventObject ]>;
      }
    }
  }
}  


Comments

Popular posts from this blog

Creamoata

Windows FILETIME to Javascript Date