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.

Asked By: Hailee

||

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

Answered By: rje

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)'

Answered By: Hailee

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,
            }),
        }
Answered By: M3RS
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.