nulib: corriger l'affichage du menu
This commit is contained in:
parent
4d2bf89e23
commit
c7286d1406
|
@ -2,7 +2,7 @@
|
|||
|
||||
__all__ = (
|
||||
'_weburl', '_ensure_session',
|
||||
'u', 'uval',
|
||||
'u', 'uval', 'uval_would_escape',
|
||||
)
|
||||
|
||||
from types import UnicodeType, StringType
|
||||
|
@ -28,11 +28,21 @@ def u(text):
|
|||
text = unicode(text, "utf-8")
|
||||
return text
|
||||
|
||||
def uval(value):
|
||||
"""Transformer value en unicode, en particulier s'il est callable ou s'il
|
||||
a une méthode render()
|
||||
def uval_would_escape(value):
|
||||
"""Tester si uval() mettrait value en échappement
|
||||
"""
|
||||
if value is None: return False
|
||||
if hasattr(value, 'render'): return False
|
||||
elif callable(value): return False
|
||||
return True
|
||||
|
||||
def uval(value, escape=True):
|
||||
"""Transformer value en unicode pour affichage.
|
||||
Si value est un objet qui a une méthode render() ou qui est callable(),
|
||||
alors utiliser le résultat comme valeur sinon utiliser web.websafe()
|
||||
"""
|
||||
if value is None: return None
|
||||
if hasattr(value, 'render'): value = value.render()
|
||||
if callable(value): value = value()
|
||||
elif callable(value): value = value()
|
||||
elif escape: value = web.websafe(value)
|
||||
return u(value)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
__all__ = (
|
||||
'load',
|
||||
'table', 'tr', 'td',
|
||||
'Menu', 'Action',
|
||||
'Menu', 'Action', 'litteral', 'p',
|
||||
'About',
|
||||
'Alert', 'set_alert',
|
||||
)
|
||||
|
@ -15,33 +15,31 @@ from ..base import isseq, seqof, Undef
|
|||
from ..ext import web
|
||||
|
||||
from . import ui
|
||||
from .api import _ensure_session, u, uval
|
||||
from .api import _ensure_session, u, uval, uval_would_escape
|
||||
from .model import Row, TableSchema
|
||||
from .ui import Action, p
|
||||
|
||||
Undef = Undef() # spécifique à ce module
|
||||
from .ui import Action, litteral, p
|
||||
|
||||
def load(prefix=None, icons=True, charset="utf-8"):
|
||||
if prefix is None: prefix = "/static/bootstrap/"
|
||||
elif not prefix.endswith("/"): prefix = prefix + "/"
|
||||
lines = [
|
||||
'<meta charset="%s" />' % charset,
|
||||
'<meta http-equiv="X-UA-Compatible" content="IE=edge" />',
|
||||
'<meta name="viewport" content="width=device-width, initial-scale=1.0" />',
|
||||
u"""<meta charset="%s" />""" % charset,
|
||||
u"""<meta http-equiv="X-UA-Compatible" content="IE=edge" />""",
|
||||
u"""<meta name="viewport" content="width=device-width, initial-scale=1.0" />""",
|
||||
]
|
||||
if icons:
|
||||
lines.extend([
|
||||
'<link rel="shortcut" href="%sfavicon.ico" />' % prefix,
|
||||
'<link rel="icon apple-touch-icon" href="%sicon.png" />' % prefix,
|
||||
u"""<link rel="shortcut" href="%sfavicon.ico" />""" % prefix,
|
||||
u"""<link rel="icon apple-touch-icon" href="%sicon.png" />""" % prefix,
|
||||
])
|
||||
lines.extend([
|
||||
ui.css("%sbootstrap/css/bootstrap.min.css" % prefix),
|
||||
ui.css("%sbslocal.css" % prefix),
|
||||
ui.js("%sjquery.min.js" % prefix),
|
||||
ui.js("%sbootstrap/js/bootstrap.min.js" % prefix),
|
||||
ui.css(u"%sbootstrap/css/bootstrap.min.css" % prefix),
|
||||
ui.css(u"%sbslocal.css" % prefix),
|
||||
ui.js(u"%sjquery.min.js" % prefix),
|
||||
ui.js(u"%sbootstrap/js/bootstrap.min.js" % prefix),
|
||||
u"<!--[if lt IE 9]>",
|
||||
ui.js("%shtml5shiv/html5shiv.js" % prefix),
|
||||
ui.js("%srespond/respond.min.js" % prefix),
|
||||
ui.js(u"%shtml5shiv/html5shiv.js" % prefix),
|
||||
ui.js(u"%srespond/respond.min.js" % prefix),
|
||||
u"<![endif]-->",
|
||||
])
|
||||
return u"\n".join(lines)
|
||||
|
@ -63,7 +61,7 @@ def trd_joinc(css):
|
|||
classes = []
|
||||
for css in seqof(css):
|
||||
classes.append(TRD_CLASS_MAP.get(css, css))
|
||||
css = ' '.join(classes)
|
||||
css = " ".join(classes)
|
||||
return css
|
||||
|
||||
def td(value, schema=None, css=Undef, tag=Undef, **kw):
|
||||
|
@ -72,9 +70,9 @@ def td(value, schema=None, css=Undef, tag=Undef, **kw):
|
|||
if schema:
|
||||
value = schema.html(value)
|
||||
if css is Undef: css = schema.css(value)
|
||||
if tag is Undef: tag = 'td'
|
||||
if tag is Undef: tag = "td"
|
||||
css = trd_joinc(css)
|
||||
return u'<%s%s>%s</%s>' % (tag, ui.classif(css), value, tag)
|
||||
return u"<%s%s>%s</%s>" % (tag, ui.classif(css), value, tag)
|
||||
|
||||
def tr(values, schema=None, css=Undef, celltag=Undef, **kw):
|
||||
css = Undef.sa(css, kw, 'c')
|
||||
|
@ -84,7 +82,7 @@ def tr(values, schema=None, css=Undef, celltag=Undef, **kw):
|
|||
tdschema = lambda value, index: schema.cell_schema(value, index)
|
||||
css = trd_joinc(css)
|
||||
lines = []
|
||||
lines.append(u'<tr%s>' % ui.classif(css))
|
||||
lines.append(u"<tr%s>" % ui.classif(css))
|
||||
index = 0
|
||||
if isinstance(values, dict) or isinstance(values, Row):
|
||||
if schema is not None:
|
||||
|
@ -100,8 +98,8 @@ def tr(values, schema=None, css=Undef, celltag=Undef, **kw):
|
|||
for value in values:
|
||||
lines.append(td(value, tdschema(value, index), tag=celltag))
|
||||
index += 1
|
||||
lines.append(u'</tr>')
|
||||
return u'\n'.join(lines)
|
||||
lines.append(u"</tr>")
|
||||
return u"\n".join(lines)
|
||||
|
||||
TABLE_CLASS_MAP = dict(
|
||||
auto="table-auto", a="table-auto",
|
||||
|
@ -114,10 +112,10 @@ def table_joinc(css):
|
|||
if css is Undef: css = None
|
||||
if type(css) is StringType:
|
||||
css = css.split(",")
|
||||
classes = ['table']
|
||||
classes = ["table"]
|
||||
for css in seqof(css):
|
||||
classes.append(TABLE_CLASS_MAP.get(css, css))
|
||||
css = ' '.join(classes)
|
||||
css = " ".join(classes)
|
||||
return css
|
||||
|
||||
def table(rows, schema=None, css=Undef, **kw):
|
||||
|
@ -127,17 +125,17 @@ def table(rows, schema=None, css=Undef, **kw):
|
|||
if css is Undef: css = schema.css(rows)
|
||||
css = table_joinc(css)
|
||||
lines = []
|
||||
lines.append(u'<table%s>' % ui.classif(css))
|
||||
lines.append(u'<thead>')
|
||||
lines.append(tr(schema.headers(), schema.header_row_schema(), celltag='th'))
|
||||
lines.append(u'</thead><tbody>')
|
||||
lines.append(u"<table%s>" % ui.classif(css))
|
||||
lines.append(u"<thead>")
|
||||
lines.append(tr(schema.headers(), schema.header_row_schema(), celltag="th"))
|
||||
lines.append(u"</thead><tbody>")
|
||||
index = 0
|
||||
for row in rows:
|
||||
lines.append(tr(row, schema.body_row_schema(index)))
|
||||
index += 1
|
||||
lines.append(u'</tbody>')
|
||||
lines.append(u'</table>')
|
||||
return u'\n'.join(lines)
|
||||
lines.append(u"</tbody>")
|
||||
lines.append(u"</table>")
|
||||
return u"\n".join(lines)
|
||||
|
||||
################################################################################
|
||||
# Menu
|
||||
|
@ -150,13 +148,13 @@ NAVBAR_TYPE_MAP = {
|
|||
class Menu(ui.Menu):
|
||||
"""Un menu"""
|
||||
|
||||
def __init__(self, title, mitems=None, profiles=None,
|
||||
id=None, in_profiles=None, css=None,
|
||||
navbar_type=None, container_fluid=None, **kw):
|
||||
if navbar_type is None: navbar_type = kw.pop('t', None)
|
||||
if container_fluid is None: container_fluid = kw.pop('f', None)
|
||||
super(Menu, self).__init__(title, mitems, profiles, id, in_profiles, css, **kw)
|
||||
if navbar_type is None: navbar_type = 'static'
|
||||
def __init__(self, text=None, mitems=None, profiles=None,
|
||||
id=None, in_profiles=Undef, css=Undef, title=Undef,
|
||||
navbar_type=Undef, container_fluid=Undef, **kw):
|
||||
navbar_type = Undef.sa(navbar_type, kw, 't', None)
|
||||
container_fluid = Undef.sa(container_fluid, kw, 'f', None)
|
||||
super(Menu, self).__init__(text, mitems, profiles, id, in_profiles, css, title, **kw)
|
||||
if navbar_type is None: navbar_type = "static"
|
||||
self.navbar_type = navbar_type
|
||||
if container_fluid is None: container_fluid = True
|
||||
self.container_fluid = container_fluid
|
||||
|
@ -169,12 +167,12 @@ class Menu(ui.Menu):
|
|||
elif mitem.in_profiles is not None:
|
||||
if sel_profile not in mitem.in_profiles:
|
||||
return []
|
||||
active = ' class="active"' if mitem.active else ''
|
||||
url = web.websafe(mitem.url)
|
||||
css = ui.classif(mitem.css)
|
||||
li_class = ui.classif("active", mitem.active)
|
||||
url = uval(mitem.url)
|
||||
a_class = ui.classif(mitem.css)
|
||||
accesskey = ui.accesskeyif(mitem.accesskey)
|
||||
title = web.websafe(mitem.title)
|
||||
return [u"""<li%s><a href="%s"%s%s>%s</a></li>""" % (active, url, css, accesskey, title)]
|
||||
text = uval(mitem.text)
|
||||
return [u"""<li%s><a href="%s"%s%s>%s</a></li>""" % (li_class, url, a_class, accesskey, text)]
|
||||
|
||||
def __dropdown(self, menu, profile):
|
||||
children = []
|
||||
|
@ -183,11 +181,11 @@ class Menu(ui.Menu):
|
|||
if not children:
|
||||
# ne pas afficher un menu vide
|
||||
return []
|
||||
active = ' active' if menu.active else ''
|
||||
lines = [u"""<li class="dropdown%s">""" % active]
|
||||
firsturl = web.websafe(menu.mitems[0].url)
|
||||
title = web.websafe(menu.title)
|
||||
lines.append(u"""<a href="%s" class="dropdown-toggle" data-toggle="dropdown">%s <b class="caret"></b></a>""" % (firsturl, title))
|
||||
li_class = ui.addclassif("active", menu.active, "dropdown")
|
||||
lines = [u"""<li class="%s">""" % li_class]
|
||||
firsturl = uval(menu.mitems[0].url)
|
||||
text = uval(menu.text)
|
||||
lines.append(u"""<a href="%s" class="dropdown-toggle" data-toggle="dropdown">%s <b class="caret"></b></a>""" % (firsturl, text))
|
||||
lines.append(u"""<ul class="dropdown-menu">""")
|
||||
lines.extend(children)
|
||||
lines.append(u"""</ul>""")
|
||||
|
@ -212,10 +210,10 @@ class Menu(ui.Menu):
|
|||
if self.__is_navbar_fixed(navbar_type):
|
||||
lines.append(u"""<style type="text/css">body { margin-top: 50px; }</style>""")
|
||||
css = u''
|
||||
css = ui.addclassif('navbar-fixed-top', self.__is_navbar_fixed(navbar_type), css)
|
||||
css = ui.addclassif('navbar-static-top', self.__is_navbar_static(navbar_type), css)
|
||||
css = ui.addclassif("navbar-fixed-top", self.__is_navbar_fixed(navbar_type), css)
|
||||
css = ui.addclassif("navbar-static-top", self.__is_navbar_static(navbar_type), css)
|
||||
if self.profiles is not None and self.sel_profile is not None:
|
||||
css = ui.addclassif('%s-profile' % self.sel_profile, None, css)
|
||||
css = ui.addclassif("%s-profile" % self.sel_profile, None, css)
|
||||
css = ui.addclassif(self.css, None, css)
|
||||
lines.append(u"""<div class="navbar navbar-default%s" role="navigation">""" % css)
|
||||
lines.append(u"""<div class="container%s">
|
||||
|
@ -225,10 +223,16 @@ class Menu(ui.Menu):
|
|||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
</div>
|
||||
</button>""" % (u'-fluid' if container_fluid else ''))
|
||||
text = self.text
|
||||
if text is not None:
|
||||
if uval_would_escape(text):
|
||||
lines.append(u"""<span class="navbar-brand">%s</span>""" % uval(text))
|
||||
else:
|
||||
lines.append(uval(text))
|
||||
lines.append(u"""</div>
|
||||
<div class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav">""" % ('-fluid' if container_fluid else ''))
|
||||
<ul class="nav navbar-nav">""")
|
||||
if self.profiles is not None:
|
||||
# gestion des profils
|
||||
lines.append(u"""<li class="navbar-text"><form class="form-inline" method="get">""")
|
||||
|
@ -239,7 +243,7 @@ class Menu(ui.Menu):
|
|||
and profile in selection.in_profiles:
|
||||
lines.append(u"""<button class="btn btn-xs btn-primary active" type="submit" formaction="%s" formmethod="%s"><span>%s</span></button>""" % (selection.url, selection.method, profile))
|
||||
continue
|
||||
profile_ref = '%s_ref' % profile
|
||||
profile_ref = "%s_ref" % profile
|
||||
profile_mitem = None
|
||||
if profile_ref in selection:
|
||||
profile_mitem = self.get_mitem(selection[profile_ref])
|
||||
|
@ -254,9 +258,8 @@ class Menu(ui.Menu):
|
|||
else:
|
||||
lines.extend(self.__mitem(mitem, self.sel_profile))
|
||||
lines.append(u"""</ul>""")
|
||||
title = self.title
|
||||
if title is not None:
|
||||
title = web.websafe(title)
|
||||
if selection is not None:
|
||||
title = uval(selection.title)
|
||||
lines.append(u"""<p class="navbar-text"><b><em>%s</em></b></p>""" % title)
|
||||
lines.append(u"""</div>
|
||||
</div>
|
||||
|
@ -312,7 +315,7 @@ jQuery.noConflict()(function($) {
|
|||
if onShowDetails is not None:
|
||||
if callable(onShowDetails): onShowDetails = onShowDetails()
|
||||
lines.append(onShowDetails)
|
||||
lines.append("""\
|
||||
lines.append(u"""\
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
@ -432,8 +435,8 @@ class Alert(object):
|
|||
if callable(action): action = action()
|
||||
if isinstance(action, Action):
|
||||
url = web.websafe(action.url)
|
||||
title = web.websafe(action.title or u"Cliquez ici pour continuer")
|
||||
action = u"""<p><a href="%s" accesskey="r">%s</a></p>""" % (url, title)
|
||||
text = web.websafe(action.text or u"Cliquez ici pour continuer")
|
||||
action = u"""<p><a href="%s" accesskey="r">%s</a></p>""" % (url, text)
|
||||
lines.append(p(action))
|
||||
if exc_info is not None:
|
||||
lines.append(u"""<div class="small" style="margin-top: 3em; color: gray;">Pour information, le message d'erreur technique est """)
|
||||
|
|
|
@ -5,14 +5,15 @@ __all__ = (
|
|||
'accesskeyif',
|
||||
'classif', 'addclassif',
|
||||
'favicon', 'css', 'js', 'jscss',
|
||||
'p',
|
||||
'litteral', 'p',
|
||||
'Action', 'Menu',
|
||||
)
|
||||
|
||||
import urlparse, urllib
|
||||
|
||||
from nulib.base import odict, isseq, seqof
|
||||
from nulib.web import web
|
||||
from ..base import Undef, odict, isseq, seqof
|
||||
from ..ext import web
|
||||
|
||||
from .api import u
|
||||
|
||||
def checked(b):
|
||||
|
@ -57,8 +58,17 @@ def jscss(href):
|
|||
csshref = href
|
||||
return u"%s\n%s" % (js(jshref), css(csshref))
|
||||
|
||||
class litteral(object):
|
||||
"""Objet proxy vers un texte littéral.
|
||||
à utiliser avec uval() pour ne pas mettre en échappement la valeur
|
||||
"""
|
||||
def __init__(self, text): self.text = text
|
||||
def __call__(self): return self.text
|
||||
__unicode__ = __call__
|
||||
|
||||
def p(text):
|
||||
"""Si text commence par un tag, le laisser tel quel, sinon le mettre entre <p> et </p>
|
||||
text doit avoir le cas échéant déjà été traité avec uval
|
||||
"""
|
||||
text = u(text)
|
||||
if text[0:1] == '<': return text
|
||||
|
@ -71,7 +81,8 @@ class Action(odict):
|
|||
"""Un lien ou une action d'un formulaire
|
||||
|
||||
url: cible de l'action ou du lien (avec querystring)
|
||||
title: titre du lien ou de l'élément de menu
|
||||
text: titre du lien ou de l'élément de menu
|
||||
title: description de l'action. vaut text par défaut
|
||||
id: identifiant dans un menu
|
||||
in_profiles (p): profils dans lequel l'action est valide. le premier profil
|
||||
est le profil par défaut
|
||||
|
@ -112,28 +123,39 @@ class Action(odict):
|
|||
self[querykey] = query
|
||||
self[urlkey] = url
|
||||
|
||||
def __init__(self, url, title=None, id=None, in_profiles=None, css=None, accesskey=None, query=None, method=None, **kw):
|
||||
def __init__(self, url, text=Undef, id=Undef, in_profiles=Undef, css=Undef, accesskey=Undef, query=Undef, title=Undef, method=Undef, **kw):
|
||||
text = Undef.sa(text, kw, 't', None)
|
||||
id = Undef.sa(id, kw, None, None)
|
||||
in_profiles = Undef.sa(in_profiles, kw, 'p', None)
|
||||
css = Undef.sa(css, kw, 'c', None)
|
||||
accesskey = Undef.sa(accesskey, kw, 'ak', None)
|
||||
query = Undef.sa(query, kw, 'q', None)
|
||||
title = Undef.sa(title, kw, 'nt')
|
||||
method = Undef.sa(method, kw, 'm', None)
|
||||
super(Action, self).__init__(**kw)
|
||||
if isseq(url):
|
||||
if url[1:2]: title = url[1]
|
||||
if url[1:2]: text = url[1]
|
||||
if url[2:3]: id = url[2]
|
||||
if url[3:4]: in_profiles = url[3]
|
||||
if url[4:5]: css = url[4]
|
||||
if url[5:6]: accesskey = url[5]
|
||||
if url[6:7]: method = url[6]
|
||||
if url[6:7]: title = url[6]
|
||||
if url[7:8]: method = url[7]
|
||||
url = url[0] if url[0:1] else None
|
||||
if url is None: raise ValueError("url is required")
|
||||
if title is None: title = url
|
||||
if text is None: text = url
|
||||
if in_profiles is None: in_profiles = kw.pop('p', None)
|
||||
if css is None: css = kw.pop('c', None)
|
||||
if accesskey is None: accesskey = kw.pop('ak', None)
|
||||
if query is None: query = kw.pop('q', None)
|
||||
if title is Undef: title = text
|
||||
if method is None: method = 'get'
|
||||
|
||||
self.title = title
|
||||
self.text = text
|
||||
self.id = id
|
||||
self.css = css
|
||||
self.accesskey = accesskey
|
||||
self.title = title
|
||||
self.method = method
|
||||
|
||||
self.url = url
|
||||
|
@ -207,26 +229,30 @@ class Menu(odict):
|
|||
self.nextid += 1
|
||||
return nextid
|
||||
|
||||
def __init__(self, title, mitems=None, profiles=None,
|
||||
id=None, in_profiles=None, css=None, **kw):
|
||||
if in_profiles is None: in_profiles = kw.pop('p', None)
|
||||
if css is None: css = kw.pop('c', None)
|
||||
def __init__(self, text=None, mitems=None, profiles=None,
|
||||
id=None, in_profiles=Undef, css=Undef, title=Undef, **kw):
|
||||
in_profiles = Undef.sa(in_profiles, kw, 'p', None)
|
||||
css = Undef.sa(css, kw, 'c', None)
|
||||
title = Undef.sa(title, kw, 'nt')
|
||||
super(Menu, self).__init__(**kw)
|
||||
self.__dict__['nextid'] = 0
|
||||
self.__dict__['idmap'] = {}
|
||||
if title is None: title = u""
|
||||
|
||||
if text is None: text = u""
|
||||
if id is None: id = self.__nextid()
|
||||
self.title = title
|
||||
self.mitems = []
|
||||
if title is Undef: title = text
|
||||
self.text = text
|
||||
profiles = seqof(profiles, None)
|
||||
self.profiles = profiles
|
||||
self.default_profile = profiles[0] if profiles is not None else None
|
||||
self.id = str(id)
|
||||
self.in_profiles = seqof(p, None)
|
||||
self.css = css
|
||||
self.title = title
|
||||
self.sel_id = None
|
||||
self.sel_profile = None
|
||||
self.active = False
|
||||
self.mitems = []
|
||||
for mitem in seqof(mitems):
|
||||
self.add(mitem)
|
||||
|
||||
|
@ -252,8 +278,8 @@ class Menu(odict):
|
|||
else:
|
||||
mitem.active = False
|
||||
|
||||
def select(self, id, profile=None, **kw):
|
||||
if profile is None: profile = kw.pop('p', None)
|
||||
def select(self, id, profile=Undef, **kw):
|
||||
profile = Undef.sa(profile, kw, 'p', None)
|
||||
# d'abord déselectionner tout le monde
|
||||
self.reset_selection()
|
||||
# ensuite chercher le mitem à sélectionner
|
||||
|
@ -277,10 +303,10 @@ class Menu(odict):
|
|||
return True
|
||||
return False
|
||||
|
||||
def get_mitem(self, id=None, **kw):
|
||||
def get_mitem(self, id=Undef, **kw):
|
||||
"""retourner l'élément de menu correspondant à la sélection courante
|
||||
"""
|
||||
if id is None: id = self.sel_id
|
||||
id = Undef.sa(id, kw, None, self.sel_id)
|
||||
if id is None: return None
|
||||
id = str(id)
|
||||
mitem = self.idmap.get(id, None)
|
||||
|
|
Loading…
Reference in New Issue