Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Form focus #268

Closed
wants to merge 15 commits into from
Closed
10 changes: 10 additions & 0 deletions CHANGES.txt
@@ -1,6 +1,16 @@
Next release
------------

- Changed deform.js:focusFirstInput() to perform selectable input focusing on
page load.
Forms have an optional focus_form parameter ('on'|'off')
'on': if the form contains an input with manual_focus='on', that input field
will receive focus.
'on': if the form does not contain an input with manual_focus='on', the first
input field of the form will receive focus.
'off' disables focusing for that form.
Default behaviour is 'on' (unchanged).

- Make ``dateinput`` work again by using the fixed name "date" as expected
by the pstruct schema. See https://github.com/Pylons/deform/pull/221.

Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTORS.txt
Expand Up @@ -120,3 +120,4 @@ Contributors
- Charlie Clark, 2013/09/03
- Calvin Hendryx-Parker, 2013/09/03
- Cédric Messiant, 2014/06/27
- Mike Dunne, 2015/05/08
21 changes: 20 additions & 1 deletion deform/field.py
Expand Up @@ -109,6 +109,12 @@ class Field(object):
resource_registry
The :term:`resource registry` associated with this field.

manual_focus
If the field's parent form has its ``focus_form`` argument set to
``manual``, the first field with ``manual_focus`` set to ``on``
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

manual is no longer a valid value

will receive focus on page load.
Default: ``None``

*Constructor Arguments*

``renderer``, ``counter``, ``resource_registry`` and ``appstruct`` are
Expand Down Expand Up @@ -147,7 +153,7 @@ class Field(object):

def __init__(self, schema, renderer=None, counter=None,
resource_registry=None, appstruct=colander.null,
parent=None, **kw):
parent=None, manual_focus=None, **kw):
self.counter = counter or itertools.count()
self.order = next(self.counter)
self.oid = getattr(schema, 'oid', 'deformField%s' % self.order)
Expand All @@ -162,20 +168,33 @@ def __init__(self, schema, renderer=None, counter=None,
if resource_registry is None:
resource_registry = self.default_resource_registry
self.renderer = renderer
if (manual_focus is None
or manual_focus == False
or manual_focus.lower() == 'off'):
self.manual_focus = None
elif manual_focus == True or manual_focus.lower() == 'on':
self.manual_focus = 'on'
else:
self.manual_focus = None
self.resource_registry = resource_registry
self.children = []
if parent is not None:
parent = weakref.ref(parent)
self._parent = parent
self.__dict__.update(kw)
for child in schema.children:
try:
manual_focus = getattr(child, 'manual_focus')
except:
manual_focus = None
self.children.append(
Field(
child,
renderer=renderer,
counter=self.counter,
resource_registry=resource_registry,
parent=self,
manual_focus=manual_focus,
**kw
)
)
Expand Down
14 changes: 13 additions & 1 deletion deform/form.py
Expand Up @@ -53,6 +53,14 @@ class Form(field.Field):
false value, an ``autocomplete='off'`` attribute will be added to the
form tag. Default: ``None``.

focus_form
Determines this form's input focus. If ``focus_form`` is ``on`` or
omitted, the first input of the first form on the page will receive
focus on page load. If ``focus_form`` is ``on``, the first field
with its ``manual_focus`` schema attribute set to ``on`` will receive
focus. If `focus_form`` is ``off``, no focusing will be done.
Default: ``on``.

use_ajax
If this option is ``True``, the form will use AJAX (actually
AJAH); when any submit button is clicked, the DOM node related
Expand Down Expand Up @@ -100,12 +108,16 @@ class Form(field.Field):
css_class = 'deform' # bw compat only; pass a widget to override
def __init__(self, schema, action='', method='POST', buttons=(),
formid='deform', use_ajax=False, ajax_options='{}',
autocomplete=None, **kw):
autocomplete=None, focus_form='on', **kw):
if autocomplete:
autocomplete = 'on'
elif autocomplete is not None:
autocomplete = 'off'
self.autocomplete = autocomplete
if focus_form.lower() == 'off' or focus_form == False:
self.focus_form = 'off'
else:
self.focus_form = 'on'
field.Field.__init__(self, schema, **kw)
_buttons = []
for button in buttons:
Expand Down
66 changes: 54 additions & 12 deletions deform/static/scripts/deform.js
Expand Up @@ -157,19 +157,61 @@ var deform = {
},

focusFirstInput: function (el) {
/*
* If a form's data-deform-focus-form attribute is 'off', no focusing
* will be done on that form.
*
* If a form's data-deform-focus-form attribute is 'on' AND a child
* input has its data-deform-manual-focus attribute set to 'on', that
* input will be focused.
*
* If a form's data-deform-focus-form attribute is 'on' AND no child has
* its data-deform-manual-focus attribute set to 'on', the first input
* of the form will be focused.
*/
el = el || document.body;
var input = $(el).find(':input')
.filter('[id ^= deformField]')
.filter('[type != hidden]')
.first();
if (input) {
var raw = input.get(0);
if (raw) {
if (raw.type === 'text' || raw.type === 'file' ||
raw.type == 'password' || raw.type == 'text' ||
raw.type == 'textarea') {
if (!input.hasClass("hasDatepicker")) {
input.focus();

/* Select the first form which does not have data-deform-focus-form=off */
var form = $(el).find('form').filter('[data-deform-focus-form = on]').first();

if(form) {
/* Focus on the first input with data-deform-manual-focus='on' */
var manual_input = $(form).find(':input')
.filter('[id ^= deformField]')
.filter('[type != hidden]')
.filter('[data-deform-manual-focus = "on"]')
.first();

if(manual_input.length==1) {
var raw = manual_input.get(0);
if (raw) {
if (raw.type === 'text' || raw.type === 'file' ||
raw.type == 'password' || raw.type == 'text' ||
raw.type == 'textarea') {
if (!manual_input.hasClass("hasDatepicker")) {
manual_input.focus();
return;
}
}
}
} else {
/* Focus on the first element of this form */
var input = $(form).find(':input')
.filter('[id ^= deformField]')
.filter('[type != hidden]')
.first();

if(input.length==1) {
var raw = input.get(0);
if (raw) {
if (raw.type === 'text' || raw.type === 'file' ||
raw.type == 'password' || raw.type == 'text' ||
raw.type == 'textarea') {
if (!input.hasClass("hasDatepicker")) {
input.focus();
return;
}
}
}
}
}
Expand Down
6 changes: 4 additions & 2 deletions deform/templates/autocomplete_input.pt
@@ -1,14 +1,16 @@
<span tal:define="name name|field.name;
css_class css_class|field.widget.css_class;
oid oid|field.oid;
style style|field.widget.style"
style style|field.widget.style;
manual_focus manual_focus|field.manual_focus"
tal:omit-tag="">
<input type="text"
name="${name}"
value="${cstruct}"
data-provide="typeahead"
tal:attributes="class string: form-control ${css_class or ''};
style style"
style style;
data-deform-manual-focus manual_focus"
id="${oid}"/>
<script tal:condition="field.widget.values" type="text/javascript">
deform.addCallback(
Expand Down
6 changes: 4 additions & 2 deletions deform/templates/checkbox.pt
Expand Up @@ -4,13 +4,15 @@
true_val true_val|field.widget.true_val;
css_class css_class|field.widget.css_class;
style style|field.widget.style;
oid oid|field.oid"
oid oid|field.oid;
manual_focus manual_focus|field.manual_focus|None"
type="checkbox"
name="${name}" value="${true_val}"
id="${oid}"
tal:attributes="checked cstruct == true_val;
class css_class;
style style;" />
style style;
data-deform-manual-focus manual_focus" />

<span tal:condition="hasattr(field, 'schema') and hasattr(field.schema, 'label')"
tal:replace="field.schema.label" class="checkbox-label" >
Expand Down
6 changes: 4 additions & 2 deletions deform/templates/checkbox_choice.pt
@@ -1,7 +1,8 @@
<div tal:define="css_class css_class|field.widget.css_class;
style style|field.widget.style;
oid oid|field.oid;
inline getattr(field.widget, 'inline', False)"
inline getattr(field.widget, 'inline', False);
manual_focus manual_focus|field.manual_focus"
tal:omit-tag="not inline">
${field.start_sequence()}
<div tal:repeat="choice values | field.widget.values"
Expand All @@ -12,7 +13,8 @@
tal:attributes="class inline and 'checkbox-inline'">
<input tal:attributes="checked value in cstruct;
class css_class;
style style"
style style;
data-deform-manual-focus manual_focus"
type="checkbox"
name="checkbox"
value="${value}"
Expand Down
6 changes: 4 additions & 2 deletions deform/templates/checked_input.pt
Expand Up @@ -3,15 +3,17 @@
css_class css_class|field.widget.css_class;
style style|field.widget.style;
mask mask|field.widget.mask;
mask_placeholder mask_placeholder|field.widget.mask_placeholder;"
mask_placeholder mask_placeholder|field.widget.mask_placeholder;
manual_focus manual_focus|field.manual_focus"
i18n:domain="deform"
tal:omit-tag="">
${field.start_mapping()}
<div>
<input type="text" name="${name}" value="${cstruct}"
tal:attributes="style style;
class string: form-control ${css_class or ''};
placeholder subject;"
placeholder subject;
data-deform-manual-focus manual_focus"
id="${oid}"/>
</div>
<div>
Expand Down
6 changes: 4 additions & 2 deletions deform/templates/checked_password.pt
Expand Up @@ -2,14 +2,16 @@
tal:define="oid oid|field.oid;
name name|field.name;
css_class css_class|field.widget.css_class;
style style|field.widget.style">
style style|field.widget.style;
manual_focus manual_focus|field.manual_focus">
${field.start_mapping()}
<div>
<input type="password"
name="${name}"
value="${field.widget.redisplay and cstruct or ''}"
tal:attributes="class string: form-control ${css_class or ''};
style style;"
style style;
data-deform-manual-focus manual_focus"
id="${oid}"
i18n:attributes="placeholder"
placeholder="Password"/>
Expand Down
6 changes: 4 additions & 2 deletions deform/templates/dateinput.pt
@@ -1,15 +1,17 @@
<div tal:define="css_class css_class|field.widget.css_class;
oid oid|field.oid;
style style|field.widget.style;
type_name type_name|field.widget.type_name;"
type_name type_name|field.widget.type_name;
manual_focus manual_focus|field.manual_focus"
tal:omit-tag="">
${field.start_mapping()}
<input type="${type_name}"
name="date"
value="${cstruct}"

tal:attributes="class string: ${css_class or ''} form-control hasDatepicker;
style style"
style style;
data-deform-manual-focus manual_focus"
id="${oid}"/>
${field.end_mapping()}
<script type="text/javascript">
Expand Down
6 changes: 4 additions & 2 deletions deform/templates/dateparts.pt
Expand Up @@ -3,14 +3,16 @@
tal:define="oid oid|field.oid;
name name|field.name;
css_class css_class|field.widget.css_class;
style style|field.widget.style;">
style style|field.widget.style;
manual_focus manual_focus|field.manual_focus">
${field.start_mapping()}
<div class="row">
<div class="input-group col-xs-4">
<span class="input-group-addon" i18n:translate="">Year</span>
<input type="text" name="year" value="${year}"
class="span2 form-control ${css_class or ''}"
tal:attributes="style style"
tal:attributes="style style;
data-deform-manual-focus manual_focus"
maxlength="4"
id="${oid}"/>
</div>
Expand Down
6 changes: 4 additions & 2 deletions deform/templates/datetimeinput.pt
Expand Up @@ -2,14 +2,16 @@
tal:omit-tag=""
tal:define="oid oid|field.oid;
css_class css_class|field.widget.css_class;
style style|field.widget.style;">
style style|field.widget.style;
manual_focus manual_focus|field.manual_focus">
${field.start_mapping()}
<div class="row">
<div class="input-group col-xs-6">
<span class="input-group-addon" i18n:translate="">Date</span>
<input type="text" name="date" value="${date}"
class="span2 form-control ${css_class or ''} hasDatepicker"
tal:attributes="style style"
tal:attributes="style style;
data-deform-manual-focus manual_focus"
id="${oid}-date"/>
</div>
<div class="input-group col-xs-6">
Expand Down
6 changes: 4 additions & 2 deletions deform/templates/file_upload.pt
@@ -1,7 +1,8 @@
<div class="deform-file-upload"
tal:define="oid oid|field.oid;
css_class css_class|field.widget.css_class;
style style|field.widget.style">
style style|field.widget.style;
manual_focus manual_focus|field.manual_focus">

${field.start_mapping()}

Expand All @@ -16,7 +17,8 @@

<input type="file" name="upload"
tal:attributes="class css_class;
style style;"
style style;
data-deform-manual-focus manual_focus"
id="${oid}"/>

${field.end_mapping()}
Expand Down
4 changes: 3 additions & 1 deletion deform/templates/form.pt
Expand Up @@ -3,6 +3,7 @@
css_class css_class|string:${field.widget.css_class or field.css_class or ''};
item_template item_template|field.widget.item_template;
autocomplete autocomplete|field.autocomplete;
focus_form focus_form|field.focus_form;
title title|field.title;
errormsg errormsg|field.errormsg;
description description|field.description;
Expand All @@ -15,7 +16,8 @@
tal:attributes="autocomplete autocomplete;
style style;
class css_class;
action action;"
action action;
data-deform-focus-form focus_form;"
id="${formid}"
method="${method}"
enctype="multipart/form-data"
Expand Down
6 changes: 4 additions & 2 deletions deform/templates/moneyinput.pt
Expand Up @@ -3,11 +3,13 @@
mask_options mask_options|'{}';
style style|field.widget.style;
css_class css_class|field.widget.css_class;
style style|field.widget.style|False"
style style|field.widget.style|False;
manual_focus manual_focus|field.manual_focus"
tal:omit-tag="">
<input type="text" name="${name}" value="${cstruct}"
tal:attributes="style style;
class string: form-control ${css_class or ''}"
class string: form-control ${css_class or ''};
data-deform-manual-focus manual_focus"
id="${oid}"/>
<script type="text/javascript">
deform.addCallback(
Expand Down