root/galaxy-central/lib/galaxy/web/form_builder.py @ 2

リビジョン 2, 26.4 KB (コミッタ: hatakeyama, 14 年 前)

import galaxy-central

行番号 
1"""
2Classes for generating HTML forms
3"""
4
5import logging,sys
6from cgi import escape
7from galaxy.util import restore_text
8
9log = logging.getLogger(__name__)
10
11class BaseField(object):
12    def get_html( self, prefix="" ):
13        """Returns the html widget corresponding to the parameter"""
14        raise TypeError( "Abstract Method" )
15    def get_disabled_str( self, disabled=False ):
16        if disabled:
17            return ' disabled="disabled"'
18        else:
19            return ''
20    @staticmethod
21    def form_field_types():
22        return ['TextField', 'TextArea', 'SelectField', 'CheckboxField', 'AddressField', 'WorkflowField']
23    @staticmethod
24    def sample_field_types():
25        return ['TextField', 'SelectField', 'CheckboxField', 'WorkflowField']
26
27class TextField(BaseField):
28    """
29    A standard text input box.
30   
31    >>> print TextField( "foo" ).get_html()
32    <input type="text" name="foo" size="10" value="">
33    >>> print TextField( "bins", size=4, value="default" ).get_html()
34    <input type="text" name="bins" size="4" value="default">
35    """
36    def __init__( self, name, size=None, value=None ):
37        self.name = name
38        self.size = int( size or 10 )
39        self.value = value or ""
40    def get_html( self, prefix="", disabled=False ):
41        return '<input type="text" name="%s%s" size="%d" value="%s"%s>' \
42            % ( prefix, self.name, self.size, escape( str( self.value ),  quote=True ), self.get_disabled_str( disabled ) )
43    def set_size(self, size):
44        self.size = int( size )
45       
46class PasswordField(BaseField):
47    """
48    A password input box. text appears as "******"
49   
50    >>> print PasswordField( "foo" ).get_html()
51    <input type="password" name="foo" size="10" value="">
52    >>> print PasswordField( "bins", size=4, value="default" ).get_html()
53    <input type="password" name="bins" size="4" value="default">
54    """
55    def __init__( self, name, size=None, value=None ):
56        self.name = name
57        self.size = int( size or 10 )
58        self.value = value or ""
59    def get_html( self, prefix="" ):
60        return '<input type="password" name="%s%s" size="%d" value="%s">' \
61            % ( prefix, self.name, self.size, escape( str( self.value ), quote=True ) )
62    def set_size(self, size):
63        self.size = int( size )
64
65class TextArea(BaseField):
66    """
67    A standard text area box.
68   
69    >>> print TextArea( "foo" ).get_html()
70    <textarea name="foo" rows="5" cols="25"></textarea>
71    >>> print TextArea( "bins", size="4x5", value="default" ).get_html()
72    <textarea name="bins" rows="4" cols="5">default</textarea>
73    """
74    def __init__( self, name, size="5x25", value=None ):
75        self.name = name
76        self.size = size.split("x")
77        self.rows = int(self.size[0])
78        self.cols = int(self.size[-1])
79        self.value = value or ""
80    def get_html( self, prefix="", disabled=False ):
81        return '<textarea name="%s%s" rows="%d" cols="%d"%s>%s</textarea>' \
82            % ( prefix, self.name, self.rows, self.cols, self.get_disabled_str( disabled ), escape( str( self.value ), quote=True ) )
83    def set_size(self, rows, cols):
84        self.rows = rows
85        self.cols = cols
86
87class CheckboxField(BaseField):
88    """
89    A checkbox (boolean input)
90   
91    >>> print CheckboxField( "foo" ).get_html()
92    <input type="checkbox" name="foo" value="true" ><input type="hidden" name="foo" value="true">
93    >>> print CheckboxField( "bar", checked="yes" ).get_html()
94    <input type="checkbox" name="bar" value="true" checked><input type="hidden" name="bar" value="true">
95    """
96    def __init__( self, name, checked=None ):
97        self.name = name
98        self.checked = ( checked == True ) or ( isinstance( checked, basestring ) and ( checked.lower() in ( "yes", "true", "on" ) ) )
99    def get_html( self, prefix="", disabled=False ):
100        if self.checked:
101            checked_text = "checked"
102        else:
103            checked_text = ""
104        # The hidden field is necessary because if the check box is not checked on the form, it will
105        # not be included in the request params.  The hidden field ensure that this will happen.  When
106        # parsing the request, the value 'true' in the hidden field actually means it is NOT checked.
107        # See the is_checked() method below.  The prefix is necessary in each case to ensure functional
108        # correctness when the param is inside a conditional.
109        return '<input type="checkbox" name="%s%s" value="true" %s%s><input type="hidden" name="%s%s" value="true"%s>' \
110            % ( prefix, self.name, checked_text, self.get_disabled_str( disabled ), prefix, self.name, self.get_disabled_str( disabled ) )
111    @staticmethod
112    def is_checked( value ):
113        if value == True:
114            return True
115        # This may look strange upon initial inspection, but see the comments in the get_html() method
116        # above for clarification.  Basically, if value is not True, then it will always be a list with
117        # 2 input fields ( a checkbox and a hidden field ) if the checkbox is checked.  If it is not
118        # checked, then value will be only the hidden field.
119        return isinstance( value, list ) and len( value ) == 2
120    def set_checked(self, value):
121        if isinstance( value, basestring ):
122            self.checked = value.lower() in [ "yes", "true", "on" ]
123        else:
124            self.checked = value
125
126class FileField(BaseField):
127    """
128    A file upload input.
129   
130    >>> print FileField( "foo" ).get_html()
131    <input type="file" name="foo">
132    >>> print FileField( "foo", ajax = True ).get_html()
133    <input type="file" name="foo" galaxy-ajax-upload="true">
134    """
135    def __init__( self, name, value = None, ajax=False ):
136        self.name = name
137        self.ajax = ajax
138        self.value = value
139    def get_html( self, prefix="" ):
140        value_text = ""
141        if self.value:
142            value_text = ' value="%s"' % self.value
143        ajax_text = ""
144        if self.ajax:
145            ajax_text = ' galaxy-ajax-upload="true"'
146        return '<input type="file" name="%s%s"%s%s>' % ( prefix, self.name, ajax_text, value_text )
147
148class HiddenField(BaseField):
149    """
150    A hidden field.
151   
152    >>> print HiddenField( "foo", 100 ).get_html()
153    <input type="hidden" name="foo" value="100">
154    """
155    def __init__( self, name, value=None ):
156        self.name = name
157        self.value = value or ""
158    def get_html( self, prefix="" ):
159        return '<input type="hidden" name="%s%s" value="%s">' % ( prefix, self.name, escape( str( self.value ), quote=True ) )
160
161class SelectField(BaseField):
162    """
163    A select field.
164   
165    >>> t = SelectField( "foo", multiple=True )
166    >>> t.add_option( "tuti", 1 )
167    >>> t.add_option( "fruity", "x" )
168    >>> print t.get_html()
169    <select name="foo" multiple>
170    <option value="1">tuti</option>
171    <option value="x">fruity</option>
172    </select>
173   
174    >>> t = SelectField( "bar" )
175    >>> t.add_option( "automatic", 3 )
176    >>> t.add_option( "bazooty", 4, selected=True )
177    >>> print t.get_html()
178    <select name="bar" last_selected_value="4">
179    <option value="3">automatic</option>
180    <option value="4" selected>bazooty</option>
181    </select>
182   
183    >>> t = SelectField( "foo", display="radio" )
184    >>> t.add_option( "tuti", 1 )
185    >>> t.add_option( "fruity", "x" )
186    >>> print t.get_html()
187    <div><input type="radio" name="foo" value="1">tuti</div>
188    <div><input type="radio" name="foo" value="x">fruity</div>
189
190    >>> t = SelectField( "bar", multiple=True, display="checkboxes" )
191    >>> t.add_option( "automatic", 3 )
192    >>> t.add_option( "bazooty", 4, selected=True )
193    >>> print t.get_html()
194    <div class="checkUncheckAllPlaceholder" checkbox_name="bar"></div>
195    <div><input type="checkbox" name="bar" value="3">automatic</div>
196    <div><input type="checkbox" name="bar" value="4" checked>bazooty</div>
197    """
198    def __init__( self, name, multiple=None, display=None, refresh_on_change=False, refresh_on_change_values=[], size=None ):
199        self.name = name
200        self.multiple = multiple or False
201        self.size = size
202        self.options = list()
203        if display == "checkboxes":
204            assert multiple, "Checkbox display only supported for multiple select"
205        elif display == "radio":
206            assert not( multiple ), "Radio display only supported for single select"
207        elif display is not None:
208            raise Exception, "Unknown display type: %s" % display
209        self.display = display
210        self.refresh_on_change = refresh_on_change
211        self.refresh_on_change_values = refresh_on_change_values
212        if self.refresh_on_change:
213            self.refresh_on_change_text = ' refresh_on_change="true"'
214            if self.refresh_on_change_values:
215                self.refresh_on_change_text = '%s refresh_on_change_values="%s"' % ( self.refresh_on_change_text, ",".join( self.refresh_on_change_values ) )
216        else:
217            self.refresh_on_change_text = ''
218    def add_option( self, text, value, selected = False ):
219        self.options.append( ( text, value, selected ) )
220    def get_html( self, prefix="", disabled=False ):
221        if self.display == "checkboxes":
222            return self.get_html_checkboxes( prefix, disabled )
223        elif self.display == "radio":
224            return self.get_html_radio( prefix, disabled )
225        else:
226            return self.get_html_default( prefix, disabled )
227    def get_html_checkboxes( self, prefix="", disabled=False ):
228        rval = []
229        ctr = 0
230        if len( self.options ) > 1:
231            rval.append ( '<div class="checkUncheckAllPlaceholder" checkbox_name="%s%s"></div>' % ( prefix, self.name ) ) #placeholder for the insertion of the Select All/Unselect All buttons
232        for text, value, selected in self.options:
233            style = ""
234            if len(self.options) > 2 and ctr % 2 == 1:
235                style = " class=\"odd_row\""
236            if selected:
237                rval.append( '<div%s><input type="checkbox" name="%s%s" value="%s" checked%s>%s</div>' % \
238                             ( style, prefix, self.name, escape( str( value ), quote=True ), self.get_disabled_str( disabled ), text ) )
239            else:
240                rval.append( '<div%s><input type="checkbox" name="%s%s" value="%s"%s>%s</div>' % \
241                             ( style, prefix, self.name, escape( str( value ), quote=True ), self.get_disabled_str( disabled ), text ) )
242            ctr += 1
243        return "\n".join( rval )
244    def get_html_radio( self, prefix="", disabled=False ):
245        rval = []
246        ctr = 0
247        for text, value, selected in self.options:
248            style = ""
249            if len(self.options) > 2 and ctr % 2 == 1:
250                style = " class=\"odd_row\""
251            if selected: selected_text = " checked"
252            else: selected_text = ""
253            rval.append( '<div%s><input type="radio" name="%s%s"%s value="%s"%s%s>%s</div>' % \
254                         ( style,
255                           prefix,
256                           self.name,
257                           self.refresh_on_change_text,
258                           escape( str( value ), quote=True ),
259                           selected_text,
260                           self.get_disabled_str( disabled ),
261                           text ) )
262            ctr += 1
263        return "\n".join( rval )   
264    def get_html_default( self, prefix="", disabled=False ):
265        if self.multiple:
266            multiple = " multiple"
267        else:
268            multiple = ""
269        if self.size:
270            size = ' size="%s"' % str( self.size )
271        else:
272            size = ''
273        rval = []
274        last_selected_value = ""
275        for text, value, selected in self.options:
276            if selected:
277                selected_text = " selected"
278                last_selected_value = value
279            else:
280                selected_text = ""
281            rval.append( '<option value="%s"%s>%s</option>' % ( escape( str( value ), quote=True ), selected_text, text ) )
282        if last_selected_value:
283            last_selected_value = ' last_selected_value="%s"' % escape( str( last_selected_value ), quote=True )
284        rval.insert( 0, '<select name="%s%s"%s%s%s%s%s>' % \
285                     ( prefix, self.name, multiple, size, self.refresh_on_change_text, last_selected_value, self.get_disabled_str( disabled ) ) )
286        rval.append( '</select>' )
287        return "\n".join( rval )
288    def get_selected( self, return_label=False, return_value=False, multi=False ):
289        '''
290        Return the currently selected option's label, value or both as a tuple.  For
291        multi-select lists, a list is returned.
292        '''
293        if multi:
294            selected_options = []
295        for label, value, selected in self.options:
296            if selected:
297                if return_label and return_value:
298                    if multi:
299                        selected_options.append( ( label, value ) )
300                    else:
301                        return ( label, value )
302                elif return_label:
303                    if multi:
304                        selected_options.append( label )
305                    else:
306                        return label
307                elif return_value:
308                    if multi:
309                        selected_options.append( value )
310                    else:
311                        return value
312        if multi:
313            return selected_options
314        return None
315
316class DrillDownField( BaseField ):
317    """
318    A hierarchical select field, which allows users to 'drill down' a tree-like set of options.
319   
320    >>> 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': []}] )
321    >>> print t.get_html()
322    <div><ul class="toolParameterExpandableCollapsable">
323    <li><span class="toolParameterExpandableCollapsable">[+]</span><input type="checkbox" name="foo" value="heading1"">Heading 1
324    <ul class="toolParameterExpandableCollapsable" default_state="collapsed">
325    <li><input type="checkbox" name="foo" value="option1"">Option 1
326    </li>
327    <li><input type="checkbox" name="foo" value="option2"">Option 2
328    </li>
329    <li><span class="toolParameterExpandableCollapsable">[+]</span><input type="checkbox" name="foo" value="heading1"">Heading 1
330    <ul class="toolParameterExpandableCollapsable" default_state="collapsed">
331    <li><input type="checkbox" name="foo" value="option3"">Option 3
332    </li>
333    <li><input type="checkbox" name="foo" value="option4"">Option 4
334    </li>
335    </ul>
336    </li>
337    </ul>
338    </li>
339    <li><input type="checkbox" name="foo" value="option5"">Option 5
340    </li>
341    </ul></div>
342    >>> 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': []}] )
343    >>> print t.get_html()
344    <div><ul class="toolParameterExpandableCollapsable">
345    <li><span class="toolParameterExpandableCollapsable">[+]</span><input type="radio" name="foo" value="heading1"">Heading 1
346    <ul class="toolParameterExpandableCollapsable" default_state="collapsed">
347    <li><input type="radio" name="foo" value="option1"">Option 1
348    </li>
349    <li><input type="radio" name="foo" value="option2"">Option 2
350    </li>
351    <li><span class="toolParameterExpandableCollapsable">[+]</span><input type="radio" name="foo" value="heading1"">Heading 1
352    <ul class="toolParameterExpandableCollapsable" default_state="collapsed">
353    <li><input type="radio" name="foo" value="option3"">Option 3
354    </li>
355    <li><input type="radio" name="foo" value="option4"">Option 4
356    </li>
357    </ul>
358    </li>
359    </ul>
360    </li>
361    <li><input type="radio" name="foo" value="option5"">Option 5
362    </li>
363    </ul></div>
364    """
365    def __init__( self, name, multiple=None, display=None, refresh_on_change=False, options = [], value = [], refresh_on_change_values = [] ):
366        self.name = name
367        self.multiple = multiple or False
368        self.options = options
369        if value and not isinstance( value, list ):
370            value = [ value ]
371        else:
372            value = []
373        self.value = value
374        if display == "checkbox":
375            assert multiple, "Checkbox display only supported for multiple select"
376        elif display == "radio":
377            assert not( multiple ), "Radio display only supported for single select"
378        else:
379            raise Exception, "Unknown display type: %s" % display
380        self.display = display
381        self.refresh_on_change = refresh_on_change
382        self.refresh_on_change_values = refresh_on_change_values
383        if self.refresh_on_change:
384            self.refresh_on_change_text = ' refresh_on_change="true"'
385            if self.refresh_on_change_values:
386                self.refresh_on_change_text = '%s refresh_on_change_values="%s"' % ( self.refresh_on_change_text, ",".join( self.refresh_on_change_values ) )
387        else:
388            self.refresh_on_change_text = ''
389    def get_html( self, prefix="" ):
390        def find_expanded_options( expanded_options, options, parent_options = [] ):
391            for option in options:
392                if option['value'] in self.value:
393                    expanded_options.extend( parent_options )
394                if option['options']:
395                    new_parents = list( parent_options ) + [ option['value'] ]
396                    find_expanded_options( expanded_options, option['options'], new_parents )
397        def recurse_options( html, options, expanded_options = [] ):
398            for option in options:
399                selected = ( option['value'] in self.value )
400                if selected: selected = ' checked'
401                else: selected = ''
402                if option['options']:
403                    default_state = 'collapsed'
404                    default_icon = '[+]'
405                    if option['value'] in expanded_options:
406                        default_state = 'expanded'
407                        default_icon = '[-]'
408                    html.append( '<li><span class="toolParameterExpandableCollapsable">%s</span><input type="%s" name="%s%s" value="%s"%s">%s' % ( default_icon, self.display, prefix, self.name, escape(str(option['value']), quote=True), selected, option['name']) )
409                    html.append( '<ul class="toolParameterExpandableCollapsable" default_state="%s">' % default_state )
410                    recurse_options( html, option['options'], expanded_options )
411                    html.append( '</ul>')
412                else:
413                    html.append( '<li><input type="%s" name="%s%s" value="%s"%s">%s' % ( self.display, prefix, self.name, escape(str(option['value']), quote=True), selected, option['name']) )
414                html.append( '</li>' )
415        rval = []
416        rval.append( '<div><ul class="toolParameterExpandableCollapsable">' )
417        expanded_options = []
418        find_expanded_options( expanded_options, self.options )
419        recurse_options( rval, self.options, expanded_options )
420        rval.append( '</ul></div>' )
421        return '\n'.join( rval )
422   
423class AddressField(BaseField):
424    @staticmethod
425    def fields():
426        return   [  ( "short_desc", "Short address description", "Required" ),
427                    ( "name", "Name", "Required" ),
428                    ( "institution", "Institution", "Required" ),
429                    ( "address", "Address", "Required" ),
430                    ( "city", "City", "Required" ),
431                    ( "state", "State/Province/Region", "Required" ),
432                    ( "postal_code", "Postal Code", "Required" ),
433                    ( "country", "Country", "Required" ),
434                    ( "phone", "Phone", "" )  ]
435    def __init__(self, name, user=None, value=None, params=None):
436        self.name = name
437        self.user = user
438        self.value = value
439        self.select_address = None
440        self.params = params
441    def get_html( self, disabled=False ):
442        address_html = ''
443        add_ids = ['none']
444        if self.user:
445            for a in self.user.addresses:
446                add_ids.append( str( a.id ) )
447        add_ids.append( 'new' )
448        self.select_address = SelectField( self.name,
449                                           refresh_on_change=True,
450                                           refresh_on_change_values=add_ids )
451        if self.value == 'none':
452            self.select_address.add_option( 'Select one', 'none', selected=True )
453        else:
454            self.select_address.add_option( 'Select one', 'none' )
455        if self.user:
456            for a in self.user.addresses:
457                if not a.deleted:
458                    if self.value == str( a.id ):
459                        self.select_address.add_option( a.desc, str( a.id ), selected=True )
460                        # Display this address
461                        address_html += '''
462                                        <div class="form-row">
463                                            %s
464                                        </div>
465                                        ''' % a.get_html()
466                    else:
467                        self.select_address.add_option( a.desc, str( a.id ) )
468        if self.value == 'new':
469            self.select_address.add_option( 'Add a new address', 'new', selected=True )
470            for field_name, label, help_text in self.fields():
471                add_field = TextField( self.name + '_' + field_name,
472                                      40,
473                                      restore_text( self.params.get( self.name + '_' + field_name, ''  ) ) )
474                address_html += '''
475                                <div class="form-row">
476                                    <label>%s</label>
477                                    %s
478                                    ''' % ( label, add_field.get_html( disabled=disabled ) )
479                if help_text:
480                    address_html += '''
481                                    <div class="toolParamHelp" style="clear: both;">
482                                        %s
483                                    </div>
484                                    ''' % help_text     
485                address_html += '''
486                                </div>
487                                '''
488        else:
489            self.select_address.add_option( 'Add a new address', 'new' )
490        return self.select_address.get_html( disabled=disabled ) + address_html
491
492class WorkflowField(BaseField):
493    def __init__(self, name, user=None, value=None, params=None):
494        self.name = name
495        self.user = user
496        self.value = value
497        self.select_workflow = None
498        self.params = params
499    def get_html( self, disabled=False ):
500        self.select_workflow = SelectField( self.name )
501        if self.value == 'none':
502            self.select_workflow.add_option( 'Select one', 'none', selected=True )
503        else:
504            self.select_workflow.add_option( 'Select one', 'none' )
505        if self.user:
506            for a in self.user.stored_workflows:
507                if not a.deleted:
508                    if str( self.value ) == str( a.id ):
509                        self.select_workflow.add_option( a.name, str( a.id ), selected=True )
510                    else:
511                        self.select_workflow.add_option( a.name, str( a.id ) )
512        return self.select_workflow.get_html( disabled=disabled )
513
514def get_suite():
515    """Get unittest suite for this module"""
516    import doctest, sys
517    return doctest.DocTestSuite( sys.modules[__name__] )
518
519# --------- Utility methods -----------------------------
520
521def build_select_field( trans, objs, label_attr,  select_field_name, initial_value='none',
522                        selected_value='none', refresh_on_change=False, multiple=False, display=None, size=None ):
523    """
524    Build a SelectField given a set of objects.  The received params are:
525    - objs: the set of object used to populate the option list
526    - label_attr: the attribute of each obj (e.g., name, email, etc ) whose value is used to populate each option label.  If the string
527      'self' is passed as label_attr, each obj in objs is assumed to be a string, so the obj itself is used
528    - select_field_name: the name of the SelectField
529    - initial_value: the vlaue of the first option in the SelectField - allows for an option telling the user to select something
530    - selected_value: the value of the currently selected option
531    - refresh_on_change: True if the SelectField should perform a refresh_on_change
532    """
533    values = [ initial_value ]
534    for obj in objs:
535        if label_attr == 'self':
536            # Each obj is a string
537            values.append( obj )
538        else:
539            values.append( trans.security.encode_id( obj.id ) )
540    if refresh_on_change:
541        refresh_on_change_values = values
542    else:
543        refresh_on_change_values = []
544    select_field = SelectField( name=select_field_name,
545                                multiple=multiple,
546                                display=display,
547                                refresh_on_change=refresh_on_change,
548                                refresh_on_change_values=refresh_on_change_values,
549                                size=size )
550    if display is None:
551        # only insert an initial "Select one" option if we are not displaying check boxes or radio buttons
552        if selected_value == initial_value:
553            select_field.add_option( 'Select one', initial_value, selected=True )
554        else:
555            select_field.add_option( 'Select one', initial_value )
556    for obj in objs:
557        if label_attr == 'self':
558            # Each obj is a string
559            if str( selected_value ) == str( obj ):
560                select_field.add_option( obj, obj, selected=True )
561            else:
562                select_field.add_option( obj, obj )
563        else:
564            label = getattr( obj, label_attr )
565            if str( selected_value ) == str( obj.id ) or str( selected_value ) == trans.security.encode_id( obj.id ):
566                select_field.add_option( label, trans.security.encode_id( obj.id ), selected=True )
567            else:
568                select_field.add_option( label, trans.security.encode_id( obj.id ) )
569    return select_field
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。