Ember: Integrating Google Recaptcha with ember-cp-validations - ember.js

I have a simple contact form, with validation done using ember-cp-validations https://github.com/offirgolan/ember-cp-validations and I now need to integrate the new Google Recaptcha into that.
For the rendering of the recaptcha, I am using this code - https://gist.github.com/cravindra/5beeb0098dda657433ed - which works perfectly.
However, I don't know how to deal with the verification process to allow the form to be submitted/prevented if the challenge is correct/incorrect or not provided
Here is a truncated version of my contact-form component
import Ember from 'ember';
import Validations from './cp-validations/contact-form';
import config from '../config/environment';
export default Ember.Component.extend(Validations,{
data:{},
nameMessage:null,
init() {
this._super(...arguments);
this.set('data',{});
},
actions:{
submitForm() {
this.validate().then(({model,validations}) => {
if (validations.get('isValid')) {
// submit form
}
else {
if(model.get('validations.attrs.data.name.isInvalid')){
this.set('nameMessage',model.get('validations.attrs.data.name.messages'));
}
}
})
}
}
});
Here is the template for the component, which includes the rendering of the recpatcha using the gist above
<form {{action 'submitForm' on='submit'}}>
<div class="row">
<div class="medium-6 columns">
{{input type="text" value=data.name id="name" placeholder="Enter your name"}}
<div class="error-message">
{{nameMessage}}
</div>
</div>
</div>
<div class="row">
<div class="medium-12 columns">
{{google-recaptcha}}
</div>
</div>
<button class="button primary" type="submit">Submit</button>
</form>
The Validations import looks like this
import { validator, buildValidations } from 'ember-cp-validations';
export default buildValidations({
'data.name': {
validators: [
validator('presence',{
presence:true,
message:'Please enter your name'
})
]
},
});
Many thanks for any help!

Register captchaComplete in your google-recaptcha component and mix the answer with your validations
UPDATE
contact-form.hbs
<form {{action 'submitForm' on='submit'}}>
<div class="row">
<div class="medium-6 columns">
{{input type="text" value=data.name id="name" placeholder="Enter your name"}}
<div class="error-message">
{{nameMessage}}
</div>
</div>
</div>
<div class="row">
<div class="medium-12 columns">
{{google-recaptcha captchaComplete=validateRecatcha}}
</div>
</div>
<button class="button primary" type="submit">Submit</button>
</form>
contact-form.js
import Ember from 'ember';
import Validations from './cp-validations/contact-form';
import config from '../config/environment';
export default Ember.Component.extend(Validations,{
data:{},
nameMessage:null,
captchaValidated: false,
init() {
this._super(...arguments);
this.set('data',{});
},
actions:{
validateRecatcha(data){
//if data denotes captcha is verified set captchaValidated to true else false
},
submitForm() {
this.validate().then(({model,validations}) => {
if (validations.get('isValid') && this.get('captchaValidated')) {
// submit form
}
else {
if(model.get('validations.attrs.data.name.isInvalid')){
this.set('nameMessage',model.get('validations.attrs.data.name.messages'));
}
}
})
}
}
});

Related

Use closure actions, unless you need bubbling

I don't know what's wrong in my code.
template/components/item.hbs:
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default" {{action 'buttonClicked' item}} disabled={{unless item.isValid true}}>{{buttonLabel}}</button>
</div>
</div>
components/item.js:
import Component from '#ember/component';
export default Component.extend({
buttonLabel: 'Save',
actions: {
buttonClicked(param) {
this.sendAction('action', param);
}
}
});
Ember/library-app/app/components/item.js
8:13 error Use closure actions, unless you need bubbling ember/closure-actions
Since ember > 2.0 closure actions are the favored way to handle actions (Data Down Actions Up DDAU).
I would recommend reading this http://miguelcamba.com/blog/2016/01/24/ember-closure-actions-in-depth/
Since newer ember versions(2.18 I believe), there is a ESlint rule to point out that people should move to closure actions: https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/closure-actions.md
You could rewrite your code to:
my-button.hbs
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default" onclick={{action "buttonClicked" item}} disabled={{unless item.isValid true}}>{{buttonLabel}}</button>
</div>
</div>
my-button.js
import Component from '#ember/component';
export default Component.extend({
buttonLabel: 'Save',
actions: {
buttonClicked(param) {
this.get('onButtonClicked')(param);
}
}
});
Or you could wave your action through:
my-button.hbs
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default" onclick={{action onButtonClicked item}} disabled={{unless item.isValid true}}>{{buttonLabel}}</button>
</div>
</div>
my-button.js
import Component from '#ember/component';
export default Component.extend({
buttonLabel: 'Save'
});
actions: {
buttonClicked(param) {
this.sendAction('action', param);
}
}
Instead of the name 'action' try using some other actionName
Like
actions: {
buttonClicked(param) {
this.sendAction('onButtonClick', param);
}
}
Then use it in the parent template as
{{item onButtonClick="someActionHandledInTheParent"}}

Loading data that isnt on the route?

I have a dropdown setup on a form but how do i set it up if my data isnt in the route? I am trying to load in a list of territories on a new dealer form. I have added ember-power-select add on but I cant figure out how to load the data to the add on to display the available options. Do I need to create a service or controller?
New to ember just trying to work through the basics.
Router.map(function() {
this.route('login');
this.route('admin', function() {
this.route('territory', function() {
this.route('new');
this.route('edit', { path: '/:territory_id/edit'});
this.route('view', { path: '/:territory_id/view'});
});
this.route('dealer', function() {
this.route('view', { path: '/:dealer_id/view'});
this.route('new');
});
});
});
This is my form I am using on creating a new item.
<div class="well">
<form class="form-horizontal">
<fieldset>
<legend>New Dealer</legend>
<div class="form-group">
<label for="inputFirstName" class="col-lg-2 control-label">Name</label>
<div class="col-lg-4">
{{input type="text" value=model.dealerName class="form-control" placeholder="Dealer Name"}}
</div>
</div>
<div class="form-group">
<label for="inputFirstName" class="col-lg-2 control-label">Territory</label>
<div class="col-lg-4">
{{#power-select selected=dealer options=dealer onchange=(action "chooseDestination") as |dealer|}}
{{dealer.dealerName}}
{{/power-select}}
</div>
</div>
<div class="form-group">
<div class="col-lg-10 col-lg-offset-2">
{{#link-to 'admin.dealer' class="btn btn-default"}}Cancel{{/link-to}}
<button type="submit" class="btn btn-primary" {{action 'saveDealer' model}}>Submit</button>
</div>
</div>
</fieldset>
</form>
</div>
Just because a route is the new action for a territory or the view action for a dealer, doesn't mean that you can't load other data there.
For example, in the territory/view route, you could load all the dealers.
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
return Ember.RSVP.hash({
territory: this.store.find('territory', params.territory_id),
dealers: this.store.findAll('dealer')
})
}
});
Then in your controller and templates, you would have access to model.territory and model.dealers

How to share settings across an application?

I have a main page listing some categories / subcategories. Whenever a subcategory is clicked, the action openSubcategory is triggered:
// routes/application.js
import Ember from 'ember';
export default Ember.Route.extend({
userSelections: Ember.inject.service('user-selections'),
actions: {
openSubcategory: function(categoryId, subcategoryId) {
var userSelections = this.get('userSelections');
userSelections.set('category', categoryId);
userSelections.set('subcategory', subcategoryId);
this.transitionTo('filter-categories');
},
}
});
To pass the selections to the corresponding controller, I am using a service:
// services/user-selections.js
import Ember from 'ember';
export default Ember.Service.extend({
category: null,
subcategory: null,
init() {
this._super(...arguments);
this.set('category', null);
this.set('subcategory', null);
},
});
Which is evaluated in:
// controllers/filter-categories.js
import Ember from 'ember';
export default Ember.Controller.extend({
userSelections: Ember.inject.service('user-selections'),
init() {
this._super(...arguments);
this.get('userSelections'); // We need to get it so that we can observe it?
// We can not declare the observers, because we need to make sure userSelections is first read
this.addObserver('userSelections.category', function() {
Ember.run.once(this, 'refreshProducts');
});
this.addObserver('userSelections.subcategory', function() {
Ember.run.once(this, 'refreshProducts');
});
},
actions: {
changedCategory: function(selectedCategory) {
this.set('selectedCategory', selectedCategory);
this.get('userSelections').set('category', selectedCategory.value);
},
changedSubcategory: function(selectedSubcategory) {
this.set('selectedSubcategory', selectedSubcategory);
this.get('userSelections').set('subcategory', selectedSubcategory.value);
},
},
refreshProducts: function() {
var userSelections = this.get('userSelections'),
category = userSelections.get('category'),
subcategory = userSelections.get('subcategory');
var products = this.store.filter('product', function(product) {
var catId = parseInt(product.get('category').get('id')),
subcatId = parseInt(product.get('subcategory').get('id'));
if (category && catId !== category) {
return false;
}
if (subcategory && subcatId !== subcategory) {
return false;
}
return true;
});
this.set('model', products);
},
});
Observing the userSelections (after some hacking, as seen in the comments) works: the actions are properly triggering the refreshProducts method. But it seems the method is not triggered when coming from the application route, probably because the controllers/filter-categories is not yet initialized.
(*) According to the documentation there are lots "issues" observing services.
Observers and asynchrony
Observers and object initialization
Unconsumed Computed Properties Do Not Trigger Observers
"The injected property is lazy; the service will not be instantiated until the property is explicitly called" (link)
As a result, code needs to be written in a difficult to understand way.
Is there a better pattern to share data between routes / controllers than using a service?
EDIT
These are my templates:
// partials/categories.hbs (used on the application.hbs template)
{{#each model.categories as |category| }}
<div class="categories-list row">
<div class="container">
<h3 class="category-name centered">
<span class="bg-left"></span>
<span class="bg-center uppercase">{{category.name}}</span>
<span class="bg-right"></span></h3>
</div>
<div class="category owl-carousel">
{{#each category.subcategories as |subcategory| }}
<div class="category-item">
<a href="{{subcategory.link}}">
<div class="category-icon">
<img src="{{subcategory.image}}">
</div>
<h4 class="capitalize" {{action "openSubcategory" category.id subcategory.id}}>{{subcategory.name}}</h4>
</a>
</div>
{{/each}}
</div>
</div>
{{/each}}
And:
// filter-categories.hbs
<div class="container">
<div class="row">
<div class="col-md-12">
<h2>Our Vault</h2>
<legend>Filter products by category / subcategory</legend>
</div>
</div>
<div class="row">
<div class="col-md-12">
<form class="form-inline portfolio-form">
<div class="form-group col-md-2">
{{#power-select options=optionsCategory selected=selectedCategory onchange=(action "changedCategory") as |category|}}
{{category.text}}
{{/power-select}}
</div>
<div class="form-group col-md-2">
{{#power-select options=optionsSubcategory selected=selectedSubcategory onchange=(action "changedSubcategory") as |subcategory|}}
{{subcategory.text}}
{{/power-select}}
</div>
<div class="form-group col-md-2">
<button type="button" class="btn btn-default" {{action "clearSelections" id}}><i class="fa fa-remove"></i> Clear Filters</button>
</div>
</form>
</div>
</div>
<div class="row">
{{partial "products"}}
</div>
</div>
Is there a reason you're avoiding the use of dynamic segments? If not you can pass the category and subcategory as dynamic segments when using transitionTo('filter-categories'), an added benefit would be that this route will become linkable :)
So for eg. you should define your filter-categories route like this:
// router.js
...
this.route('filter-categories', { path: 'filter-categories/:category_id/:subcategory_id' });
...
Now in your routes/filter-categories.js router you could do:
// routes/filter-categories
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
// you can put your `refreshProducts` logic in here
// (I mean in this router not literally in this method)
// and return the products model or whatever you want
// I will just return an object with the ids for simplicity
return {
categoryId: params.category_id,
subcategoryId: params.subcategory_id
};
}
...
And now in your application route:
// routes/application.js
import Ember from 'ember';
export default Ember.Route.extend({
actions: {
openSubcategory: function(categoryId, subcategoryId) {
this.transitionTo('filter-categories', categoryId, subcategoryId);
}
}
});
edit:
If you don't always have an categoryId and subcategoryId you can use query parameters instead of dynamic segments.

On submit can't get data from checkbox selection

Hi in my apps I have a form with checkbox group, and I can't retrived selected at submit.
Here is some code
The form content from declare.handlebars:
<form class="declare">
<div class="hidden-fields" style="display:none">
{{view Ember.TextField valueBinding="declaration_type" class="form-control half" type="text"}}
</div>
<fieldset>
...
</fieldset>
<fieldset>
<div class="form-group">
<label>Type de support</label>
<p>
{{render 'publication/declaration_support_types' Sampick.supportTypes}}
</p>
</div>
...
</fieldset>
<div class="actions-bottom">
<button {{action "sendDeclaration" content}} class="button button-select"><i class="icon-download"></i> Confirm</button>
</div>
</form>
The handlebars code for the render of publication/declaration_support_types:
{#each }}
<label class="checkbox-inline">
{{input type="checkbox" name="publication_declaration_support_type" checked=isChecked}} {{ description }}
</label>
{{/each}}
Then I have the following controller for the render 'publication/declaration_support_types':
Sampick.PublicationDeclarationSupportTypesController = Ember.ArrayController.extend({
sortProperties: ['description'],
sortAscending: false,
itemController: 'publicationDeclarationSupportType',
selected: Ember.computed.filterBy('[]', 'isChecked', true),
selectedItems: Ember.computed.mapBy('selected', 'description')
});
Sampick.PublicationDeclarationSupportTypeController = Ember.ObjectController.extend({
isChecked: false,
toggle: function() {
this.toggleProperty('isChecked');
}
});
and finaly the route for the previous html
Sampick.PublicationDeclareRoute = Ember.Route.extend({
actions: {
sendDeclaration: function(content) {
var self = this;
if (content.get("prints") == 1) {
self.validateRecipient(content);
} else {
self.submitDeclaration(content);
}
}
}
});
My issue is that in my sendDeclaration action I can't get the selected checkbox from declarationSupportTypes using the selectedItems propertie define in the controller.
Thanks for your helps
Working fiddle: http://emberjs.jsbin.com/legozucega/1/
There was a typo in IndexRoute, action=>actions.

Ember template does not remove when transition to different route

I feel as if this is a very simple problem to fix I am just not aware of how to fix it. Currently I have an outlet that displays a template like this:
user.hbs:
<div id="user-profile">
<img {{bind-attr src="avatarURL"}} alt="User's Avatar" class="profilePic"/>
<h2>{{name}}</h2>
<span>{{email}}</span>
<p>{{bio}}</p>
<span>Created {{creationDate}}</span>
<button {{action "edit"}}>Edit</button>
{{outlet}}
</div>
The template to be rendered at the outlet is this:
user_edit.hbs:
<div id="user-edit">
<h3>Edit User</h3>
<div class="panel-body">
<div class="row">
<label class="edit-user-label">Choose user avatar</label>
{{input class="form-control" value=avatarUrl}}
</div>
<div class="row">
<label>User name</label>
{{input class="form-control" value=name}}
</div>
<div class="row">
<label class="edit-user-label">User email</label>
{{input class="form-control" value=email}}
</div>
<div class="row">
<label class="edit-user-label">User short bio</label>
{{textarea class="text-control" value=bio}}
<div>
<div class="row">
<button {{action "save"}}>SAVE</button>
<button {{action "cancel"}}>CANCEL</button>
</div>
</div>
</div>
When I first visit the user route, the outlet does not display because the button has not been clicked. The button is hooked to a controller which takes care of the action. The action just transitions to the route where the template is displayed at the outlet. It appears just as expected but when I click on a different user model, the outlet from the previous user is still there without everything in the <div class="panel-body"> </div>. So Ember hides the panel-body div on transition but not the user-edit div. If you need more information I will be happy to provide it.
Here are the controllers:
userController:
App.UserController = Ember.ObjectController.extend({
actions: {
edit: function() {
this.transitionToRoute('user.edit');
}
}
});
Here is the userEditController:
App.UserEditController = Ember.ObjectController.extend({
actions: {
save: function() {
var user = this.get('model');
user.save();
this.transitionToRoute('user', user);
},
cancel: function() {
var user = this.get('model');
this.transitionToRoute('user', user);
}
}
})
Hi why dont you use {{#link-to 'edit' model}} instead of action ???
you can pass model to link-to so you dont have to get model in controller and then transitionToRoute
Look at this