""" Classes for generating HTML forms """ import logging,sys from cgi import escape from galaxy.util import restore_text log = logging.getLogger(__name__) class BaseField(object): def get_html( self, prefix="" ): """Returns the html widget corresponding to the parameter""" raise TypeError( "Abstract Method" ) def get_disabled_str( self, disabled=False ): if disabled: return ' disabled="disabled"' else: return '' @staticmethod def form_field_types(): return ['TextField', 'TextArea', 'SelectField', 'CheckboxField', 'AddressField', 'WorkflowField'] @staticmethod def sample_field_types(): return ['TextField', 'SelectField', 'CheckboxField', 'WorkflowField'] class TextField(BaseField): """ A standard text input box. >>> print TextField( "foo" ).get_html() >>> print TextField( "bins", size=4, value="default" ).get_html() """ def __init__( self, name, size=None, value=None ): self.name = name self.size = int( size or 10 ) self.value = value or "" def get_html( self, prefix="", disabled=False ): return '' \ % ( prefix, self.name, self.size, escape( str( self.value ), quote=True ), self.get_disabled_str( disabled ) ) def set_size(self, size): self.size = int( size ) class PasswordField(BaseField): """ A password input box. text appears as "******" >>> print PasswordField( "foo" ).get_html() >>> print PasswordField( "bins", size=4, value="default" ).get_html() """ def __init__( self, name, size=None, value=None ): self.name = name self.size = int( size or 10 ) self.value = value or "" def get_html( self, prefix="" ): return '' \ % ( prefix, self.name, self.size, escape( str( self.value ), quote=True ) ) def set_size(self, size): self.size = int( size ) class TextArea(BaseField): """ A standard text area box. >>> print TextArea( "foo" ).get_html() >>> print TextArea( "bins", size="4x5", value="default" ).get_html() """ def __init__( self, name, size="5x25", value=None ): self.name = name self.size = size.split("x") self.rows = int(self.size[0]) self.cols = int(self.size[-1]) self.value = value or "" def get_html( self, prefix="", disabled=False ): return '' \ % ( prefix, self.name, self.rows, self.cols, self.get_disabled_str( disabled ), escape( str( self.value ), quote=True ) ) def set_size(self, rows, cols): self.rows = rows self.cols = cols class CheckboxField(BaseField): """ A checkbox (boolean input) >>> print CheckboxField( "foo" ).get_html() >>> print CheckboxField( "bar", checked="yes" ).get_html() """ def __init__( self, name, checked=None ): self.name = name self.checked = ( checked == True ) or ( isinstance( checked, basestring ) and ( checked.lower() in ( "yes", "true", "on" ) ) ) def get_html( self, prefix="", disabled=False ): if self.checked: checked_text = "checked" else: checked_text = "" # The hidden field is necessary because if the check box is not checked on the form, it will # not be included in the request params. The hidden field ensure that this will happen. When # parsing the request, the value 'true' in the hidden field actually means it is NOT checked. # See the is_checked() method below. The prefix is necessary in each case to ensure functional # correctness when the param is inside a conditional. return '' \ % ( prefix, self.name, checked_text, self.get_disabled_str( disabled ), prefix, self.name, self.get_disabled_str( disabled ) ) @staticmethod def is_checked( value ): if value == True: return True # This may look strange upon initial inspection, but see the comments in the get_html() method # above for clarification. Basically, if value is not True, then it will always be a list with # 2 input fields ( a checkbox and a hidden field ) if the checkbox is checked. If it is not # checked, then value will be only the hidden field. return isinstance( value, list ) and len( value ) == 2 def set_checked(self, value): if isinstance( value, basestring ): self.checked = value.lower() in [ "yes", "true", "on" ] else: self.checked = value class FileField(BaseField): """ A file upload input. >>> print FileField( "foo" ).get_html() >>> print FileField( "foo", ajax = True ).get_html() """ def __init__( self, name, value = None, ajax=False ): self.name = name self.ajax = ajax self.value = value def get_html( self, prefix="" ): value_text = "" if self.value: value_text = ' value="%s"' % self.value ajax_text = "" if self.ajax: ajax_text = ' galaxy-ajax-upload="true"' return '' % ( prefix, self.name, ajax_text, value_text ) class HiddenField(BaseField): """ A hidden field. >>> print HiddenField( "foo", 100 ).get_html() """ def __init__( self, name, value=None ): self.name = name self.value = value or "" def get_html( self, prefix="" ): return '' % ( prefix, self.name, escape( str( self.value ), quote=True ) ) class SelectField(BaseField): """ A select field. >>> t = SelectField( "foo", multiple=True ) >>> t.add_option( "tuti", 1 ) >>> t.add_option( "fruity", "x" ) >>> print t.get_html() >>> t = SelectField( "bar" ) >>> t.add_option( "automatic", 3 ) >>> t.add_option( "bazooty", 4, selected=True ) >>> print t.get_html() >>> t = SelectField( "foo", display="radio" ) >>> t.add_option( "tuti", 1 ) >>> t.add_option( "fruity", "x" ) >>> print t.get_html()
tuti
fruity
>>> t = SelectField( "bar", multiple=True, display="checkboxes" ) >>> t.add_option( "automatic", 3 ) >>> t.add_option( "bazooty", 4, selected=True ) >>> print t.get_html()
automatic
bazooty
""" def __init__( self, name, multiple=None, display=None, refresh_on_change=False, refresh_on_change_values=[], size=None ): self.name = name self.multiple = multiple or False self.size = size self.options = list() if display == "checkboxes": assert multiple, "Checkbox display only supported for multiple select" elif display == "radio": assert not( multiple ), "Radio display only supported for single select" elif display is not None: raise Exception, "Unknown display type: %s" % display self.display = display self.refresh_on_change = refresh_on_change self.refresh_on_change_values = refresh_on_change_values if self.refresh_on_change: self.refresh_on_change_text = ' refresh_on_change="true"' if self.refresh_on_change_values: self.refresh_on_change_text = '%s refresh_on_change_values="%s"' % ( self.refresh_on_change_text, ",".join( self.refresh_on_change_values ) ) else: self.refresh_on_change_text = '' def add_option( self, text, value, selected = False ): self.options.append( ( text, value, selected ) ) def get_html( self, prefix="", disabled=False ): if self.display == "checkboxes": return self.get_html_checkboxes( prefix, disabled ) elif self.display == "radio": return self.get_html_radio( prefix, disabled ) else: return self.get_html_default( prefix, disabled ) def get_html_checkboxes( self, prefix="", disabled=False ): rval = [] ctr = 0 if len( self.options ) > 1: rval.append ( '
' % ( prefix, self.name ) ) #placeholder for the insertion of the Select All/Unselect All buttons for text, value, selected in self.options: style = "" if len(self.options) > 2 and ctr % 2 == 1: style = " class=\"odd_row\"" if selected: rval.append( '%s' % \ ( style, prefix, self.name, escape( str( value ), quote=True ), self.get_disabled_str( disabled ), text ) ) else: rval.append( '%s' % \ ( style, prefix, self.name, escape( str( value ), quote=True ), self.get_disabled_str( disabled ), text ) ) ctr += 1 return "\n".join( rval ) def get_html_radio( self, prefix="", disabled=False ): rval = [] ctr = 0 for text, value, selected in self.options: style = "" if len(self.options) > 2 and ctr % 2 == 1: style = " class=\"odd_row\"" if selected: selected_text = " checked" else: selected_text = "" rval.append( '%s' % \ ( style, prefix, self.name, self.refresh_on_change_text, escape( str( value ), quote=True ), selected_text, self.get_disabled_str( disabled ), text ) ) ctr += 1 return "\n".join( rval ) def get_html_default( self, prefix="", disabled=False ): if self.multiple: multiple = " multiple" else: multiple = "" if self.size: size = ' size="%s"' % str( self.size ) else: size = '' rval = [] last_selected_value = "" for text, value, selected in self.options: if selected: selected_text = " selected" last_selected_value = value else: selected_text = "" rval.append( '' % ( escape( str( value ), quote=True ), selected_text, text ) ) if last_selected_value: last_selected_value = ' last_selected_value="%s"' % escape( str( last_selected_value ), quote=True ) rval.insert( 0, '' ) return "\n".join( rval ) def get_selected( self, return_label=False, return_value=False, multi=False ): ''' Return the currently selected option's label, value or both as a tuple. For multi-select lists, a list is returned. ''' if multi: selected_options = [] for label, value, selected in self.options: if selected: if return_label and return_value: if multi: selected_options.append( ( label, value ) ) else: return ( label, value ) elif return_label: if multi: selected_options.append( label ) else: return label elif return_value: if multi: selected_options.append( value ) else: return value if multi: return selected_options return None class DrillDownField( BaseField ): """ A hierarchical select field, which allows users to 'drill down' a tree-like set of options. >>> t = DrillDownField( "foo", multiple=True, display="checkbox", options=[{'name': 'Heading 1', 'value': 'heading1', 'options': [{'name': 'Option 1', 'value': 'option1', 'options': []}, {'name': 'Option 2', 'value': 'option2', 'options': []}, {'name': 'Heading 1', 'value': 'heading1', 'options': [{'name': 'Option 3', 'value': 'option3', 'options': []}, {'name': 'Option 4', 'value': 'option4', 'options': []}]}]}, {'name': 'Option 5', 'value': 'option5', 'options': []}] ) >>> print t.get_html()
  • [+]Heading 1
    • Option 1
    • Option 2
    • [+]Heading 1
      • Option 3
      • Option 4
  • Option 5
>>> t = DrillDownField( "foo", multiple=False, display="radio", options=[{'name': 'Heading 1', 'value': 'heading1', 'options': [{'name': 'Option 1', 'value': 'option1', 'options': []}, {'name': 'Option 2', 'value': 'option2', 'options': []}, {'name': 'Heading 1', 'value': 'heading1', 'options': [{'name': 'Option 3', 'value': 'option3', 'options': []}, {'name': 'Option 4', 'value': 'option4', 'options': []}]}]}, {'name': 'Option 5', 'value': 'option5', 'options': []}] ) >>> print t.get_html()
  • [+]Heading 1
    • Option 1
    • Option 2
    • [+]Heading 1
      • Option 3
      • Option 4
  • Option 5
""" def __init__( self, name, multiple=None, display=None, refresh_on_change=False, options = [], value = [], refresh_on_change_values = [] ): self.name = name self.multiple = multiple or False self.options = options if value and not isinstance( value, list ): value = [ value ] else: value = [] self.value = value if display == "checkbox": assert multiple, "Checkbox display only supported for multiple select" elif display == "radio": assert not( multiple ), "Radio display only supported for single select" else: raise Exception, "Unknown display type: %s" % display self.display = display self.refresh_on_change = refresh_on_change self.refresh_on_change_values = refresh_on_change_values if self.refresh_on_change: self.refresh_on_change_text = ' refresh_on_change="true"' if self.refresh_on_change_values: self.refresh_on_change_text = '%s refresh_on_change_values="%s"' % ( self.refresh_on_change_text, ",".join( self.refresh_on_change_values ) ) else: self.refresh_on_change_text = '' def get_html( self, prefix="" ): def find_expanded_options( expanded_options, options, parent_options = [] ): for option in options: if option['value'] in self.value: expanded_options.extend( parent_options ) if option['options']: new_parents = list( parent_options ) + [ option['value'] ] find_expanded_options( expanded_options, option['options'], new_parents ) def recurse_options( html, options, expanded_options = [] ): for option in options: selected = ( option['value'] in self.value ) if selected: selected = ' checked' else: selected = '' if option['options']: default_state = 'collapsed' default_icon = '[+]' if option['value'] in expanded_options: default_state = 'expanded' default_icon = '[-]' html.append( '
  • %s%s' % ( default_icon, self.display, prefix, self.name, escape(str(option['value']), quote=True), selected, option['name']) ) html.append( '
      ' % default_state ) recurse_options( html, option['options'], expanded_options ) html.append( '
    ') else: html.append( '
  • %s' % ( self.display, prefix, self.name, escape(str(option['value']), quote=True), selected, option['name']) ) html.append( '
  • ' ) rval = [] rval.append( '
      ' ) expanded_options = [] find_expanded_options( expanded_options, self.options ) recurse_options( rval, self.options, expanded_options ) rval.append( '
    ' ) return '\n'.join( rval ) class AddressField(BaseField): @staticmethod def fields(): return [ ( "short_desc", "Short address description", "Required" ), ( "name", "Name", "Required" ), ( "institution", "Institution", "Required" ), ( "address", "Address", "Required" ), ( "city", "City", "Required" ), ( "state", "State/Province/Region", "Required" ), ( "postal_code", "Postal Code", "Required" ), ( "country", "Country", "Required" ), ( "phone", "Phone", "" ) ] def __init__(self, name, user=None, value=None, params=None): self.name = name self.user = user self.value = value self.select_address = None self.params = params def get_html( self, disabled=False ): address_html = '' add_ids = ['none'] if self.user: for a in self.user.addresses: add_ids.append( str( a.id ) ) add_ids.append( 'new' ) self.select_address = SelectField( self.name, refresh_on_change=True, refresh_on_change_values=add_ids ) if self.value == 'none': self.select_address.add_option( 'Select one', 'none', selected=True ) else: self.select_address.add_option( 'Select one', 'none' ) if self.user: for a in self.user.addresses: if not a.deleted: if self.value == str( a.id ): self.select_address.add_option( a.desc, str( a.id ), selected=True ) # Display this address address_html += '''
    %s
    ''' % a.get_html() else: self.select_address.add_option( a.desc, str( a.id ) ) if self.value == 'new': self.select_address.add_option( 'Add a new address', 'new', selected=True ) for field_name, label, help_text in self.fields(): add_field = TextField( self.name + '_' + field_name, 40, restore_text( self.params.get( self.name + '_' + field_name, '' ) ) ) address_html += '''
    %s ''' % ( label, add_field.get_html( disabled=disabled ) ) if help_text: address_html += '''
    %s
    ''' % help_text address_html += '''
    ''' else: self.select_address.add_option( 'Add a new address', 'new' ) return self.select_address.get_html( disabled=disabled ) + address_html class WorkflowField(BaseField): def __init__(self, name, user=None, value=None, params=None): self.name = name self.user = user self.value = value self.select_workflow = None self.params = params def get_html( self, disabled=False ): self.select_workflow = SelectField( self.name ) if self.value == 'none': self.select_workflow.add_option( 'Select one', 'none', selected=True ) else: self.select_workflow.add_option( 'Select one', 'none' ) if self.user: for a in self.user.stored_workflows: if not a.deleted: if str( self.value ) == str( a.id ): self.select_workflow.add_option( a.name, str( a.id ), selected=True ) else: self.select_workflow.add_option( a.name, str( a.id ) ) return self.select_workflow.get_html( disabled=disabled ) def get_suite(): """Get unittest suite for this module""" import doctest, sys return doctest.DocTestSuite( sys.modules[__name__] ) # --------- Utility methods ----------------------------- def build_select_field( trans, objs, label_attr, select_field_name, initial_value='none', selected_value='none', refresh_on_change=False, multiple=False, display=None, size=None ): """ Build a SelectField given a set of objects. The received params are: - objs: the set of object used to populate the option list - label_attr: the attribute of each obj (e.g., name, email, etc ) whose value is used to populate each option label. If the string 'self' is passed as label_attr, each obj in objs is assumed to be a string, so the obj itself is used - select_field_name: the name of the SelectField - initial_value: the vlaue of the first option in the SelectField - allows for an option telling the user to select something - selected_value: the value of the currently selected option - refresh_on_change: True if the SelectField should perform a refresh_on_change """ values = [ initial_value ] for obj in objs: if label_attr == 'self': # Each obj is a string values.append( obj ) else: values.append( trans.security.encode_id( obj.id ) ) if refresh_on_change: refresh_on_change_values = values else: refresh_on_change_values = [] select_field = SelectField( name=select_field_name, multiple=multiple, display=display, refresh_on_change=refresh_on_change, refresh_on_change_values=refresh_on_change_values, size=size ) if display is None: # only insert an initial "Select one" option if we are not displaying check boxes or radio buttons if selected_value == initial_value: select_field.add_option( 'Select one', initial_value, selected=True ) else: select_field.add_option( 'Select one', initial_value ) for obj in objs: if label_attr == 'self': # Each obj is a string if str( selected_value ) == str( obj ): select_field.add_option( obj, obj, selected=True ) else: select_field.add_option( obj, obj ) else: label = getattr( obj, label_attr ) if str( selected_value ) == str( obj.id ) or str( selected_value ) == trans.security.encode_id( obj.id ): select_field.add_option( label, trans.security.encode_id( obj.id ), selected=True ) else: select_field.add_option( label, trans.security.encode_id( obj.id ) ) return select_field