Changeset 2993

Show
Ignore:
Timestamp:
01/07/08 08:58:56 (1 year ago)
Author:
athomas
Message:

Form validation and messages, hints.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trachacksplugin/0.11/setup.py

    r2979 r2993  
    1313        ], 
    1414    entry_points={ 
    15         'trac.plugins': 'trachacks = trachacks' 
     15        'trac.plugins': [ 
     16            'trachacks.web_ui = trachacks.web_ui', 
     17            ] 
    1618        }, 
    1719    install_requires=[ 
  • trachacksplugin/0.11/trachacks/htdocs/css/trachacks.css

    r2983 r2993  
    33 
    44.newhack { 
    5   width: 850px; 
     5  width: 640px; 
    66} 
    77 
    88.newhack input[type="text"] { 
    9   width: 65%; 
     9  width: 90%; 
     10  padding: 2px; 
     11  margin: 0.1em 0em; 
     12
     13 
     14.newhack textarea { 
     15  width: 90%; 
     16  padding: 4px; 
     17  margin: 0.1em 0em; 
    1018} 
    1119 
     
    1927} 
    2028 
    21 .info { 
    22   opacity: 0.0; 
    23 
    24  
    25 .newhack div.info { 
    26   width: 40ex; 
     29/* TODO pointer.gif XXX XXX XXX */ 
     30.newhack .hint { 
    2731  font-size: xx-small; 
    28   float: right; 
    29   clear: both; 
     32  position: absolute; 
     33  right: -42ex; 
     34  width: 200px; 
    3035  background: #b0ffb0; 
    31   border: 2px solid #40e040; 
     36  border: 1px solid #40e040; 
    3237  margin: 0em; 
    3338  padding: 0.2em 1em; 
    3439} 
     40 
     41.newhack div.error { 
     42  background: #ffb0b0; 
     43  border: 1px solid #e04040; 
     44  width: 90%; 
     45  text-align: center; 
     46  margin: 0.2em 0em; 
     47  padding: 2px; 
     48  font-weight: bold; 
     49} 
     50 
     51.newhack .hint .hint-pointer { 
     52    position: absolute; 
     53    left: -10px; 
     54    top: 5px; 
     55    width: 10px; 
     56    height: 19px; 
     57    background: url(../pointer.gif) left top no-repeat; 
     58} 
  • trachacksplugin/0.11/trachacks/htdocs/js/trachacks.js

    r2983 r2993  
    11$(document).ready(function() { 
    2   // Move the label for each field into the info block. 
    3   $('.info').each(function() { 
    4   var info = this; 
    5   var fieldid = info.id.slice(0, -4); 
     2  // Move the label for each field into the hint block. 
     3  $('.hint').each(function() { 
     4  var hint = this; 
     5  var fieldid = hint.id.slice(0, -4); 
    66 
    7     $('label[@for="' + fieldid + '"]').each(function() { 
    8     var title = $(this).text(); 
    9  
    10       $(info).prepend('<strong>' + title + '</strong>'); 
    11     }); 
    127  }); 
    138 
    14   // Fade all the info blocks out then focus the #name field 
    15   $('.info').fadeTo('fast', 0.4, function() { 
     9  // Handle focus/blur of input fields 
     10  $.fn.handleInfo = function(hint, label) { 
     11    return this.each(function() { 
     12    var hintid; 
     13 
     14      if (hint == undefined) 
     15        hintid = '#' + this.id + 'hint'; 
     16      else 
     17        hintid = hint; 
     18 
     19      hintid = $(hintid); 
     20 
     21      $(hintid).hide(); 
     22 
     23      $(this).focus(function() { hintid.show(); }); 
     24      $(this).blur(function() { hintid.hide(); }); 
     25 
     26      if (hintid.attr('copied_label') == undefined) { 
     27      var title = label; 
     28 
     29        hintid.attr('copied_label', true); 
     30        if (title == undefined) { 
     31          $('label[@for="' + this.id + '"]').each(function() { 
     32            title = $(this).text(); 
     33          }); 
     34        } 
     35        hintid.prepend('<strong>' + title + '</strong>' + '<span class="hint-pointer">&nbsp;</span>'); 
     36      } 
     37    }); 
     38  } 
     39 
     40  $('#name, #title, #description, #installation').handleInfo(); 
     41 
     42  $('input[@name="type"]').handleInfo('#typehint', 'Type'); 
     43  $('input[@name="release"]').handleInfo('#releasehint', 'Compatibility'); 
     44 
     45  if (!$('input[@class="error"]:first').focus().size()) { 
    1646    $('#name').focus(); 
    17   }); 
    18  
    19  
    20   // Handle focus/blur of input fields 
    21   $('input, textarea').focus(function() { 
    22   var id = '#' + this.id + 'info'; 
    23  
    24     $(id).fadeTo('slow', 1.0); 
    25   }); 
    26   $('input, textarea').blur(function() { 
    27     $('#' + this.id + 'info').fadeTo('slow', 0.4); 
    28   }); 
     47  } 
    2948}); 
  • trachacksplugin/0.11/trachacks/__init__.py

    r172 r2993  
    1 from trachacks import * 
  • trachacksplugin/0.11/trachacks/web_ui.py

    r2983 r2993  
    1010from trac.core import * 
    1111from trac.config import * 
     12from trac.perm import IPermissionRequestor 
     13from trac.web.chrome import Chrome 
    1214from acct_mgr.htfile import HtPasswdStore 
    1315from acct_mgr.api import IPasswordStore 
    1416from trac.wiki.model import WikiPage 
    1517from trac.util.compat import sorted 
    16 from trac.web.api import IRequestHandler 
     18from trac.web.api import IRequestHandler, ITemplateStreamFilter 
    1719from trac.web.chrome import ITemplateProvider, INavigationContributor, \ 
    1820                            add_stylesheet, add_script, add_ctxtnav 
     
    2325from tracvote import VoteSystem 
    2426from genshi.builder import tag as builder 
     27from genshi.filters.transform import Transformer 
     28from trachacks.validate import * 
    2529 
    2630 
     
    4650class TracHacksHandler(Component): 
    4751    """Trac-Hacks request handler.""" 
    48     implements(INavigationContributor, IRequestHandler, ITemplateProvider) 
     52    implements(INavigationContributor, IRequestHandler, ITemplateProvider, 
     53               IPermissionRequestor, ITemplateStreamFilter) 
    4954 
    5055    limit = IntOption('trachacks', 'limit', 25, 
     
    5358    path_match = re.compile(r'/hacks/?(new|cloud|list)?') 
    5459    title_extract = re.compile(r'=\s+([^=]*)=', re.MULTILINE | re.UNICODE) 
    55     page_split = re.compile(r'(\w+)', re.I | re.UNICODE) 
     60 
     61    def __init__(self): 
     62        # Validate form 
     63        form = Form('content') 
     64        form.add('name', Pattern(r'[A-Z][A-Za-z0-9]+(?:[A-Z][A-Za-z0-9]+)*'), 
     65                 'Name must be in CamelCase.') 
     66        form.add('title', MinLength(8), 
     67                 'Please write a few words for the description.') 
     68        form.add('description', MinLength(16), 
     69                 'Please write at least a sentence or two for the description.') 
     70        form.add('release', MinLength(1), 'At least one release must be checked.', 
     71                 path='//dd[@id="release"]', where='append') 
     72        self.form = form 
     73 
     74    # ITemplateStreamFilter methods 
     75 
     76    def filter_stream(self, req, method, filename, stream, data): 
     77        errors = data.get('errors') 
     78        if errors and req.path_info == '/hacks/new': 
     79            stream |= self.form.inject_errors(errors) 
     80        return stream 
    5681 
    5782    # IRequestHandler methods 
     83 
    5884    def match_request(self, req): 
    5985        return self.path_match.match(req.path_info) 
     
    101127        return self.render_cloud(req, data, hacks) 
    102128 
     129    # INavigationContributor methods 
     130    def get_active_navigation_item(self, req): 
     131        return 'hacks' 
     132 
     133    def get_navigation_items(self, req): 
     134        yield ('mainnav', 'hacks', 
     135                builder.a('Hacks', href=req.href.hacks(), accesskey='H')) 
     136 
     137    # ITemplateProvider methods 
     138    def get_templates_dirs(self): 
     139        """ 
     140        Return the absolute path of the directory containing the provided 
     141        ClearSilver templates. 
     142        """ 
     143        from pkg_resources import resource_filename 
     144        return [resource_filename(__name__, 'templates')] 
     145 
     146    def get_htdocs_dirs(self): 
     147        """Return the absolute path of a directory containing additional 
     148        static resources (such as images, style sheets, etc). 
     149        """ 
     150        from pkg_resources import resource_filename 
     151        return [('hacks', resource_filename(__name__, 'htdocs'))] 
     152 
     153    # IPermissionRequestor methods 
     154    def get_permission_actions(self): 
     155        return ['HACK_CREATE'] 
     156 
     157    # Internal methods 
    103158    def render_new(self, req, data): 
     159        req.perm.require('HACK_CREATE') 
     160 
    104161        add_script(req, 'common/js/wikitoolbar.js') 
     162 
     163        data['focus'] = 'name' 
     164 
     165        # Populate data with form submission 
     166        if req.method == 'POST' and 'create' in req.args or 'preview' in req.args: 
     167            data.update(req.args) 
     168 
     169            _, errors = self.form.validate(data) 
     170            data['errors'] = errors 
     171        else: 
     172            data['type'] = 'plugin' 
     173            data['release'] = ['0.11'] 
     174 
    105175        return 'hacks_new.html', data, None 
    106176 
     
    202272        return hacks 
    203273 
    204     # INavigationContributor methods 
    205     def get_active_navigation_item(self, req): 
    206         return 'hacks' 
    207  
    208     def get_navigation_items(self, req): 
    209         yield ('mainnav', 'hacks', 
    210                 builder.a('Hacks', href=req.href.hacks(), accesskey='H')) 
    211  
    212     # ITemplateProvider methods 
    213     def get_templates_dirs(self): 
    214         """ 
    215         Return the absolute path of the directory containing the provided 
    216         ClearSilver templates. 
    217         """ 
    218         from pkg_resources import resource_filename 
    219         return [resource_filename(__name__, 'templates')] 
    220  
    221     def get_htdocs_dirs(self): 
    222         """Return the absolute path of a directory containing additional 
    223         static resources (such as images, style sheets, etc). 
    224         """ 
    225         from pkg_resources import resource_filename 
    226         return [('hacks', resource_filename(__name__, 'htdocs'))] 
    227  
    228274 
    229275