Focus cursor to the end of an input value (CreateView, ModelForm)
Question:
I have a form with 2 inputs. I am using CreateView and ModelForm. And {% form %}.
Problem:
I need the second input (which already has an initial value) when “tabbed” to, to put the cursor at the end of the string.
Current behavior:
When “tab” to get to the second input, it highlights the entire string.
What I have so far:
I am able to autofocus by adding this line of code to my ModelForm init:
self.fields['description'].widget.attrs['autofocus'] = 'on'
.
I was thinking that something like this:
self.fields['inc_number'].widget.attrs['onfocus'] = 'INC'[:0]
(“INC is the initial value) might solve the problem. I get no errors, but it still just highlights the entire string when I tab from the description input.
For my code, I will try to focus on just the most relevant parts.
models.py
class ITBUsage(models.Model):
""" ITB usage """
itb = models.ForeignKey(IncidentTriageBridge, related_name='itb',
on_delete=models.DO_NOTHING, verbose_name="ITB name")
description = models.CharField(max_length=100, verbose_name="Description", null=True)
inc_number = models.CharField(max_length=20, verbose_name="Incident number", null=True)
...
views.py
class StartItb(CreateView):
# class StartItb(CreateView):
""" Start a bridge by adding a new row in ITBUsage """
model = models.ITBUsage
form_class = forms.StartItbForm
template_name = "itb_tracker/start_form.html"
initial = {'inc_number': "INC"} # prefill INC for user
def get_form_kwargs(self):
""" inject the extra data """
kwargs = super().get_form_kwargs()
...
return kwargs
def form_valid(self, form):
...
return super().form_valid(form)
Above: initial = {'inc_number': "INC"}
this is currently how I get “INC” into the input
forms.py:
class StartItbForm(forms.ModelForm):
""" Start a bridge by adding a for in ITBUsage """
class Meta:
model = models.ITBUsage
fields = ['description', 'inc_number']
def __init__(self, *args, **kwargs):
...
super().__init__(*args, **kwargs)
self.fields['description'].widget.attrs['autofocus'] = 'on'
def clean(self):
...
return cleaned_data
def clean_inc_number(self):
""" Validate the INC Number is in the correct format """
inc_number = self.cleaned_data['inc_number']
if inc_number and not re.match(r'INC[0-9]+', inc_number):
raise forms.ValidationError("Format must be 'INC' followed by a number. For example: 'INC123456'")
return inc_number
def save(self, commit=True):
""" inject extra data """
...
return super().save(commit)
Above: self.fields['description'].widget.attrs['autofocus'] = 'on'
is how I make it set the cursor to the first input (description) when directed to the page.
My form html is simply using the {% form %} with some Bootstrap 4. Functionally works as expected. BUT this is why I am unable to put the onfocus logic directly on the input.
Desired behavior:
After the user finished typing in ‘description’, hit ‘Tab’, cursor goes to the end of “INC” (initial value) of the ‘inc_number’ input. (intention is so that they do not delete “INC” but rather add to the end of it.
Answers:
The widget.attrs
contains HTML attributes for your widget. You can set the onfocus
attribute and that has to be a JavaScript function.
So it should look something like this:
self.fields['inc_number'].widget.attrs['onfocus'] = 'this.setSelectionRange(..)
Although I’m not sure that works everywhere – see this post: How to set cursor position at the end of input text in Google Chrome
So based on help from “rje”, this is what I did to solve the problem.
I needed to not set the initial in the view.
But instead self.fields['inc_number'].initial = "INC"
in the form.
So that I could get the length (rather than hard coding to 3).
length = len(self.fields['inc_number'].initial)
.
with the solution line being
self.fields['inc_number'].widget.attrs['onfocus'] = 'this.setSelectionRange(length, length)'
In your FormView
, override get_form()
as below.
class ExampleFormView(FormView):
...
def get_form(self):
form = super().get_form()
# ensure autofocus cursor is at the end of the prefilled (initial) text
length = len(form.initial['name'])
form.fields['name'].widget.attrs['onfocus'] = f'this.setSelectionRange({length}, {length})'
return form
def get_initial(self):
initial = super().get_initial()
initial['name'] = 'Example initial name'
return initial
...
Also make sure autofocus
is set on the ModelForm
.
forms.py
class ExampleForm(forms.ModelForm):
class Meta:
model = ExampleModel
fields = ['name']
widgets = {
'name': forms.TextInput(attrs={
'autofocus': True,
}),
}
I have a form with 2 inputs. I am using CreateView and ModelForm. And {% form %}.
Problem:
I need the second input (which already has an initial value) when “tabbed” to, to put the cursor at the end of the string.
Current behavior:
When “tab” to get to the second input, it highlights the entire string.
What I have so far:
I am able to autofocus by adding this line of code to my ModelForm init:
self.fields['description'].widget.attrs['autofocus'] = 'on'
.
I was thinking that something like this:
self.fields['inc_number'].widget.attrs['onfocus'] = 'INC'[:0]
(“INC is the initial value) might solve the problem. I get no errors, but it still just highlights the entire string when I tab from the description input.
For my code, I will try to focus on just the most relevant parts.
models.py
class ITBUsage(models.Model):
""" ITB usage """
itb = models.ForeignKey(IncidentTriageBridge, related_name='itb',
on_delete=models.DO_NOTHING, verbose_name="ITB name")
description = models.CharField(max_length=100, verbose_name="Description", null=True)
inc_number = models.CharField(max_length=20, verbose_name="Incident number", null=True)
...
views.py
class StartItb(CreateView):
# class StartItb(CreateView):
""" Start a bridge by adding a new row in ITBUsage """
model = models.ITBUsage
form_class = forms.StartItbForm
template_name = "itb_tracker/start_form.html"
initial = {'inc_number': "INC"} # prefill INC for user
def get_form_kwargs(self):
""" inject the extra data """
kwargs = super().get_form_kwargs()
...
return kwargs
def form_valid(self, form):
...
return super().form_valid(form)
Above: initial = {'inc_number': "INC"}
this is currently how I get “INC” into the input
forms.py:
class StartItbForm(forms.ModelForm):
""" Start a bridge by adding a for in ITBUsage """
class Meta:
model = models.ITBUsage
fields = ['description', 'inc_number']
def __init__(self, *args, **kwargs):
...
super().__init__(*args, **kwargs)
self.fields['description'].widget.attrs['autofocus'] = 'on'
def clean(self):
...
return cleaned_data
def clean_inc_number(self):
""" Validate the INC Number is in the correct format """
inc_number = self.cleaned_data['inc_number']
if inc_number and not re.match(r'INC[0-9]+', inc_number):
raise forms.ValidationError("Format must be 'INC' followed by a number. For example: 'INC123456'")
return inc_number
def save(self, commit=True):
""" inject extra data """
...
return super().save(commit)
Above: self.fields['description'].widget.attrs['autofocus'] = 'on'
is how I make it set the cursor to the first input (description) when directed to the page.
My form html is simply using the {% form %} with some Bootstrap 4. Functionally works as expected. BUT this is why I am unable to put the onfocus logic directly on the input.
Desired behavior:
After the user finished typing in ‘description’, hit ‘Tab’, cursor goes to the end of “INC” (initial value) of the ‘inc_number’ input. (intention is so that they do not delete “INC” but rather add to the end of it.
The widget.attrs
contains HTML attributes for your widget. You can set the onfocus
attribute and that has to be a JavaScript function.
So it should look something like this:
self.fields['inc_number'].widget.attrs['onfocus'] = 'this.setSelectionRange(..)
Although I’m not sure that works everywhere – see this post: How to set cursor position at the end of input text in Google Chrome
So based on help from “rje”, this is what I did to solve the problem.
I needed to not set the initial in the view.
But instead self.fields['inc_number'].initial = "INC"
in the form.
So that I could get the length (rather than hard coding to 3).
length = len(self.fields['inc_number'].initial)
.
with the solution line being
self.fields['inc_number'].widget.attrs['onfocus'] = 'this.setSelectionRange(length, length)'
In your FormView
, override get_form()
as below.
class ExampleFormView(FormView):
...
def get_form(self):
form = super().get_form()
# ensure autofocus cursor is at the end of the prefilled (initial) text
length = len(form.initial['name'])
form.fields['name'].widget.attrs['onfocus'] = f'this.setSelectionRange({length}, {length})'
return form
def get_initial(self):
initial = super().get_initial()
initial['name'] = 'Example initial name'
return initial
...
Also make sure autofocus
is set on the ModelForm
.
forms.py
class ExampleForm(forms.ModelForm):
class Meta:
model = ExampleModel
fields = ['name']
widgets = {
'name': forms.TextInput(attrs={
'autofocus': True,
}),
}