Bootstrap + jQuery validationEngine make custom ajax validation call onFieldSuccess to update feedback - jquery-validation-engine

I am using the jQuery Validation Engine plugin to validate my form. I am also using Bootstrap to give the user feedback (success/fail) of the given input.
Here is how I am initializing the plugin:
jquery
$.validationEngine.defaults.promptPosition = 'inline';
$.validationEngine.defaults.onFieldFailure = function (field) {
console.log('onFieldFailure called');
field.parent().removeClass('has-success').addClass('has-error');
field.nextAll('span').children().removeClass('fa-check').addClass('fa-remove');
};
$.validationEngine.defaults.onFieldSuccess = function (field) {
console.log('onFieldSuccess called');
field.parent().removeClass('has-error').addClass('has-success');
field.nextAll('span').children().removeClass('fa-remove').addClass('fa-check');
};
$form.validationEngine('attach');
I am using CodeIgniter to handle the form server-side. Everything is working great.
html/php
<div class="form-group has-feedback">
<label for="email"><i class="fa fa-asterisk"><span class="sr-only">This field is required</span></i> E-mail Address</label>
<input type="text" class="form-control" id="email" name="email"
data-validation-engine="validate[required, custom[email], ajax[email_exists]]"
data-errormessage-value-missing="This field is required"
data-errormessage="Invalid E-mail address"
value="<?php echo set_value('email'); ?>"
placeholder="your#email.com">
<span class="form-control-feedback"><i class="fa fa-remove"></i></span>
<?php echo form_error('email'); ?>
</div>
Here is my controller (how I'm returning a response):
php
public function ajax_email_exists() {
if ($this->user_model->email_exists($this->input->get('fieldValue'))) {
echo json_encode(array('email', FALSE));
} else {
echo json_encode(array('email', TRUE));
}
}
When the user blurs out of the email field, I do an ajax call email_exists which is working fine as well. Here is what that looks like. It is located in the jquery.validation-engine-en.js file as suggested in the docs.
jquery
'email_exists': {
'url': 'path-to-my-script.php',
'alertTextLoad': '<i class="fa fa-cog fa-spin"></i> Validating, please wait...',
'alertTextOk': '<i class="fa fa-check-circle"></i> E-mail address is valid',
'alertText': '<i class="fa fa-exclamation-triangle"></i> That Email-address already exists'
},
The validation itself is working great. I am getting correct response back - the problem I'm running into is I can't seem to figure out how to make the success of the ajax call to call the onFieldSuccess method. As soon as I blur out of the email field onFieldFailure is called and my input is red. When the ajax validation is complete, I am unable to get rid of the invalid style and apply my valid style. In essence, call the onFieldSuccess method to give the correct feedback.
A thought I had was maybe I need to look at using funcCall instead?
Thank you for your time & suggestions!
EDIT
I've updated my initialize method to add css classes to the elment(s). It seems i'm always getting to addFailureCssClassToField even when I am getting a success result back from the server.

Just in case anyone come across this thread, here is how I came up with a solution.
As mentioned before, all of my validation is/was working correctly. The problem was updating the feedback accordingly. I was unable to update the style(s) to reflect what was happening.
I have updated/cleaned up the plugin code itself so my line numbers are going to be off. That said, around line #1578, you will find this:
jquery
if (options.showPrompts) {
// see if we should display a green prompt
if (msg) {
methods._showPrompt(errorField, msg, "pass", true, options);
options.onFieldSuccess(errorField); // Added this line.
} else {
methods._closePrompt(errorField);
}
}
Because I was getting a success response, I needed to call onFieldSuccess. I am also passing in the field element (jQuery object) to render to.

Related

Reset Password Page Not Rendering Correctly When Accessing from Reset Email Link

Ok, so first off, this is my first post. I've searched high and low for a solution, but have found none. I have posted this first on Udemy, for the course I've taken, but no one has answered, so I'm reposting it here.
I have been trying very hard to figure out why the new-password page will not display correctly for me. The reset link works fine, and I can even reset the password on my new password page when I am sent there from the email link.
However, no matter what I do, I can't get it to display any styling. It only gives me basic html. The logic works fine, it's just the page that doesn't display correctly.
I know it isn't a path issue to the css folder either. If I simply render as another basic page without any token logic, such as replacing my index page with the new-password page, then it displays normally. I just don't know what I'm missing, or if there was some updates that I need to take into consideration.
I'm hoping someone sees this and can help me out. It's the only thing that doesn't work right, and it's very frustrating.
Just to be a little more clear, if I do something like below, and just replace or create a route, the page shows up correctly. It's the token logic I believe that is breaking the rendering, I just don't know how, since I don't get any errors.
Please let me know what code you may need to see, as I'm not sure what sections would be helpful, there are a lot of moving parts here. I will be happy to post whatever is needed.
exports.getNewPassword = (req, res, next) => {
res.render("auth/new-password", {
path: "/new-password",
pageTitle: "Update Password",
});
};
With the logic built-in and following the email reset link, the below will not render any styling, only the html.
exports.getNewPassword = (req, res) => {
const token = req.params.token;
User.findOne({
resetToken: token,
resetTokenExpiration: { $gt: Date.now() },
})
.then((user) => {
if (!user) {
req.flash(
"error",
"That reset password link has already been used."
);
return res.redirect("/");
}
let message = req.flash("error");
message.length > 0 ? (message = message[0]) : (message = null);
res.render("auth/new-password", {
path: "/new-password",
pageTitle: "New Password",
errorMessage: message,
userId: user._id.toString(),
passwordToken: token,
});
})
.catch((err) => console.log(err));
};
I am using ejs for templating as well. As I said above, if I remove all token logic and just render the page as a normal view, it works fine.
<main>
<% if (errorMessage) { %>
<div class="user-message user-message--error"><%= errorMessage %></div>
<% } %>
<form class="login-form" action="/new-password" method="POST">
<div class="form-control">
<label for="password">Password</label>
<input type="password" name="password" id="password">
</div>
<input type="hidden" name="userId" value="<%= userId %>">
<input type="hidden" name="passwordToken" value="<%= passwordToken %>">
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
<button class="btn" type="submit">Update Password</button>
</form>
</main>
Well, in case anyone stumbles across this, the answer was pretty simple, though I'm not sure why in this one instance is was a problem. However, the solution was to add a forward slash in front of my path to the css location for the update password page.
Again, not sure why it needed it, seeing as all my other css and view pages were in the same folder structures and worked fine, but it apparently solved the issue. SMDH.
<link rel="stylesheet" href="/css/login.css" />

Laravel Request does not return the modified request on validation fail

I have recently updated my controllers to use Requests to validate data before saving, originally I used $request->validate() within the controller route, but I am now at a stage where I really need to seperate it out in to a request.
Issue
Before validation takes place, I need to alter some of the parameters in the request, I found out this can be done using the prepareForValidation() method, and this works great, during validation the values in the request have been altered. My issue comes if the validation fails. I need to be able to return the request I've altered back to the view, at the moment, after redirection it appears to be using the request as it was before I ran prepareForValidation(). (i.e. returns title as 'ABCDEFG' instead of 'Changed The Title').
After some reading on other SO posts and Laravel forum posts, it looks as though I need to overwrite the FormRequest::failedValidation() method (which I've done, see code below), however I'm still struggling to find a way to pass my altered request back. I've tried to edit the failedValidation() method, I've provided details further down.
Expectation vs Reality
Expectation
User enters 'ABCDEFG' as the title and presses save.
The title is altered in the request (using prepareForValidation()) to be 'Changed The Title'.
Validation fails and the user is redirected back to the create page.
The contents of the title field is now `Changed The Title'.
Reality
User enters 'ABCDEFG' as the title and presses save.
The title is altered in the request (using prepareForValidation()) to be 'Changed The Title'.
Validation fails and the user is redirected back to the create page.
The contents of the title field shows `ABCDEFG'.
What I've tried
Passing the request over to the ValidationException class.
After digging through the code, it looks as though ValidationException allows a response to be passed over as a parameter.
protected function failedValidation(Validator $validator)
{
throw (new ValidationException($validator, $this))
->errorBag($this->errorBag)
->redirectTo($this->getRedirectUrl());
}
However this results in the error Call to undefined method Symfony\Component\HttpFoundation\HeaderBag::setCookie() in Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::addCookieToResponse.
Flashing the request to the session
My next attempt was to just flash my request to the session, this doesn't seem to work, instead of my modified request being in the session, it looks to be the request before I ran prepareForValidation().
protected function failedValidation(Validator $validator)
{
$this->flash();
throw (new ValidationException($validator))
->errorBag($this->errorBag)
->redirectTo($this->getRedirectUrl());
}
Returning a response instead of an exception
My final attempt to get this to work was to return a response using withInput() instead of the exception.
protected function failedValidation(Validator $validator)
{
return redirect($this->getRedirectUrl())
->withErrors($validator)
->withInput();
}
However it looks as though the code continues in to the BlogPostController::store() method instead of redirecting back to the view.
At this point I'm out of ideas, I just can't seem to get the altered request back to the view if validation fails!
Other Notes
I am pretty much a Laravel newbie, I have experience with a custom framework loosely based on Laravel, but this is my first CMS project.
I fully understand I may well be going down the wrong route, perhaps there's a better way of altering a request and passing it back when validation fails?
What am I trying to achieve by doing this? The main thing is the active checkbox. By default it is checked (See the blade below), if the user unchecks it and presses save, active is not passed over in the HTTP request, therefore active does not exist in the Laravel request object and when the user is returned back, the active checkbox has been checked again when it shouldn't be.
Then why have you used title in your example? I am using title in my post because I think it's easier to see what I am trying to achieve.
Any help is apreciated as I've currently burnt quite a few hours trying to solve this. 😣
Related Code
BlogPostController.php
<?php
namespace App\Http\Controllers;
use App\BlogPost;
use Illuminate\Http\Request;
use App\Http\Requests\StoreBlogPost;
class BlogPostController extends Controller
{
/**
* Run the auth middleware to make sure the user is authorised.
*/
public function __construct()
{
$this->middleware('auth');
}
/**
* Show the form for creating a new resource.
*
* #return \Illuminate\Http\Response
*/
public function create()
{
return view('admin.blog-posts.create');
}
/**
* Store a newly created resource in storage.
*
* #param StoreBlogPost $request
* #return \Illuminate\Http\Response
*/
public function store(StoreBlogPost $request)
{
$blogPost = BlogPost::create($request->all());
// Deal with the listing image upload if we have one.
foreach ($request->input('listing_image', []) as $file) {
$blogPost->addMedia(storage_path(getenv('DROPZONE_TEMP_DIRECTORY') . $file))->toMediaCollection('listing_image');
}
// Deal with the main image upload if we have one.
foreach ($request->input('main_image', []) as $file) {
$blogPost->addMedia(storage_path(getenv('DROPZONE_TEMP_DIRECTORY') . $file))->toMediaCollection('main_image');
}
return redirect()->route('blog-posts.edit', $blogPost->id)
->with('success', 'The blog post was successfully created.');
}
}
// Removed unrelated controller methods.
StoreBlogPost.php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Validation\ValidationException;
class StoreBlogPost extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'title' => 'required',
'url' => 'required',
'description' => 'required',
'content' => 'required',
];
}
/**
* Get the error messages for the defined validation rules.
*
* #return array
*/
public function messages()
{
return [
'title.required' => 'The Title is required',
'url.required' => 'The URL is required',
'description.required' => 'The Description is required',
'content.required' => 'The Content is required',
];
}
/**
* Prepare the data for validation.
*
* #return void
*/
protected function prepareForValidation()
{
$this->merge([
'active' => $this->active ?? 0,
'title' => 'Changed The Title',
]);
}
/**
* #see FormRequest
*/
protected function failedValidation(Validator $validator)
{
throw (new ValidationException($validator))
->errorBag($this->errorBag)
->redirectTo($this->getRedirectUrl());
}
}
create.blade.php
#extends('admin.layouts.app')
#section('content')
<div class="edit">
<form action="{{ route('blog-posts.store') }}" method="POST" enctype="multipart/form-data">
#method('POST')
#csrf
<div class="container-fluid">
<div class="row menu-bar">
<div class="col">
<h1>Create a new Blog Post</h1>
</div>
<div class="col text-right">
<div class="btn-group" role="group" aria-label="Basic example">
<a href="{{ route('blog-posts.index') }}" class="btn btn-return">
<i class="fas fa-fw fa-chevron-left"></i>
Back
</a>
<button type="submit" class="btn btn-save">
<i class="fas fa-fw fa-save"></i>
Save
</button>
</div>
</div>
</div>
</div>
<div class="container-fluid">
<div class="form-group row">
<label for="content" class="col-12 col-xl-2 text-xl-right col-form-label">Active</label>
<div class="col-12 col-xl-10">
<div class="custom-control custom-switch active-switch">
<input type="checkbox" name="active" value="1" id="active" class="custom-control-input" {{ old('active', '1') ? 'checked' : '' }}>
<label class="custom-control-label" for="active"></label>
</div>
</div>
</div>
<div class="form-group row">
<label for="title" class="col-12 col-xl-2 text-xl-right col-form-label required">Title</label>
<div class="col-12 col-xl-10">
<input type="text" name="title" id="title" class="form-control" value="{{ old('title', '') }}">
</div>
</div>
</div>
</form>
</div>
#endsection
I had the same issue and here is the solution I found after digging through laravel code.
It seems that Laravel creates a different object for the FormRequest, so you can do something like this.
protected function failedValidation(Validator $validator)
{
// Merge the modified inputs to the global request.
request()->merge($this->input());
parent::failedValidation($validator);
}

update customer attribute in frontend account profile form

I'm learning Shopware and I got to something I can't figure out how to solve.
I'm writing a test plugin that adds an attribute to the customer. I've added the correspondent field to the Registration form and it saves its value to the db automatically, like I read somewhere in the docs.
Now I wanted to let the attribute be editable in the account profile page, after the password field. I managed to put the input there, and even show the value from the db. But when I change the value and save, the value its not updated. I don't know if it is just a matter of getting the field name right, or do I need to override something else. Or is it just not possible? Any help on how to achieve this would be greatly appreciated.
Relevant code below:
plugin bootstrap
public function install(InstallContext $context)
{
$service = $this->container->get('shopware_attribute.crud_service');
$service->update('s_user_attributes', 'test_field', 'string');
$metaDataCache = Shopware()->Models()->getConfiguration()->getMetadataCacheImpl();
$metaDataCache->deleteAll();
Shopware()->Models()->generateAttributeModels(['s_user_attributes']);
return true;
}
register/personal_fieldset.tpl
{extends file="parent:frontend/register/personal_fieldset.tpl"}
{block name='frontend_register_personal_fieldset_password_description'}
{$smarty.block.parent}
<div class="register--test-field">
<input autocomplete="section-personal test-field"
name="register[personal][attribute][testField]"
type="text"
placeholder="Test Field"
id="testfield"
value="{$form_data.attribute.testField|escape}"
class="register--field{if $errorFlags.testField} has--error{/if}"
/>
</div>
{/block}
account/profile.tpl
{extends file="parent:frontend/account/profile.tpl"}
{block name='frontend_account_profile_profile_required_info'}
<div class="profile--test-field">
<input autocomplete="section-personal test-field"
name="profile[attribute][testfield]"
type="text"
placeholder="Test Field"
id="testfield"
value="{$sUserData.additional.user.test_field|escape}"
class="profile--field{if $errorFlags.testField} has--error{/if}"
/>
</div>
{$smarty.block.parent}
{/block}
The form type that it's used on registration isn't the same you have on profile.
If you check \Shopware\Bundle\AccountBundle\Form\Account\PersonalFormType::buildForm, you can see
$builder->add('attribute', AttributeFormType::class, [
'data_class' => CustomerAttribute::class
]);
That means the attributes are included on form and they will be persisted. That's why you can save the value on registration form.
On profile you have \Shopware\Bundle\AccountBundle\Form\Account\ProfileUpdateFormType. And here the attribute isn't added to form builder.
How to extend the ProfileUpdateFormType?
Subscribe Shopware_Form_Builder on Bootstrap (or on a specific Subscriber class)
$this->subscribeEvent('Shopware_Form_Builder', 'onFormBuild');
Create the method onFormBuild to add your logic
public function onFormBuild(\Enlight_Event_EventArgs $event) {
if ($event->getReference() !== \Shopware\Bundle\AccountBundle\Form\Account\ProfileUpdateFormType::class) {
return;
}
$builder = $event->getBuilder();
$builder->add('attribute', AttributeFormType::class, [
'data_class' => CustomerAttribute::class
]);
}
With this approach all attributes are available on your profile form.
Other possibility you have is using the 'additional' property instead of 'attribute' and then subscribe a controller event or hook a controller action to handle your custom data.

Angularjs + Laravel Stripe integration - Response goes to server and other details missing

i have an Angular Storefront app set up. I have a shopping cart functionality in place and a stripe "pay with card" button etc. pretty much looks like this:
<form action="/#/order" method="POST">
<script
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="{{ stripeApiKey }}"
data-billingAddress=true
data-shippingAddres=true
data-amount="{{ amount }}"
data-name="StoreFront Name"
data-description="Custom-Made Jewellery"
data-image="../images/www/logo.png"
data-locale="auto">
</script>
</form>
Evrything up to this point is working fine. I submit the form and stripe returns the token but the form goes to the server following the route localhost/order (without the # symbol) instead of angular's localhost/#/order.
Why is stripe forcing this redirect? In other words why isn't angular capturing this return call?
Anyways. Then I create a route with Laravel to capture this and dump to inspect the returned data like so:
Route::post('/order', function($request){
dd($request);
});
Yep, data captured by stripe-generated form is returned except amount is missing... I mean everything including stripeToken, buyer's details such as: Name, Email, Billing and Shipping address are returned BUT detail regarding the amount is missing.
Is this normal or I'm I missing something?
Lastly currency is still showing the default: Where can I change currency from say USD to GBP?
Thanks in advance
1/ I don't think Checkout is forcing the redirect, but I don't know enough about Angular to explain what's going on, sorry.
2/ Yes, this is normal. The amount passed to Checkout in the data-amount configuration option is used for display purposes only. The actual amount that is charged is the one you pass in the amount parameter in the charge creation request in your server-side code.
If you need the amount to be user-specified (for instance, if you're taking donations), you'll need to add the amount to the form. Here is a simple JSFiddle to illustrate this case: https://jsfiddle.net/ywain/g2ufa8xr/
3/ You can use the data-currency parameter to change the currency displayed in the Checkout form. Just like data-amount, this is for display purposes only and the actual currency used for the charge is specified by the currency parameter in the charge creation.
This is what i managed to do.
I went with the custom form approach. I had a form template to capture both customer and card inputs in billing.template.html like so:
<form method="POST" id="payment-form">
<span class="payment-errors"></span>
<div>
<label>Name</label>
<input type="text" name="name" data-stripe="name">
</div>
<div>
<label>Email</label>
<input type="text" name="email" data-stripe="address_email">
</div>
<div>
<label>Address Line 1</label>
<input type="text" name="street" data-stripe="address_line1">
</div>
<div>
<label>Postcode</label>
<input type="text" name="postcode" data-stripe="address_zip">
</div>
<div>
<label for="country">Country</label>
<select ng-include="'../templates/_partials/_countrylist.html'"
id="countries" name="country" class="form-control"
name="country" ng-model="country" id="country" size="2"
data-stripe="address_country" required></select>
</div>
<div class="form-row">
<label>
<span>Card Number</span>
<input type="text" name="cardNumber" size="20" data-stripe="number"/>
</label>
</div>
<div class="form-row">
<label>
<span>CVC</span>
<input type="text" name="cvc" size="4" data-stripe="cvc"/>
</label>
</div>
<div class="form-row">
<label>
<span>Expiration (MM/YYYY)</span>
<input type="text" name="expMonth" size="2" data-stripe="exp-month"/>
</label>
<span> / </span>
<input type="text" name="expYear" size="4" data-stripe="exp-year"/>
</div>
<button id="customButton">Pay with Card</button>
</form>
I know we are not supposed to use name attribute in those form inputs but i left them so i could use angular validation, but i remove them using jquery before submitting to server.
Now i created a controller to handle the form: BillingController.js. In there i had an "on click" handler which kick started things by getting a hold of the form and doing some preparatory work: disabling button to prevent further clicks and removing those 'dreaded' name attributes, comme ca:
$('#customButton').on('click',function(event) {
var $form = $('#payment-form');
// Disable the submit button to prevent repeated clicks
$form.find('button').prop('disabled', true);
//NOW REMOVE THOSE NAME ATTRIBUTES
$form.find('input').removeAttr('name');
// call Stripe object and send form data to get back the token.
// NOTE first argument is $form
Stripe.card.createToken($form, stripeResponseHandler);
// Prevent the form from submitting with the default action
return false;
});
Now let me quote the documentation here as this is very important to understand: https://stripe.com/docs/tutorials/forms
The important code to notice is the call to Stripe.card.createToken.
The first argument is the form element containing credit card data
entered by the user. The relevant values are fetched from their
associated inputs using the data-stripe attribute specified in the
form.
Next we create stripeResponseHandler(). Remember it was the second argument in Stripe.card.createToken($form, stripeResponseHandler); above which gets called when Stripe returns the token.
function stripeResponseHandler(status, response) {
var $form = $('#payment-form');
if (response.error) {
// Show the errors on the form
$form.find('.payment-errors').text(response.error.message);
$form.find('button').prop('disabled', false);
} else {
// response contains id and card, which contains additional card details
var token = response.id;
// Insert the token into the form so it gets submitted to the server
$form.append($('<input type="hidden" name="stripeToken" />').val(token));
// and submit
$form.get(0).submit();
}
};
This is copy and paste stuff from stripe's own documentation: https://stripe.com/docs/tutorials/forms. Now, I want to say that, this is where a lot of us were tripping over the fact that form was performing a redirect etc. - notice final line $form.get(0).submit(); . Thats what caused the auto submit, redirecting to what ever action was on form, if u had any (in my case action attribute wasn't necessary as i was doing redirects in my controller).
So i decided to remove $form.get(0).submit() and implemented my own redirect after i was done sending data to the server.
NOTE: Stripe's response will have included data from the $form - try console.log(response); to have an idea of what's being posted back.
FINALLY:
We check if there were any errors returned and if so display them. Otherwise its all good, send data to the server.
The final code looks like:
function stripeResponseHandler(status, response) {
var $form = $('payment-form');
if (response.error) {
// Show the errors on the form
$form.find('.payment-errors').text(response.error.message);
} else {
// response contains id and card, which contains additional card details
var token = response.id;
// prepare data
var data = {
stripeToken: token,
fullName: response.card.name,
street: response.card.address_line1,
postcode: response.card.address_zip,
town: response.card.address_city,
country: response.card.address_country,
last4: response.card.last4
};
// send to server
$http.post('/checkout', data).then(function(result){
// here you can redirect yourself.
window.location.href = "/#/order-complete";
});
}
};
Angular really playing well with stripe here. Check out this link also: https://gist.github.com/boucher/1750368 - learn a lot from it.
I hope it helps someone today. Happy coding!
Stripe doesn't get involved with your form aside from preventing the default action on form submit event and stopping event propagation. Once the checkout process completes, it appends the relevant data to your form and then triggers a form submit event that is handled by HTML / Javascript natively.
I recommend using something like https://github.com/tobyn/angular-stripe-checkout to get your Stripe response handled correctly by Angular.
Otherwise you could add ng-submit="handleStripeCheckout($event)" to your form instead of action="/#/form". When Stripe's checkout process completes, your $scope.handleStripeCheckout method will be run and you can analyze the new form data inside that method.
Edit: Stripe checkout.js actually triggers form.submit(). That's a pretty bad bug on their part considering that almost no browsers handle that correctly. (Form submitted using submit() from a link cannot be caught by onsubmit handler)

meteor-typeahead: Listing and selecting

I have installed meteor-typeahead via npm. https://www.npmjs.org/package/meteor-typeahead
I have also installed
meteor add sergeyt:typeahead
from https://atmospherejs.com/sergeyt/typeahead
I am trying to get the data-source attribute example to function so I can display a list of countries when the user begins to type. I have inserted all countries into the collection :-
Country = new Meteor.Collection('country');
The collection is published and subscribed.
When I type into the input field, no suggestions appear. Is it something to do with activating the API? if so how do I do this? Please reference the website https://www.npmjs.org/package/meteor-typeahead
My form looks like this:
<template name="createpost">
<form class="form-horizontal" role="form" id="createpost">
<input class="form-control typeahead" name="country" type="text" placeholder="Country" autocomplete="off" spellcheck="off" data-source="country"/>
<input type="submit" value="post">
</form>
</template>
client.js
Template.createpost.helpers({
country: function(){
return Country.find().fetch().map(function(it){ return it.name; });
} });
In order to make your input to have typeahead completion you need:
Activate typeahead jQuery plugin using package API
Meteor.typeahead call in template rendered event handler.
Meteor.typeahead.inject call to activate typeahead plugin for elementes matched by CSS selector available on the page (see demo app).
Write 'data-source' function in your template understandable by typeahead plugin. It seems your 'data-source' function is correct.
Add CSS styles for typeahead input(s)/dropdown to your application. See example here in demo app.
Try this way in your template:
<input type="text" name="country" data-source="country"
data-template="country" data-value-key="name" data-select="selected">
Create template like country.html (for example /client/templates/country.html) which contains:
<template name="country">
<p>{{name}}</p>
</template>
In your client javascript:
Template.createpost.rendered = function() {
Meteor.typeahead.inject();
}
and
Template.createpost.helpers({
country: function() {
return Country.find().fetch().map(function(it){
return {name: it.name};
});
},
selected: function(event, suggestion, datasetName) {
console.log(suggestion); //or anything what you want after selection
}
})

Resources