Ok it seems I got this thing fixed up. The problem was in the bootstrap3.py 
module. I am including an updated version
as an attachment. Line changes: 203, 440-442.

-- 
Resources:
- http://web2py.com
- http://web2py.com/book (Documentation)
- http://github.com/web2py/web2py (Source code)
- https://code.google.com/p/web2py/issues/list (Report Issues)
--- 
You received this message because you are subscribed to the Google Groups 
"web2py-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to web2py+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Developed by Paolo Caruccio ( paolo.carucci...@gmail.com )
Released under web2py license
version 1 rev.201312222030

Description
-----------
This package applies to some standard elements of web2py the bootstrap3 theme.
Current version covers: the auth navbar, the menus and SQLFORM
Supported version of bootstrap framework: 3.0.2+
Working since Version 2.8.2-stable+timestamp.2013.12.17.16.49.17 of web2py
Tested with python 2.7.6 and the following browsers:
    - Chrome 31.0.1650.63
    - Firefox 26.0
    - Opera 12.16 Build 1860
    - IE 10
    - IE 9
    - IE 8

Package content and requirements
--------------------------------
The package includes:
    - bootstrap3.py
    - web2py-bootstrap3.css
    - web2py-bootstrap3.js
    - example of layout.html (inspired to web2py welcome application)

The bundle doesn't include the framework files. You must download them from
http://getbootstrap.com/

Moreover, bootstrap3 to work in IE8 requires the use of 'respond.js'.
In the example layout this file is linked to a CDN repository but if you
have to use the web2py app offline and your browser is IE8, please download
it from https://github.com/scottjehl/Respond and put it in
"static/js" folder of your application and follow the comment you find in the 
head section of the 'layout.html' example.
For more details on bootstrap3 browsers support you should check
http://getbootstrap.com/getting-started/#browsers

Installation and usage
----------------------
- put the python module in "modules" folder of your w2p-app (you should restart
  web2py to load the module)
- in "static" folder of your w2p-app put 'web2py-bootstrap3.css' in "css"
  sub-folder and 'web2py-bootstrap.js' in the "js" folder
- replace the 'layout.html' in views folder of your w2p-app
  with that in this package
- download and uncompress the framework files in the static folder
  of your w2p-app
- in a model add the following code lines:

    # bootstrap3 theme for web2py
    from gluon import current
    current.auth = auth
    import bootstrap3 as bs3

- code your views by using the bootstrap3 framework guidelines
 (see more at http://getbootstrap.com/getting-started/)
- call menu with "bs3.menu()" and auth_navbar with "bs3.navbar()",
  set the SQLFORM formstyle to "bs3.form()"

Auth navbar
-------------
The navbar is fully customizable: you can change the text and icon for auth
items, add dividers and add your own items, set the order of the items
to your need and taste.
You can call it in your view or controller simply with:

                            bs3.navbar()

Menu navigation
---------------
If you want a response.menu navbar use in your view or controller:

                            bs3.menu()

If instead you want a navbar with your custom items, you can pass a list (build
like response.menu) as 'menu_list' argument:

      my_nav = [['Home', True, URL('index')],['Page', False, URL('link')]]
      bs3.menu(menu_list=my_nav)

Sometimes you want a list of links displayed by tabs:

                  bs3.menu(menu_type='tabs', menu_list=my_nav)

or pills:

                  bs3.menu(menu_type='pills', menu_list=my_nav)

If you need to add bootstrap3 classes to the menu you can use the argument
'add_classes' of the function. For example:

      bs3.menu(menu_type='tabs',add_classes='justified',menu_list=my_nav)

returns a tabbed menu justified (tabs have equal widths of their parent), while

      bs3.menu('pills','nav-stacked pull-right',my_nav)

returns a pills navigation stacked and right floated.

Forms
-----
To render a SQLFORM as a bootstrap3 form you can set its formstyle argument to:

                            bs3.form()

In this case the label will be over the input:

                            label |
                            input

For a label and input on the same line you can pass 'horizontal' as
first argument:

                  bs3.form(layout='horizontal')

                         label | input

It's possible display an inline forms (the label is rendered as input's
placeholder) by passing "layout = 'inline'" to the function :

                     bs3.form('inline')

The function has a second argument for styling checkboxes and radio widgets
(default is 'inline'):

                  bs3.form(rc_mode='inline')
                 
           checkbox 1 | checkbox 2 | checkbox 3

                  bs3.form(rc_mode='stacked')

                       checkbox 1 |
                       checkbox 2 |
                       checkbox 3

for example:

                bs3.form('horizontal','stacked')

returns a form where the labels are inline with their control and 
checkboxex/radioboxes widget has inputs stacked.

To add buttons to the form in runtime, you should use the function:

                  bs3.add_button(form, value, url, _class)

The mandatory arguments of the above function are:
    - form (the form to which append the button)
    - value (the button text)
    - url (the action's url)

The last argument is optional. Set it if you need a different class
than 'btn btn-default' one.

If you need to insert dynamically into the form an extra row, you should use:
                
      bs3.build_row(id, label, control, comment, form, layout, rc_mode) 

The mandatory arguments of the above function are:
    - id (the row id)
    - label (the row label)
    - control (the row widget)
    - comment (the row comment)

'layout' and 'rc_mode' arguments are optional.

To apply the bootstrap3 formstyle to auth forms you should set:

            auth.settings.formstyle = bs3.form()

Form-horizontal bootstrap3 settings
-----------------------------------
The default settings are:

    - for the label "col-md-4"
    - for the control wrapper "col-md-8"

These settings can be changed in "bootstrap3.py" and in "web2py-bootstrap3.js"
by modifying the values of:

    - FH_CL_PREFIX
    - FH_LABEL_COLW
    - FH_CONTROL_COLW

License
-------
bootstrap3.py is released under web2py license 
(http://www.web2py.com/init/default/license), while
web2py-bootstrap3.css and web2py-bootstrap3.js are released under the MIT
license (a copy is included in the package).

"""


from gluon import *
from gluon.languages import lazyT
from numbers import Number
from gluon.tools import Recaptcha


FH_CL_PREFIX = 'col-md-'
FH_LABEL_COLW = '4'
FH_CONTROL_COLW = '8'  # 12 - FH_LABEL_COLW


def navbar():
    ''' full customizable auth navbar
    
    '''

    bar = current.auth.navbar(mode='bare')
    [li_login, li_register, li_request_reset_password, li_retrieve_username,
     li_logout, li_profile, li_change_password] = [None for n in xrange(7)]

    # text and icons for auth items in the drop-down
    ## text for dropdown toggle
    default_text = current.T('Login')
    ## login
    ico_login = 'glyphicon glyphicon-off'
    txt_login = current.T('Login')
    ## register
    ico_register = 'glyphicon glyphicon-user'
    txt_register = current.T('Register')
    ## password reset
    ico_request_reset_password = 'glyphicon glyphicon-lock'
    txt_request_reset_password = current.T('Lost password?')
    ## username retrieve
    ico_retrieve_username = 'glyphicon glyphicon-lock'
    txt_retrieve_username = current.T('Forgot username?')
    ## logout
    ico_logout = 'glyphicon glyphicon-off'
    txt_logout = current.T('Logout')
    ## profile
    ico_profile = 'glyphicon glyphicon-user'
    txt_profile = current.T('Profile')
    ## password change
    ico_change_password = 'glyphicon glyphicon-lock'
    txt_change_password = current.T('Password')

    # divider
    li_divider = LI(_class='divider')

    # not auth items (add your own)
    ## below some examples
    ico0 = 'glyphicon glyphicon-info-sign'
    txt0 = current.T('About')
    href0 = URL(current.request.controller, 'about')
    li_about = LI(A(I(_class=ico0), ' ', txt0, _href=href0, _rel='nofollow'))
    ## ----------------
    ico1 = 'glyphicon glyphicon-book'
    txt1 = current.T('Help')
    href1 = URL(current.request.controller, 'help')
    li_help = LI(A(I(_class=ico1), ' ', txt1, _href=href1, _rel='nofollow'))

    # auth items builder
    for k, v in bar.iteritems():
        if k == 'user':
            welcome_text = '%s %s' % (bar['prefix'], bar['user'])
            toggletext = default_text if v is None else welcome_text
        elif k == 'login':
            ico = ico_login
            txt = txt_login
            res = LI(A(I(_class=ico), ' ', txt, _href=v, _rel='nofollow'))
            li_login = res
        elif k == 'register':
            ico = ico_register
            txt = txt_register
            res = LI(A(I(_class=ico), ' ', txt, _href=v, _rel='nofollow'))
            li_register = res
        elif k == 'request_reset_password':
            ico = ico_request_reset_password
            txt = txt_request_reset_password
            res = LI(A(I(_class=ico), ' ', txt, _href=v, _rel='nofollow'))
            li_request_reset_password = res
        elif k == 'retrieve_username':
            ico = ico_retrieve_username
            txt = txt_retrieve_username
            res = LI(A(I(_class=ico), ' ', txt, _href=v, _rel='nofollow'))
            li_retrieve_username = res
        elif k == 'logout':
            ico = ico_logout
            txt = txt_logout
            res = LI(A(I(_class=ico), ' ', txt, _href=v, _rel='nofollow'))
            li_logout = res
        elif k == 'profile':
            ico = ico_profile
            txt = txt_profile
            res = LI(A(I(_class=ico), ' ', txt, _href=v, _rel='nofollow'))
            li_profile = res
        elif k == 'change_password':
            ico = ico_change_password
            txt = txt_change_password
            res = LI(A(I(_class=ico), ' ', txt, _href=v, _rel='nofollow'))
            li_change_password = res

    # dropdown toggle
    toggle = A(toggletext, ' ', B('', _class='caret'),
               _href='#',
               _class='dropdown-toggle',
               _rel='nofollow',
               **{'_data-toggle': 'dropdown'})

    # set the order of items in the drop-down and add dividers
    ul = [li_register,
          li_profile,
          li_request_reset_password,
          li_change_password,
          li_retrieve_username,
          li_divider,
          li_login,
          li_logout,
          li_divider,
          li_about,
          li_help
          ]

    # dropdown
    dropdown = UL(*[li for li in ul if li is not None],
                  _class='dropdown-menu', _role='menu', _id='w2p-auth-bar')

    # navbar
    navbar = LI(toggle, dropdown, _class='dropdown',
                **{'_data-w2pmenulevel': 'l0'})

    return navbar


def menu(menu_type=None, add_classes=None, menu_list=None):
    ''' possible options:
    menu type
        - navbar (default)
        - pills
        - tabs

    additional classes
        - all the bootstrap 3 classes appliable to nav (for example
          "pull-right", "nav-stacked" and so on

    menu_list is a list of lists like response.menu

    '''

    menu_class = ('nav-%s' % menu_type if menu_type in ('pills', 'tabs')
                  else 'navbar-nav')

    if add_classes and isinstance(add_classes, basestring):
        menu_class += ' %s' % add_classes
    
    current_menu = current.response.menu if not menu_list else menu_list

    menu = MENU(current_menu,
                _class='nav %s' % menu_class,
                li_class='dropdown',
                ul_class='dropdown-menu')

    return menu


def add_button(form, value, url, _class='btn btn-default'):
    ''' add buttons to the bs3 form
        This function overrides that one in gluon/html.py

    '''

    REDIRECT_JS = 'window.location="%s";return false'
    submit = form.element(_type='submit')
    submit.parent.append(
        CAT(TAG['button'](value, _class=_class, _type='button',
                          _onclick=url if url.startswith('javascript:') else
                          REDIRECT_JS % url), XML(' ')))


def build_row(id, label, control, comment, form,
              layout=None, rc_mode='inline'):
    ''' form row builder
    
    '''

    fh_label_class = '%s%s' % (FH_CL_PREFIX, FH_LABEL_COLW)
    fh_offest_class = '%soffset-%s' % (FH_CL_PREFIX, FH_LABEL_COLW)
    fh_control_class = '%s%s' % (FH_CL_PREFIX, FH_CONTROL_COLW)

    if not id.startswith('submit_record'):
        # form controls
        if isinstance(label, (basestring, lazyT)):
            clabel = LABEL(label)
            if not label and layout != 'horizontal':
                clabel = CAT('')
            label = clabel

        if control is None:
            # make the control as an empty string
            control = ''

        if isinstance(control, (basestring, lazyT)):
            # string
            control = P(control, _class='form-control-static')
        elif isinstance(control, Number):
            # number
            control = P(str(control), _class='form-control-static')
        elif isinstance(control, INPUT):
            ctrl_cls = None
            control_type = control.attributes.get('_type')
            if control_type not in ('checkbox', 'radio', 'file'):
                ctrl_cls = 'form-control'
                if isinstance(control, TEXTAREA):
                    # textarea
                    control['_rows'] = '3'
            elif control_type == 'file':
                # file input
                if layout == 'horizontal':
                    ctrl_cls = 'form-control-static'
            elif control_type in ('checkbox', 'radio'):
                # checkbox/radio input
                if form.readonly:
                    control['_disabled'] = 'disabled'
                clabel = label[0]
                label = LABEL('') if layout == 'horizontal' else CAT('')
                control = DIV(LABEL(control, clabel),
                              _class=control['_type'])
            if ctrl_cls:
                control.add_class(ctrl_cls)
        elif isinstance(control, SPAN):
            # static text
            control = P(control[0])
            control.add_class('form-control-static')
        elif isinstance(control, A):
            # link
            if layout == 'horizontal':
                label = LABEL(label)
            control.add_class('btn btn-default')
            control['_role'] = 'button'
            if layout not in ('horizontal','inline'):
                control = P(control)
        elif isinstance(control, Recaptcha):
            # print out ReCaptcha as is
            control = XML(control, sanitize=False)
        elif isinstance(control[0], UL) and control[0]['_class'] == 'w2p_list':
            # listwidget
            control = control[0]
            for c in control:
                if isinstance(c[0], INPUT):
                    c[0].add_class('form-control')
            control.add_class('list-unstyled')
        elif (isinstance(control[0], INPUT)
              and control[0]['_autocomplete'] == 'off'
              and control[-1]['_id'].startswith('_autocomplete_')):
            # autocompletewidget
            control[0].add_class('form-control')
            control = P(control, _class='w2p-autocomplete-widget')
        elif (isinstance(control, (TABLE, DIV)) and
              control.attributes.get('_class') and
             ('web2py_radiowidget' in control['_class'] or
              'web2py_checkboxeswidget' in control['_class'])):
            # radiowidget/checkboxeswidget
            labels = [l for l in control.elements('label')]
            mode = rc_mode if rc_mode == 'stacked' else 'inline'
            group = []
            for n, input in enumerate(control.elements('input')):
                itype = input.attributes.get('_type')
                if itype in ('checkbox', 'radio'):
                    if form.readonly:
                        input['_disabled'] = 'disabled'
                    if mode == 'inline':
                        group_class = '%s-%s' % (itype, mode)
                        group_element = LABEL(input,
                                              labels[n][0],
                                              _class=group_class)
                    else:
                        group_element = LI(LABEL(input,
                                                 labels[n][0]),
                                           _class=itype)
                else:
                    group_element = input
                group.append(group_element)
            control = (UL(*group, _class='rc_container list-unstyled')
                       if mode != 'inline'
                       else CAT(DIV(*group, _class='rc_container')))
        elif (isinstance(control, DIV) and
              isinstance(control[0], INPUT) and
              '_type' in control[0].attributes and
              control[0]['_type'] == 'file' and
              isinstance(control[1], SPAN) and
              isinstance(control[1][1], A)):
              # UploadWidget
            file_inp = control[0]
            file_inp['_style'] = 'display:none;'
            file_link = control[1][1]
            delete_box = False
            has_image = False
            if control[1][2] == '|':
                delete_box = True
                del_inp = control[1][3]
                del_inp['_style'] = 'opacity:0;'
                delete_lbl = control[1][4]
            if isinstance(control[-1], IMG):
                has_image = True
                image_preview = control[-1]
            if has_image:
                file_repr = IMG(_src=image_preview['_src'],
                                _alt='thumbnail',
                                _id='image-thumb',
                                _class='img-thumbnail')
            else:
                file_repr = SPAN(file_link[0])
            file_link = CAT(A(file_repr,
                              _href=file_link['_href'],
                              _class='w2p-file-preview'),
                            SPAN(current.T('no file'),
                                 _id='no-file',
                                 _style='display:none;'))
            edit_btn_class = 'btn btn-default dropdown-toggle'
            edit_btn = TAG['button'](current.T('edit'), ' ',
                                     SPAN(_class='caret'),
                                     _type='button',
                                     _class=edit_btn_class,
                                     **{'_data-toggle': 'dropdown'})
            drop_down = UL(LI(A(current.T(delete_lbl[0]),
                                _href='#',
                                _id='delete-file-option')),
                           LI(A(current.T('change'),
                                _href='#',
                                _id='change-file-option')),
                           _class='dropdown-menu',
                           _role='menu')
            edit_btn_dd = DIV(edit_btn,
                              drop_down,
                              _id='edit-btn-dd',
                              _class='btn-group')
            file_reset_btn = TAG['button'](current.T('reset'),
                                           _id='file-reset-btn',
                                           _type='button',
                                           _class='btn btn-default',
                                           _style='display:none;')
            control = CAT(DIV(file_inp,
                              file_link,
                              del_inp,
                              edit_btn_dd,
                              file_reset_btn,
                              _class='w2p-uploaded-file'))
        else:
            # widget not implemented
            print "row '%s': widget not implemented" % id

        if layout == 'horizontal':
            label_bs3_class = '%s control-label' % fh_label_class
            comment_class = 'help-block %s %s' % (fh_offest_class,
                                                  fh_control_class)
            control = DIV(control, _class=fh_control_class)
        elif layout == 'inline':
            control['_placeholder'] = label[0]
            label_bs3_class = 'sr-only'
            comment_class = 'sr-only'
        else:
            label_bs3_class = 'control-label'
            comment_class = 'help-block'

        ### label
        if label:
            label.add_class(label_bs3_class)

        ### comment
        if comment:
            comment = SPAN(comment, _class=comment_class)

        form_row = DIV(label, control, comment, _id=id, _class='form-group')
    else:  # form buttons
        if not len(control):
            # if the buttons are not wrapped in a DIV
            # (i.e. form buttons attribute) then we have to wrap them
            control = CAT(control)
        inputs = []
        first_btn = True
        for n, input in enumerate(control):
            btn_class = 'btn-primary' if first_btn else 'btn-default'
            input_type = input.attributes.get('_type')
            if isinstance(input, INPUT):
                # input tag
                if input_type in('button', 'submit', 'reset'):
                    first_btn = False
                    btn_id = input.attributes.get('_id')
                    js_click = input.attributes.get('_onclick')
                    input.add_class('bs3-form-btn btn %s' % btn_class)
                    button = TAG['button'](input['_value'],
                                           _type=input_type,
                                           _onclick=js_click,
                                           _class=input['_class'],
                                           _id=btn_id)
                elif input_type == 'image':
                    first_btn = False
                    input.add_class('bs3-form-btn btn %s' % btn_class)
                    button = input
                else:
                    # it isn't a button
                    input.add_class('form-control')
                    button = input
            elif input_type in ('button', 'submit', 'reset'):
                # 'button' tag
                first_btn = False
                input.add_class('bs3-form-btn btn %s' % btn_class)
                button = input
            elif isinstance(input, A):
                # anchor tag as a button
                first_btn = False
                input.add_class('bs3-form-btn btn %s' % btn_class)
                input['_role'] = 'button'
                button = input
            else:
                button = input
            inputs.extend([button, XML(' ')])

        buttons = (CAT(*inputs)
                   if layout != 'horizontal'
                   else DIV(CAT(*inputs),
                            _class='%s %s' % (fh_offest_class,
                                              fh_control_class)))

        form_row = DIV(buttons, _class='form-group', _id=id)

    return form_row


def form(layout=None, rc_mode='inline'):
    ''' formstyle for SQLFORM

    '''

    def style(form, fields):

        parent = CAT()
        form_group = None

        for id, label, widget, comment in fields:
            form_group = build_row(id, label, widget, comment,
                                   form, layout, rc_mode)
            parent.append(form_group)

        form_classes = ('bs3-form form-%s' % layout if layout in ('horizontal',
                                                                  'inline')
                        else 'bs3-form')

        form.add_class(form_classes)
        form['_role'] = 'form'

        return parent

    return style

Reply via email to