Skip to content
This repository has been archived by the owner on Apr 17, 2023. It is now read-only.

Client side validation of Signup form #1251

Closed
wants to merge 1 commit into from

Conversation

krishnak9
Copy link

#861 This implements the client side validation to the signup page.

These are few screenshots:-

username
Submit button gets disabled when user enters an invalid entry.
email
screenshot from 2017-03-15 18 20 31
screenshot from 2017-03-15 18 21 47
screenshot from 2017-03-15 18 22 35
The submit button gets enabled after all fields are correct.
screenshot from 2017-03-15 18 28 44

//popover css used in signup page for client side validation

#signup_page .popover{
background-color: rgba(0,0,0,0.6);
Copy link
Contributor

Choose a reason for hiding this comment

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

Please use 2 spaces and spaces after the commas.

Copy link
Author

Choose a reason for hiding this comment

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

like this rgba(0, 0, 0, 0.6);

Copy link
Author

Choose a reason for hiding this comment

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

I will use the 2 spaces for Indent level :)

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes. Also, move this to a separated file. It could be a registration.scss in the pages folder since you are overriding the default popover style on the sign up page.

One question I have: is it okay to override the popover style only on this page? /cc @mssola

Copy link
Collaborator

Choose a reason for hiding this comment

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

@vitoravelino I don't think it's ok to override it. All popovers should look and feel in the same way throughout the app.

Copy link
Contributor

@vitoravelino vitoravelino Mar 16, 2017

Choose a reason for hiding this comment

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

That's what I thought. So, these styling can be removed then.

I didn't see any form validation using popover. It's using the built-in validators. I think you can use setCustomValidity method to achieve the same look and feel.

What do you think, @mssola?

}

#signup_page .popover.right > .arrow:after{
border-right-color: rgba(0,0,0,0.6);
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here.

Copy link
Author

Choose a reason for hiding this comment

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

Changing Now..:D

i.fa.fa-check
- if @admin || !@first_user_admin
| Create account
- else
| Create admin
- if @have_users
.text-center = link_to 'I already have an account. Login.', new_user_session_path, class: 'btn btn-link'
javascript:
Copy link
Contributor

Choose a reason for hiding this comment

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

Move this out of the slim file.

I suggest you to follow the same structure done on #1233. Since I was responsible for that, I can help you if you need any help.

Copy link
Author

Choose a reason for hiding this comment

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

Should I move the javascript to the Portus/app/assets/javascripts/modules/users/pages/sign-up.js ??

Copy link
Contributor

Choose a reason for hiding this comment

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

Not only move to that but also restructure the code itself to follow a more component based style.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree with @vitoravelino. Please change this to a more component based style.

= f.email_field :email, class: 'form-control input-lg first', placeholder: 'Email', required: true
= f.password_field :password, class: 'form-control input-lg', placeholder: 'Password (8 characters min.)', required: true
= f.password_field :password_confirmation, class: 'form-control input-lg last', placeholder: 'Password again', required: true
= f.text_field :username, class: 'form-control input-lg', placeholder: 'Username', required: true, autofocus: true,rel: 'popover'
Copy link
Contributor

Choose a reason for hiding this comment

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

Add a space after comma.

= f.password_field :password_confirmation, class: 'form-control input-lg last', placeholder: 'Password again', required: true
= f.text_field :username, class: 'form-control input-lg', placeholder: 'Username', required: true, autofocus: true,rel: 'popover'
= f.email_field :email, class: 'form-control input-lg first', placeholder: 'Email', required: true, rel: 'popover'
= f.password_field :password, class: 'form-control input-lg', placeholder: 'Password (8 characters min.)', required: true,rel: 'popover'
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here.

= f.text_field :username, class: 'form-control input-lg', placeholder: 'Username', required: true, autofocus: true,rel: 'popover'
= f.email_field :email, class: 'form-control input-lg first', placeholder: 'Email', required: true, rel: 'popover'
= f.password_field :password, class: 'form-control input-lg', placeholder: 'Password (8 characters min.)', required: true,rel: 'popover'
= f.password_field :password_confirmation, class: 'form-control input-lg last', placeholder: 'Password again', required: true,rel: 'popover'
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here.

Copy link
Author

Choose a reason for hiding this comment

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

So the submit button must be disabled at the starting. I will change the popover width too.

@vitoravelino
Copy link
Contributor

Popover width too thin.

Submit button disabled if form is empty since is invalid.

@vitoravelino
Copy link
Contributor

Tests are very welcome too. You can check the some of the rspec feature tests. 😄

@krishnak9
Copy link
Author

I am implementing the changes.... will push them soon...! I will also try to make travis pass.

@vitoravelino
Copy link
Contributor

Travis is already passing. That's a false negative, don't worry. It happens sometimes. 😅

@krishnak9
Copy link
Author

krishnak9 commented Mar 16, 2017

@vitoravelino I did something similar to password-form.js by making signup-form.js and then using it from signup.js in pages . But when i check for event listener on the user_password and user_password_confirmation and email, Its not showing any. Please help me in this. I am new to this component based style.

@vitoravelino
Copy link
Contributor

@krishnak9, could you push the code?

Copy link
Collaborator

@mssola mssola left a comment

Choose a reason for hiding this comment

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

Thanks for this 👏 Just address @vitoravelino's comments.

// interactions.
class UsersSignUpForm extends BaseComponent {
elements() {
this.$Password = this.$el.find(PASSWORD_FIELD);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you rename variable to lower case ? (e.g. this.$Password should be this.$password).

Copy link
Contributor

Choose a reason for hiding this comment

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

☝️

Copy link
Author

Choose a reason for hiding this comment

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

Yes . I will do that for sure .

onKeyup() {
const Password = this.$Password.val();
const PasswordConfirmation = this.$PasswordConfirmation.val();
const PasswordInvalid = !Password;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think this is needed.

mount() {
fadeIn(this.$el);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please don't remove this 😉

Copy link
Contributor

Choose a reason for hiding this comment

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

☝️

@@ -0,0 +1,12 @@
//popover css used in signup page for client side validation
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do you really have to re-style popovers ?

Copy link
Contributor

@vitoravelino vitoravelino Mar 16, 2017

Choose a reason for hiding this comment

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

Github hided my comment regarding this.

"That's what I thought. So, these styling can be removed then.

I didn't see any form validation using popover. It's using the built-in validators. I think you can use setCustomValidity method to achieve the same look and feel.

What do you think, @mssola?"

Copy link
Author

Choose a reason for hiding this comment

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

I mean its not much necessary but if we use popover for validation messages then the width of the popover must be increased. But i think i will remove this and use HTMLSelectElement.setCustomValidity() as suggested by @vitoravelino

@mssola
Copy link
Collaborator

mssola commented Mar 16, 2017

Now you can remove all that jQuery code right ?

}

events() {
this.$el.on('keyup', PASSWORD_FIELD, e => this.onKeyup(e));
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the reason nothing is happening on the focus out is because is you listening to the wrong event. You should use blur.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also rename the method event listener.

Copy link
Author

Choose a reason for hiding this comment

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

Even on the 'keyup' its not listening.

}

onKeyup() {
const Password = this.$Password.val();
Copy link
Contributor

Choose a reason for hiding this comment

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

Lowercase for variables.

Copy link
Author

Choose a reason for hiding this comment

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

I am on it :) .!

@krishnak9
Copy link
Author

Yeah I will remove all that jquery codes on the slim file, but i still ididnt get this component based style structure to work. I am still doing it. I am new to this so might take some time to learn all.

@vitoravelino
Copy link
Contributor

vitoravelino commented Mar 16, 2017

@krishnak9 Let me try to explain. There's a BaseComponent class that is a simple empty js class that tries to avoid some boilerplate of having to call common methods that repeats in every component. There's an order and purpose for those methods.

When you extend BaseComponent, the component inherits its constructor that expects a jQuery wrapped dom element. This element works like the scope of the component. Everything you need to access on DOM should be in its dom tree. We know that we could just use $(selector) and access anything but it's not recommended due lack of control and scope of the component.

Components should do as simple things as possible, if you have many responsibilities in a component, you might need to split into separated components or delegate to a service.

Regarding migrating your initial code to this structure, you can see that you repeat yourself a lot wrapping the same dom elements, right? I guess you've already figured that out and started putting all the elements that you are gonna need to access inside the elements() method.

Second, and really important, thing: events. When you need to listen to any event from any dom element (ideally below this.$el, there will be cases that you are gonna need to listen from another component or service), this is where you are gonna define the event listeners (e.g keyup, blur, focus, etc). To listen to dom elements events, we use the .on() method. I'm not sure if you are aware that

(e) => this.onKeyup(e)

is the same thing as

function (e) {
  this.onKeyUp(e);
}.bind(this)

That's just some syntax suger from ES2015.

This covers the basics of the current component based structure. With that knowledge I think you can do most of the migration. Let me know if you have any question.

@krishnak9
Copy link
Author

Finally found what was going wrong. I didn't reload my vagrant environment and thats why the changes didn't take place 😅. I also removed the popover and implemented setCustomValidity() method. Please review @vitoravelino @mssola .

const passwordInvalid = !this.$password.val() || this.$password.val().length < 8;
const passwordConfirmationInvalid = !this.$passwordConfirmation.val() ||
this.$password.val() !== this.$passwordConfirmation.val();
if (passwordConfirmationInvalid) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Add an empty line above.

Copy link
Author

Choose a reason for hiding this comment

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

before the if statement ?

Copy link
Contributor

@vitoravelino vitoravelino Mar 17, 2017

Choose a reason for hiding this comment

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

Yes. Makes code easier to read.

} else {
this.$passwordConfirmation[0].setCustomValidity('');
}
if (passwordInvalid) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here.

this.$password[0].setCustomValidity('');
this.$passwordConfirmation.attr('pattern', this.$password.val());
}
if (passwordInvalid || passwordConfirmationInvalid || emailInvalid || usernameInvalid) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here.

this.$password = this.$el.find(PASSWORD_FIELD);
this.$passwordConfirmation = this.$el.find(CONFIRMATION_PASSWORD_FIELD);
this.$username = this.$el.find(USERNAME_FIELD);
this.$userEmail = this.$el.find(EMAIL_FIELD);
Copy link
Contributor

Choose a reason for hiding this comment

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

If you are not gonna use the $element, you don't need to cache it here. So, I think you can remove both username and email elements, constants and event listeners.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oops. My bad! Forgot the disabled button. 😅

Copy link
Author

Choose a reason for hiding this comment

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

Yeah ..they are used in emailInvalid , usernameInvalid and for disabling the submit button. So no need to remove them right ?

Copy link
Contributor

Choose a reason for hiding this comment

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

No need. I just woke up. Wasn't thinking really straight yet. KKKK.

}

onfocusout() {
const usernameInvalid = !this.$username;
Copy link
Contributor

@vitoravelino vitoravelino Mar 17, 2017

Choose a reason for hiding this comment

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

I think you should do the same as you did for email, no?

Copy link
Author

Choose a reason for hiding this comment

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

Yeah ! I will do that . In that way if we use any pattern based user-name ,it will also get validated.

this.$el.on('focusout', EMAIL_FIELD, e => this.onfocusout(e));
}

onfocusout() {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would suggest to rename it to onFocusout. The idea (at least that's what I do) is to start a function/method with on only if it's an event listener/callback. That will highlight that this is probably a a function that is used as an event listener/callback related to the focusout event.

I know that in this case it wouldn't add much value because this component is very simple, but it's good to have consistency around.

Copy link
Author

Choose a reason for hiding this comment

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

Yeah..! Sure 😀.!

@krishnak9
Copy link
Author

Did the changes! , Don't know why travis is failing here. In my fork it did passed Travis (commit: changes)

@vitoravelino
Copy link
Contributor

It's passing, don't worry. As I said before, it's a false negative.

@krishnak9
Copy link
Author

Oh Cool 😀 . Any more changes !

this.$password.val() !== this.$passwordConfirmation.val();

if (passwordConfirmationInvalid) {
this.$passwordConfirmation[0].setCustomValidity('Please enter same Password as Above');
Copy link
Contributor

Choose a reason for hiding this comment

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

To lowercase Password and Above.

@mssola
Copy link
Collaborator

mssola commented Mar 17, 2017

And yeah, please add a feature test 👍

@krishnak9
Copy link
Author

To write feature tests for this, I have to execute the javascript using capybara-webkit driver, therefore I need to include this gem too. As I have to trigger the focusout event and check if the button is disabled or not if fields are wrong or Is there any other way to do this ? @vitoravelino @mssola

@mssola
Copy link
Collaborator

mssola commented Mar 18, 2017

To write feature tests for this, I have to execute the javascript using capybara-webkit driver, therefore I need to include this gem too. As I have to trigger the focusout event and check if the button is disabled or not if fields are wrong or Is there any other way to do this ? @vitoravelino @mssola

There's no need to include any new gem. Notice that whenever you want a feature test to be able to execute JS, you need to add the js: true flag. That being said, maybe you can take a look at the spec/support directory, which includes some helper methods. In that directory, I think that the wait_for_events.rb and the helpers.rb files can be of help (and don't be afraid to extend them if you think it's necessary 😉).

@krishnak9
Copy link
Author

Sorry I got busy with the hectic college schedule. For feature test I have done something like this

scenario "Submit Button gets disabled when any field is filled wrong", js: true do
fill_in "user_username", with: user.username
fill_in "user_email", with: "gibberish"
fill_in "user_password", with: "12341234"
fill_in "user_password_confirmation", with: "532"
page.execute_script "$('#user_username').trigger('focusout')"
expect(page).to have_button('submit_btn', disabled: true)

end
but there was an error saying

Capybara::ElementNotFound:
Unable to find field "user_username"

I can't find out what caused this error. @vitoravelino @mssola

@mssola
Copy link
Collaborator

mssola commented Mar 23, 2017

@krishnak9 iirc, that only worked (as in the other tests of this same file) when js: true was not specified. When JS is needed, you have to take another approach (I still don't fully know why 😅). Check other tests with js: true on how this can be accomplished. If I recall correctly, it could work with something like this:

fill_in "Username", with: user.username

or with something like this (I don't recall if this was the exact way of doing it though):

find("#user_username").fill user.username

You can take a look at the spec/features/namespaces_spec.rb file to check some tests using js: true. Moreover, I'd recommend adding a wait method before this: expect(page).to have_button('submit_btn', disabled: true). Otherwise this might become a flaky test I fear 😃

@krishnak9
Copy link
Author

Oh okay 😃 !. I will try to find out the approach for this . Thanks..! I will check by using this find("#user_username").fill user.username . I think this may work.

@krishnak9
Copy link
Author

krishnak9 commented Mar 27, 2017

Finally found a way to work through 😅. It's related with the page path, I had to define the path explicitly. Added the feature test for Client side validation. @mssola @vitoravelino Please review. 😃

page.execute_script "$('#user_username').trigger('focusout')"
wait_for_effect_on("#user_username")
expect(page).to have_button("submit_btn", disabled: true)

Copy link
Contributor

Choose a reason for hiding this comment

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

Remove empty line.

page.execute_script "$('#user_username').trigger('focusout')"
wait_for_effect_on("#user_username")
expect(page).to have_button("submit_btn", disabled: false)

Copy link
Contributor

Choose a reason for hiding this comment

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

Same here.

fill_in "user_password", with: "12341234"
fill_in "user_password_confirmation", with: "532"
page.execute_script "$('#user_username').trigger('focusout')"
wait_for_effect_on("#user_username")
Copy link
Contributor

Choose a reason for hiding this comment

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

Did you test without this? Just curious.

Copy link
Author

@krishnak9 krishnak9 Mar 27, 2017

Choose a reason for hiding this comment

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

No I didn't test without this. I just want to make sure that focusout get's completed before the check for disabled submit button.

Copy link
Author

@krishnak9 krishnak9 Mar 27, 2017

Choose a reason for hiding this comment

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

I did it as @mssola mentioned about test might become flaky 😅

You can take a look at the spec/features/namespaces_spec.rb file to check some tests using js: true. Moreover, I'd recommend adding a wait method before this: expect(page).to have_button('submit_btn', disabled: true). Otherwise this might become a flaky test I fear 😃

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe I'd wait for the effect on the submit button, since this is the element that will change its state.

@krishnak9
Copy link
Author

What more should be done here . Or is this concluded. Please review @mssola @vitoravelino .

@mssola
Copy link
Collaborator

mssola commented Mar 29, 2017

@krishnak9 fix my nitpick, and then squash your commits into a single one. Then I'd say it will be good to be merged. Thanks 👏

@krishnak9 krishnak9 force-pushed the Validation branch 3 times, most recently from 05617a2 to 5f90a00 Compare March 29, 2017 21:15
@mssola
Copy link
Collaborator

mssola commented Mar 30, 2017

Just one final thing, could you sign off your commit as specified in the CONTRIBUTING.md file ? If you have your author & email in your git config, it should be a matter of just using the -s flag in the commit command.

Thanks!

Signed-off-by: Krishna Kumar <krishnaklooper@gmail.com>
Copy link
Collaborator

@mssola mssola left a comment

Choose a reason for hiding this comment

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

A couple of considerations before merging (sorry for the trouble...):

  1. The submit button should be disabled when loading the page (this is as simple as adding the disabled="true" element in the HTML code.
  2. This should be done on key press maybe instead of on focus out (maybe with some timer so we don't hit this code a gazillion times). This is because on a usual workflow, when I type the password confirmation in the end, I should see have the button enabled again without me focusing out the element.

What do you think ?

@krishnak9
Copy link
Author

Oh okay. I will disable the button at the starting. I see the problem here. Is it okay that if I add a new keyup method which is called only on the confirmation password field and keeping others with focusout only . With this as soon the password matches, the submit button gets enabled. And having a variable set true if all the credentials before the password matching are correct . By checking this variable and the password matching on each keyup inside the keyup method. I think this will solve the problem and also it will call the keyup method only "length of the password number of times". What are your views on this @mssola

@mssola
Copy link
Collaborator

mssola commented Mar 30, 2017

I will disable the button at the starting

You can even write a very simple feature test that checks that when visiting the signup page this element is disabled.

Is it okay that if I add a new keyup method which is called only on the confirmation password field and keeping others with focusout only . With this as soon the password matches, the submit button gets enabled. And having a variable set true if all the credentials before the password matching are correct . By checking this variable and the password matching on each keyup inside the keyup method. I think this will solve the problem and also it will call the keyup method only "length of the password number of times". What are your views on this

It has to be applied always. I just said password confirmation as an example. Another use case:

  1. I type the email wrong but I continue anyways.
  2. Type the password and password confirmation correctly.
  3. Go back to fix my email

In this case, the button won't be enabled when I've just corrected. So, to be fair, this should be done for each input element.

@vitoravelino
Copy link
Contributor

In order to make it more readable, I guess you could have individuals event listeners for each field and store the validation result in a variable. This way you are more flexible to manage the experience of giving a specific feedback back to the user based on the field. In the end of each of these listeners you would need to a common method that will check if all the validations are valid and if yes, enable the button, otherwise disable it.

Since the project is not using a lib/fw with some feature/sugar syntax and BaseComponent is still super simple, this will be a bit verbose and feel not elegant but, remember, it's plain js.

@krishnak9
Copy link
Author

Yeah this is true. This should be done on all the input elements .I will do the changes. I will try to do with individual event listeners or the timer based approach, I will check for it. Will push the changes soon.

@krishnak9
Copy link
Author

I have implemented this using keyup, having different event handlers for each field. But when writing test, I have to enable js:true in each test where submit button is clicked as the submit button is disabled at start . There is this error showing on tests . I have attached the screenshot
screenshot from 2017-04-11 14 38 51 @mssola @vitoravelino . What caused this ??

@vitoravelino
Copy link
Contributor

@krishnak9 sorry the delay, any luck so far?

@mssola
Copy link
Collaborator

mssola commented Jul 18, 2017

@krishnak9 are you still working on this ?

@krishnak9
Copy link
Author

@mssola @vitoravelino Sorry Sir ! I got busy with my college campus placements so I m not getting time to actually work on this. If required you can close this PR. When I will resume the work, I will send a new PR.

@mssola
Copy link
Collaborator

mssola commented Jul 20, 2017

Let's close this then. It can be re-opened if none else has worked on this in the meantime. Thanks for working on this 👍

@mssola mssola closed this Jul 20, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants