Aries' Blog

Freestyle technical blog

JQuery UI 1.12 Datepicker wrong position issue (and fix)

jQuery UI 1.12 has a Datepicker component, which renders under the text input box.

In some scenario, when the page is scrolled down, the Datepicker will appear in wrong position. It’s due to the scroll distance not catered.

Fortunately, the Datepicker has a beforeShow properties which accept a function to modify the Datepicker’s height. So we can fix the issue as below

1. Create function to fix the position

function setDatepickerPos(input, inst) {
        var rect = input.getBoundingClientRect();
        // use 'setTimeout' to prevent effect overridden by other scripts
        setTimeout(function () {
            var scrollTop = $("body").scrollTop();
    	    inst.dpDiv.css({ top: rect.top + input.offsetHeight + scrollTop });
        }, 0);
    }

2. Add the function to ‘beforeShow’ properties of Datepicker

$('#MyDatepicker').datepicker({
	dateFormat: "yy-mm-dd",
	changeMonth: true,
	changeYear: true,
	defaultDate: +0,
	inline: true,
	beforeShow: function (input, inst) { setDatepickerPos(input, inst) },
});

Handle concurrent login in web app

In many web applications, there are requirements to limit concurrent login of a single account.

We assume these scenario are considered as “concurrent login”:

  • Login from 2 different devices (e.g. 1 PC & 1 mobile phone)
  • Login from 2 different browsers
  • Login from browser private mode and normal mode

Multi-tab is not considered as “concurrent login”

There are 2 approach to tackle this problem, we can either

  • Kick the 1st login when new login detected, or
  • Block the 2nd login attempt when someone is already logged in

1. Kick 1st login

New login kicked previous login, there are several benefits of this approach

  • No need to consider logout action, nowadays many users don’t bother logging out from web app when they close browser. This approach works in this case
  • No need to detect session close event, detecting tab / browser close event on client-side is difficult if not impossible. Behavior of onbeforeunload(), onunload(), Navigator.sendBeacon() is different even in major browsers
  • Best cross browser/device compatibility, the only client-side technology involved is Session Cookie (or a means to store the LoginID on client)
  • Easy to implement, no polling on client-side / daemon task on server, just simple request / response call.

Basically there are 2 parts:

  1. On Login a LoginID is generated and stored in a shared Dictionary (e.g. DB) as UserID / LoginID key-value pair, then the LoginID is encrypted and store in Session Cookie. Encryption prevent LoginID from maliciously modified on client-side
  2. On each request the LoginID is retrieved and decrypted from Cookie, then compared against Dictionary value (for that UserID). If the values match then proceed the request, if not it means the request is from previous login session that is already invalidated.

One security concern is Session Hijacking, SSL + secure flag on Cookie could prevent hacker from accessing the Cookie by network sniffing.

 

2. Block 2nd login

Block new login attempts after first login.

The key is to determine when the first login session ended:

  1. User logout action
  2. User close tab / browser
  3. Crash of tab / browser

Point 1 is easy but 2 & 3 are tricky, we can detect tab close by onbeforeunload(), onunload() but browser close is hard to detect. Also, sending request to server in onbeforeunload(), onunload() can only be done by Navigator.sendBeacon() that not all browsers support.

For browser crash, there is no ways to detect by JavaScript.

Another approach is to send Heartbeat messages to server, and logout the user once Heartbeat expired. This approach should work in most circumstances, but note should be taken in multi-tab scenario to avoid sending too many Heartbeat requests to server.

Prevent “Enter” button to submit form

When we have more than 1 submit button in a HTML form, we may want to disable the “Enter” button from triggering the default submit button. Below shows 2 ways to do it:

1. Using JavaScript

Detect “Enter” key on “Text” & “Password” input. This approach works on all major browsers (including I.E. 11)

Add below jQuery event handler in every page (put it in a global js file)

$("form.noEnterKeySubmit").find("input[type='text'], input[type='password']")
        .on('keyup keypress', function (e) {
            if (e.keyCode == 13) {
                e.preventDefault();
                return false;
            }
        });

Then mark the form with class noEnterKeySubmit

<form action="myAction" class="noEnterKeySubmit" method="post">
...

2. Using “disabled” submit button

As per HTML5 spec, the default button is the first submit button in a form. So we can effectively prevent “Enter” key form submitting by putting a disabled & hidden submit button as the first button in the form.

The advantage is no JavaScript needed, but the downside is this does not work on I.E. & Safari

<form action="...">
  <!-- Prevent implicit submission of the form -->
  <button type="submit" disabled style="display: none" aria-hidden="true"></button>

  <!-- ... -->

  <button type="submit">Submit</button>
</form>