Creating "components" with Meteor and Handlebars - javascript

I'm exploring the Meteor-framework. I've a lot of experience with Java EE / JavaServer Faces (JSF), but I'm new to JavaScript-development. I'm trying to create a nicely formatted "horizontal form", as described at http://twitter.github.io/bootstrap/base-css.html#forms. My problem is, that this gives me a lot of repeated HTML for each input field, which is not DRY...
So, I'm trying to create a construct like "composite components" in JSF 2.x. This means I'm looking for a way to call a Meteor-template and substitute some values in it.
My template is like this:
<template name="formField">
<div class="control-group">
<label class="control-label" for="{{this.id}}">{{this.label}}:</label>
<div class="controls">
{{this.formField}}
</div>
</div>
</template>
I was able to get it partially working, by calling it like this:
{{> formField labels.x}}
Where I defined the labels map like this:
Template.myTemplate.labels = {
"x": {id: 'idx', label: 'labelx'},
"y": {id: 'idy', label: 'labely'},
"z": {id: 'idz', label: 'labelz'}
}
The problem is, of course, substituting the <input>-element in the template. I tried all sorts of ways to do this, but there are several problems:
I could add the <input>-element to my formField-template, but then I cannot bind the value of that <input>-element to a property of the JavaScript-object that is being edited in the form.
I could create a function that returns an <input>-element, but then I would end up with markup-code in my JavaScript-sources.
Another problem is that I'm not really satisfied with the Template.myTemplate.labels-map. It seems this is unavoidable, since I was not able to use any literal values. It would be nice if I was able to call the template like this:
{{> formField idx labelx}}
{{> formField idy labely}}
{{> formField idz labelz}}
To conclude, I have basically one question: what's the best way to create some sort of component with Meteor/Handlebars?

Forms are on the roadmap:
https://trello.com/card/easy-forms/508721606e02bb9d570016ae/8
But in particular check out this discussion:
https://github.com/meteor/meteor/issues/1054 (from the above link).
As for creating components in Meteor, there is no the way to do it, but you might want to check out how the accounts packages work, as that's pretty isolated, extensible, and a form:
https://github.com/meteor/meteor/tree/master/packages

Related

HTML Templating Engines and Liquid

I'm trying to get my head round templating engines and if there is something suitable for my requirements.
I'd like to specify HTML and provide dynamic functionality from within the HTML itself. For example, say I had a check box on a page
<label><input type="checkbox" id="cbox1" value="first_checkbox"> This is my checkbox</label>
I'd like to specify logic within HTML so that I display more content only if that checkbox has been checked, e.g.
// if #cbox1.checked == true
<h1>The check box is checked</h1>
// else
<h1>The check box is not checked</h1>
// end
Now, it is likely that liquid will be used to provide dynamic functionality based on a data store. So it'd also be nice to use the same liquid syntax to make the form dynamic (i.e. use liquid syntax in the if conditional above).
Is it possible to write a 'js engine', perhaps using jquery, that I could include in my web pages that would allow me to use liquid syntax but bind to variables in the 'js engine' as well as the data store to make my content dynamic?
Or, is there a better approach?
I would recommend using Vue.js (https://vuejs.org/).
The template engine is very easy to learn, and provides all the functionality you mention.
Here is a working example of your scenario:
https://jsbin.com/kikaxecogo/edit?html,output
But all you need to do is initialise Vue:
new Vue({
el: '#app',
data: {
showData: false
}
});
and write the template data:
<input type="checkbox" v-model="showData">
<div v-if="showData">
This is visible using v-if
</div>
<div v-else>
The check box is not checked
</div>
I've written a introduction guide to Vue.js here https://steveedson.co.uk/vuejs-intro/
You can also bind to other text inputs, data from ajax sources etc:
<input type="text" v-model="name">
<p>Hello {{ name }}</p>
And everything will update automatically.
As an alternative to Vue.js, there is also:
https://facebook.github.io/react/
https://angularjs.org/

Aurelia repeat.for for - some questions

I am attempting to generate a form like this:
<div class="col-xs-6" for.repeat="field in ${config.formMain}">
<div class="form-group">
<label>${field.label & oneTime}</label>
<select if.one-time="field.controlType == 'select'" class="form-control">
<option value=""><- Select -></option>
</select>
</div>
</div>
where config is a variable in my view-model.
A couple questions:
1) I have tried both for.repeat="field in ${config.formMain}" and for.repeat="field in config.formMain" and neither syntax seems to work. How am I meant to access the array from the object?
2) Is there any support for something like for.repeat.one-time="field in ${config.formMain}" where this would cause Aurelia to only execute the loop compilation once (which is useful in my case because the form is not going to chage) but keep the bindings inside the loop as default bindings (i.e. if I have a for.repeat inside of the outer one, it should update itself if the array it is iterating over changes)?
Thanks in advance.
The correct syntax is: repeat.for="thing of things"
See that is of instead of in, following the ECMAScript 6 style. More information at https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/for...of
You could use "& oneTime" to execute the loop only once. Like this:
repeat.for="thing of things & oneTime"

Meteor Newbie query about best practice

Learning Meteor, which is great, but I'm struggling a bit with "best practice".
For example, I'm building a small example quiz, which looks like:
Question : 1 + 1 = ? ---> Dropdown box [1, 2, 3]
Question : 1 + 0 = ? ---> Dropdown box [1, 2, 3]
Question : 1 - 1 = ? ---> Dropdown box [1, 2, 3]
Question : 2 + 1 = ? ---> Dropdown box [1, 2, 3]
Then, at the bottom is a Button that says "Score Me".
When that button is pressed, it should go through each question and put a little "Correct" or "Wrong" next to the dropdown box.
All of this looks something like:
<template name="questions">
<div class="jumbotron">
{{#each questions}}
<span class="lead">{{question}} </span>
<select class="answerDropDown">
{{#each answer}}
<option value={{this}}>{{this}}</option>
{{/each}}
</select>
<span id="correctornot">{{correctornot}}</span>
{{/each}}
</div>
</template>
I'm pulling the data from the DB using:
Questions = new Mongo.Collection("questions");
And everything is working fine. I've plugged in events and it reacts well, etc... etc...
What I can't do easily is see how to make correctornot work. Well... I say that, but what I mean is nothing really makes sense.
I would imagine when I got the data:
Template.questions.helpers({
questions: function() {
return Questions.find({});
}
I would want to add extra data to each question, but this isn't meant to be saved in the database.
I guess I'm just looking for the correct way to go about it. Even the answers aren't saved anywhere, so when I check to see if it's correct, I'm currently looking at Session variables that are changed on the dropdown select event.
Anyway, all advice welcome.
Cheers.
What you want is benefit from Blaze's reactivity without persisting information in your database. The answer? Reactive client-side values! They come in different form:
Session variables
Reactive variables
Client-side collections
... and many more provided by atmosphere packages
When evaluated in designated contexts (such as helpers or Tracker.autorun), the code included in said context will be ran again every time the value in the reactive variable changes, hence, in the case of helpers, triggering changes in your template.
Example: using Session
Here, we will see the easier way to achieve this, using the built-in Session object. Let's assume your questions template is included in a template called form which contains your submit button:
<template name="form">
{{> questions}}
<form id="form">
<input type="submit" value="Score me" />
</form>
</template>
First, we must set a submitted session variable to false when the form template gets rendered.
Template.form.onRendered(function () {
Session.set('submitted', false);
});
Then, catch the form submission event, and instead of submitting, we're going to set our new submitted reactive variable to true.
Template.form.events({
'submit #form': function (e, t) {
e.preventDefault();
// do some prior validation here if needed
Session.set('submitted', true);
}
});
Now, all we need to do is to check in a submitted template helper whether submitted is set to true or false. If it is true, we then proceed to display the result of the correctornot helper, which fetches the value selected in the dropdown and compares it to the correct answer for the question stored in the current data context (the each loop). Assuming this answer is stored in a correctAnswer property of your question:
Template.questions.helpers({
'questions': function() {
return Questions.find({});
},
'submitted': function () {
return Session.get('submitted');
},
'correctornot': function () {
var userResponse = $('#'+this._id+'.answerDropDown').val();
return (this.correctAnswer == userResponse);
}
});
And here is our slightly enhanced questions template:
<template name="questions">
<div class="jumbotron">
{{#each questions}}
<span class="lead">{{question}} </span>
<select id="{{_id}}" class="answerDropDown">
{{#each answer}}
<option value={{this}}>{{this}}</option>
{{/each}}
</select>
{{#if submitted}}
<span id="correctornot">{{correctornot}}</span>
{{/if}}
{{/each}}
</div>
</template>
As you can see, with very little code, you can achieve a nice reactive behavior.
What if I don't want to pollute Session with a pointless flag?
Then, you can look into reactive variables. They're easy to implement once you have the logic in your mind. The little tricky part in your case is that you seem to use nested templates: the submit button triggering the reactive change and the questions list reacting to it are not in the same template. So you will need to navigate between template instances to access the submitted reactive variable. Look at this answer for clues on this subject.

Angular directive inserts wrong DOM node

I have an app with many forms. Each field has several HTML elements, so I thought I could extract some directives (one per type of field) to keep my forms tidy.
I've created a sample app to demonstrate the problem, but I'm getting inconsistent behavior. In the sample app, a <link /> element replaces the <input />. In my real app, <input /> just gets removed from the DOM completely. I feel like this should be easy; why doesn't it work?
To answer your stated question, it's because you told it to, with ng-transclude. That replaces the contents of the tag with the original element, which I don't think you wanted; you probably wanted the original contents to be transcluded as the label instead.
This is probably what you're looking for:
<div class="form-group" >
<label for="{{htmlId}}" ng-transclude></label>
<input id="{{htmlId}}" class="form-control" type="text" ng-model="model" />
<span ng-repeat="error in errors">{{error}}</span>
</div>
I've moved the tranclusion into the label. While this works, I would also recommend the style of actually passing a label attribute, rather than transclude it, just for the sake of having a consistent API and simpler code; it's functionally equivalent, though, so don't let me bully you.
Also, you've got a few errors in your .js as well. First, you want to use = in your scope instead of &
scope: {
model: '=',
errors: '='
},
& is used to pass methods, while = is used for objects (this is a simplification). Since your model and errors are objects, you'll want to use = instead.
Finally, in your example, your html template and your directive's template don't have the same name... you've got an extra 's' in your .js, but that's probably just in the plunker and not your real app.

knockout js: parameters in templated bindings

Im trying to template some components often used in my project. To omit the introduced redundancy in html content. However i didnt figure it out if it is even possible to do so.
I have a template like:
<script type="text/x-jquery-tmpl" id="somefieldtemplate" >
<input name=" Prefix" type="text" data-bind="value: ${ $item.fieldName}SomeField" />
..... mor contents ...
</script>
The parameter bound to the input component shall be adjustable as someone may see via template options so i have an entry like
<div data-bind="template: { name: 'somefieldtemplate',
templateOptions:
{ fieldName:'somefield', displayName:'somefieldlabel' } }">
The error message in my console log is:
SyntaxError: Unexpected token {
I narrowed the problem down to the fact that if i remove $item.fieldName with its value it works.
Has anybody an enlightening solution to this problem maybe ?
Edit:
As information im currently using knockout.js in version: knockout-latest
I had the same problem and after struggling a lot, I solved it by moving to knockout 1.3 beta (now it's in RC) which doesn't use external templating engines. It doesn't support templateOptions either but that's not a problem. I just constructed a custom data for the template which contains the main data as a property plus other properties that contain the stuff I would have passed in templateOptions in 1.2. I passed it in the data parameter, and everything worked fine.
Try
data-bind="value: ${fieldName} + 'SomeField'"
Sadly i need to answer it myself. But i managed to get it working. Thanks to the tip of RP Niemeyer.
For everyone interested in this:
Observables are generated dynamically in the model someModel with a register component method. This observables have always names like <field>SomeField
Template:
<script type="text/x-jquery-tmpl" id="somefieldtemplate" >
<input name="${$item.fieldName}Prefix" type="text" data-bind="value: someModel[$item.fieldName + 'SomeField']" />
..... more contents ...
</script>
Template-Binding:
<div data-bind="template: { name: 'somefieldtemplate',
templateOptions:
{ fieldName:'somefield', displayName:'somefieldlabel' } }">

Categories

Resources