Meteor with Typeahead: where to call typeahead() on input element? - meteor

I'm using Meteor with Iron Router, and can't seem to get typeahead (this version: https://github.com/bassjobsen/Bootstrap-3-Typeahead) to work.
Here's some code:
HomeController = RouteController.extend({
//....
after: function () {
var tags = this.getData().tags;
console.log(tags);
if(tags.length > 0) {
var tags = ['hello', 'world'];
console.log("Adding typeahead for tags to ", $('.input-search')[0]);
console.log("tags: ", tags);
$('.input-search').typeahead({
source: tags,
updater: function(item) {
Router.go('/projects/tag/' + item);
}
});
}
},
I have a header that's part of the application layout, and has an input like this:
<input type="text" class="form-control input-search" data-provide="typeahead" placeholder="Search">
The jQuery in the after: function gets the input correctly. But calling typeahead on the input doesn't seem to activate typeahead properly: when typing in the input, nothing happens.
However if I wrap the typeahead call in a setTimeout, it does work.
Of course, whenever you start wrapping things in setTimeouts, something isn't right.
Where/when is the correct place to initialise typeahead when using Iron Router?

You can initialize the typeahead in the rendered function of your template. For instance:
Template.mytemplate.rendered = function() {
$('.input-search').typeahead({
source: tags,
updater: function(item) {
Router.go('/projects/tag/' + item);
}
});
};

I figured it out.
You need to make sure any inputs used by plugins are "preserved", i.e. not re-rendered, by Meteor. The simplest way to do this is to make sure the input has an ID attribute. So changing my search input to this fixed it:
<input type="text" id="input-search" class="form-control" data-provide="typeahead" placeholder="Search">
Relevant documentation: http://docs.meteor.com/#template_preserve

As of Meteor 1.0.3.1 and iron:router#1.0.7 working solution is to install sergeyt:typeahead and init typeahead like this:
Template.MyTemplate.rendered = function () {
Meteor.typeahead.inject();
}
Init may be done only once for top-level template.

I wrote an article about this, you can read it here.
In summary, your implementation using javascript to select and alter the element is bad practice in a reactive environment. I've tried it that way and it's a huge pain.
What I found is that you can create a helper that returns a JSON string of your typeahead data and use the data-source attribute, like so
JS
Template.myHelper.helper({
typeahead : function(){
return JSON.stringify(Session.get("typeahead"));
}
});
HTML
<template name="myTemplate">
<input data-source="{{typeahead}}" data-provide="typeahead" id="blah" type="text" />
</template>

Related

Angular Reactive form asynchronous operation

I am getting this error when I try to build a reactive form for creating a new password form.I have mentioned the source code below and when I remove the source code part then there is no error but without that my operation is not working as well. I think I have to add or delete something in my source code to get the desired output
main.ts:12 TypeError: Cannot read property 'value' of null
at FormGroup.passwordShouldMatch [as validator] (password.validators.ts:18)
at FormGroup._runValidator (forms.js:4089)
at FormGroup.updateValueAndValidity (forms.js:4050)
at new FormGroup (forms.js:4927)
at FormBuilder.group (forms.js:8924)
at new ChangePasswordComponent (change-password.component.ts:15)
at createClass (core.js:31987)
at createDirectiveInstance (core.js:31807)
at createViewNodes (core.js:44210)
at callViewAction (core.js:44660)
static passwordShouldMatch(control : AbstractControl) {
let newPassword = control.get('newPassowrd');
let confirmPassword = control.get('confirmPassowrd');
if (newPassword.value !== confirmPassword.value){
return { passwordShouldMatch:true };
return null;
}
}
As you didn't add any code snippet I am considering your form structure is something like this.
this.fb.group({
newPassowrd: [''],
confirmPassowrd: [''],
});
here, you include an custom validation function called passwordShouldMatch and this function looks fine. So, I assume that you did something wrong when you set the validator to that form group.
this.fb.group({
newPassowrd: [''],
confirmPassowrd: [''],
}, { validator: this.passwordShouldMatch});
this is how you should set the validation function for the form group. And in html your form should be something like this.
<form [formGroup]="form" novalidate (ngSubmit)="onSubmit(survey)">
<input type="text" placeholder="Untitled form" formControlName="newPassowrd">
<input type="text" placeholder="Untitled form" formControlName="confirmPassowrd">
<span *ngIf="form.hasError('passwordShouldMatch')">not match</span>
</form>
everything should work this way. Here is the working version of stackblitz

creating element on click in Meteor

I'm using Meteor with React. I have a really simple goal, but i have tried a lot and can't solve it for myself. I will show you my attemps below.
I want to create a form for the Ingredients. At the first moment there is only one input (for only one ingredient) and 2 buttons: Add Ingredient and Submit.
class IngredientForm extends Component {
render() {
return(
<form onSubmit={this.handleSubmit.bind(this)}>
<input type="text"/>
{ this.renderOtherInputs() }
<input type="button" value="Add Ingredient" onClick={this.addIngredient.bind(this)}>
<input type="submit" value="Submit Form">
</form>
);
}
}
So when I click Submit then all the data goes to the collection. When I click Add Ingredient then the another text input appears (in the place where renderOtherInputs() ).
I know, that Meteor is reactive - so no need to render something directly. I should underlie on the reactive data storage.
And I know from the tutorials the only one way to render something - I should have an array (that was based on collection, which is always reactive) and then render something for each element of that array.
So I should have an array with number of elements = number of additional inputs. that is local, so I can't use Collection, let's use Reactive Var instead of it.
numOfIngredients = new ReactiveVar([]);
And when I click Add button - the new element should be pushed to this array:
addIngredient(e) {
e.preventDefault();
let newNumOfIngredients = numOfIngredients.get();
newNumOfIngredients.push('lalal');
numOfIngredients.set(newNumOfIngredients);
}
And after all I should render additional inputs (on the assumption of how many elements I have in the array):
renderOtherInputs() {
return numOfIngredients.get().map((elem) => {
return(
<input type="text"/>
);
}
}
The idea is: when I click Add button then new element is pushed to the ReactiveVar (newNumOfIngredients). In the html code I call this.renderOtherInputs(), which return html for the as many inputs as elements I have in my ReactiveVar (newNumOfIngredients). newNumOfIngredients is a reactive storage of data - so when I push element to it, all things that depends on it should re-render. I have no idea why that is not working and how to do this.
Thank you for your help.
Finally I got the solution. But why you guys don't help newbie in web? It is really simple question for experienced developers. I read that meteor and especially react have powerful communities, but...
the answer is: we should use state!
first let's define our state object in the constructor of react component:
constructor(props) {
super(props);
this.state = {
inputs: [],
}
}
then we need a function to render inputs underlying our state.inputs:
renderOtherInputs() {
return this.state.inputs.map( (each, index) => {
return (
<input key={ index } type="text" />
);
});
}
and to add an input:
addInput(e) {
e.preventDefault();
var temp = this.state.inputs;
temp.push('no matter');
this.setState({
inputs: temp,
});
}
p.s. and to delete each input:
deleteIngredient(e) {
e.preventDefault();
let index = e.target.getAttribute('id');
let temp = this.state.inputs;
delete temp[index];
this.setState({
inputs: temp,
});
}

Translating number into currency without jQuery or manipulating the DOM

It is said that,
A good rule of thumb is that if you're using jQuery to manipulate any
DOM elements, you're probably doing it wrong.
And:
Needing access to an element from a helper function indicates that you
are trying to use a procedural coding style rather than a
template-driven style.
I have a simple number input that I wish to translate into a more comprehensible currency outside it so that the user understands what he is doing. I wanted to do this:
<input type="number" class="raw-price">
<p>Price in USD: {{priceInUsd}}</p>
And then define a helper:
Template.myTemplate.helpers({
priceInUsd: function() {
var rawPrice = $('.raw-price').val()
//perform calculation
return calculationResult
}
})
First of all, this isn't working (I don't really know why). Second, this goes against the "rules" I posted above. How am I supposed to do it? I probably could do this the same way using an event listener instead, but that would still be the wrong approach, I assume.
Flash update!
Actually! Here's a better solution: reactive variables! If you wish to keep your rawPrice in this one template, just install the standard reactive-var package:
meteor add reactive-var
And go at it this way:
Template:
<input type="number" class="raw-price">
<p>Price in USD: {{priceInUsd}}</p>
onCreated:
Template.myTemplate.onCreated(function() {
this.rawPrice = new ReactiveVar;
this.rawPrice.set(''); // not sure what you want to preset your value to
});
Helper:
Template.myTemplate.helpers({
priceInUsd: function() {
var rawPrice = Template.instance().rawPrice.get()
//perform calculation
return calculationResult
}
})
Event:
Template.myTemplate.events({
"change .raw_price": function (evt, template) {
template.rawPrice.set($(evt.currentTarget).val());
}
});
Previous (accepted) answer
According to most examples of two-way data binding in meteor I have seen, best case would probably be to use a helper, an event and a Session variable, like so.
Template:
<input type="number" class="raw-price">
<p>Price in USD: {{priceInUsd}}</p>
Helper:
Template.myTemplate.helpers({
priceInUsd: function() {
var rawPrice = Session.get('rawPrice');
//perform calculation
return calculationResult
}
})
Event:
Template.myTemplate.events({
"change .raw_price": function (evt) {
Session.set("rawPrice", $(evt.currentTarget).val());
}
});
Sadly, you're using a session variable, but it is still better than using a collection for such a local thing, like I saw in other examples...

Meteor templates accessing subtemplate helper functions

Is it possible for a parent Meteor template to access a subtemplate directly? Ideally, I'd like to use templates a widgets with an API. I was hoping for something like this:
mypage.html
<template name="myPage">
<div class="widgetContainer"></div>
<button>submit</button>
</template>
mypage.js
Template.myPage.rendered = function(){
this.myWidgetInstance = UI.render(Template.myWidget)
UI.insert(this.myWidgetInstance, $('.widgetContainer')[0]);
}
Template.myPage.events({
'click button': function(e, template){
// I don't want this logic to live inside of mywidget.js.
// I also don't want this template to know about the <input> field directly.
var val = template.data.myWidgetInstance.getMyValue();
}
});
mywidget.html
<template name="myWidget">
<input></input>
</template>
mywidget.js
Template.myWidget.getValue = function(){
return this.$('input').val();
}
The above doesn't work because myWidgetInstance.getMyValue() doesn't exist. There doesn't appear to be a way for external code to access template helper functions on an instance.
Is anyone using Meteor templates in the way I'm trying to use them above? Or is this better suited for a separate jQuery widget? If so, it'd be a shame because I still want my widget template to benefit from the features Meteor provides.
It is possible to access subtemplate helper function.
Your example will work once you apply few fixes:
fix 1 : getValue() instead of getMyValue()
Template.myPage.events({
'click button': function(e, template){
// I don't want this logic to live inside of mywidget.js.
// I also don't want this template to know about the <input> field directly.
var val = template.myWidgetInstance.getValue();
console.log(val);
}
});
fix 2 : $('input').val(); instead this.$('input').val();
Template.myWidget.getValue = function(){
return $('input').val();
}
fix 3 : <input> should have no close tag.
<template name="myWidget">
<input type="text" value="sample value">
</template>

Run JS after rendering a meteor template

I have a template that looks something like this:
<template name="foo">
<textarea name="text">{{contents}}</textarea>
</template>
I render it with:
Template.foo = function() {
return Foos.find();
}
And I have some event handlers:
Template.foo.events = {
'blur textarea': blurHandler
}
What I want to do is set the rows attribute of the textarea depending on the size of its contents. I realize that I could write a Handlebars helper, but it wouldn't have access to the DOM element being rendered, which would force me to do some unnecessary duplication. What I want, ideally, is for meteor to trigger an event after an element is rendered. Something like:
Template.foo.events = {
'render textarea': sizeTextarea
}
Is this possible?
As of Meteor 0.4.0 it is possible to check if a template has finished rendering, see http://docs.meteor.com/#template_rendered
If I understand your question correctly, you should wrap your textarea resize code inside a Template.foo.onRendered function:
Template.foo.onRendered(function () {
this.attach_textarea();
})
I think the current 'best' way to do this (it's a bit of a hack) is to use Meteor.defer ala Callback after the DOM was updated in Meteor.js.
Geoff is one of the meteor devs, so his word is gospel :)
So in your case, you could do something like:
<textarea id="{{attach_textarea}}">....</textarea>
and
Template.foo.attach_textarea = function() {
if (!this.uuid) this.uuid = Meteor.uuid();
Meteor.defer(function() {
$('#' + this.uuid).whatever();
});
return this.uuid;
}
EDIT
Note, that as 0.4.0, you can do this in a much ad-hoc way (as pointed out by Sander):
Template.foo.rendered = function() {
$(this.find('textarea')).whatever();
}
Since about June 2014, the correct way to do this has been to set a callback using Template.myTemplate.onRendered() .
Yeah I think so - not sure if it's "the right way", but this works for me:
In your app JS, the client section will run whatever javascript there on the client. For example:
if (Meteor.is_client) {
$(function() {
$('textarea').attr('rows' , 12) // or whatever you need to do
})
...
Note the example here uses JQuery, in which case you need to provide this to the client (I think :-). In my case:
I created a /client dir, and added jquery.js file under this.
Hope this helps.

Resources