Meteor Newbie query about best practice - javascript

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.

Related

Can't set headers after they are sent. in NodeJS using HBS

I get the following error while trying to render my page
Can't set headers after they are sent.
Below is what I am trying to do
res.render('information', {
name: rows[i].name,
boxId: rows[i].box
});
console.log(rows[i].name);
It prints out the right information in the console, so i know that this is correct, but getting that information render at information page is an issue
{{#each items}}
<form method="post" action="">
<input type="hidden" value="{{BoxId}}">
<button type="submit">{{name}}</button>
</form>
{{/each}}
Essentially on that page I am trying to run a for loop, where a new button form must be generated with each item found in the database. If there 5 items with name, and box value, then this should produce 5 times with their respective value and name set.
However, this is a secondary problem, because even doing <h3> {{name}} </h3> it doesnt work and i get the set headers problem.
Any help would be appreciated.
Template language being used:
http://handlebarsjs.com/
I'm not sure how you have this setup, but is the res.render itself in a for loop? If so, pass the entire data set to the template, and loop through in the template itself, something like:
var data = {
rows: rows
}
res.render('information', data);
And in the template:
{{#each rows}}
{{this.propName}}
{{/each}}
If you have nested data, you can have nested {{#each}} statements as well.

Meteor : Fundamental Database Strategy Scenario

The Html Page Looks like this :
index.html
<head>
<title>MP3</title>
</head>
<body>
{{> download}}
<div class="container">
<ul>
{{#each songs}} {{> okay}} {{/each}}
</ul>
</div>
</body>
<template name="download">
<input type="text" id="songname">
<button id="hitit">Submit</button>
</template>
<template name="okay">
<form action="{{linksong}}">
<li><input type="submit" value="Download {{kbps}} kbps" id = "{{kbps}}"></li></form>
</template>
Now, from the input button, I use this code to add it to minimongo collecction :
document.addEventListener('DOMContentLoaded', function() {
var inputterm = document.getElementById("songname");
var submitButton = document.getElementById("hitit");
submitButton.addEventListener('click', function() {
SearchTerm.insert({
sub: inputterm.value
});
}, false);
});
Then I have to access the term I added to the database, query it, and add the result to a new database.
Q1 : I don't know how to query the database to extract the same term.
But, before that, a fundamental question arises :
Let's say there are ten users using the app at the same time from different locatons, and they hit submit at different times but before anyone has got a response.
THe query is added to the same database for all the users. SO, the one who fires the last query will get the correct result and the rest will be like : "What the hell?"
All I'm trying to say, if this app is going to have thousand users, is it Viable to create seperate databases for all of them, and if yes, then how can it be implemented?
And Is there any other way like Sessions, publish and subscribe, and do they fit the above scenario ?
Plus it is a download song sort of a website, so there is no point asking the user to login or whatever.

How to use the same form for updating/inserting records using quickForm?

I have this Meteor template:
<template name="personalDetailsForm">
{{> quickForm collection="PersonalDetails" id="personalDetailsForm" type="insert"}}
{{> quickForm collection="PersonalDetails" doc=editingDoc id="personalDetailsForm" type="update"}}
</template>
The forms are displayed as I expect, however I just want one form. A blank form for when there is no data which does an insert when the form is submitted. Then when the form is reloaded the data previously submitted is shown on the form. If the form is then submitted again any data that has changed will be updated.
Currently the insert form is displaying and underneath it the update form is displaying, with the data that has been previously inserted. Trying to update the data on the second form doesn't work, instead it inserts a new record. This I imagine could be because the form ids are the same.
Ideally I would like to do something like this:
<body>
{{#if PersonalDetails}}
{{> personalDetailsFormUpdate}}
{{ else }}
{{> personalDetailsFormInsert}}
{{/if}}
</body>
<template name="personalDetailsFormInsert">
{{> quickForm collection="PersonalDetails" id="personalDetailsFormInsert" type="insert"}}
</template>
<template name="personalDetailsFormUpdate">
{{> quickForm collection="PersonalDetails" doc=editingDoc id="personalDetailsFormUpdate" type="update"}}
</template>
I think this part of the documentation is what I'm looking for:
Can I reuse the same quickForm or autoForm for both inserts and updates?
Yes. Your code that flips between states should do the following in this order:
Change the type attribute's value to "insert" or "update" as appropriate, probably by updating a reactive variable.
Change the doc attribute's value to the correct document for an update or to null (or a document containing default values) for an insert, probably by updating a reactive variable.
Call AutoForm.resetForm(formId). This will clear any existing validation errors for the form.
Can anyone provide an example of this?
The question was asked some time ago, but I had the same issue and just solved it.
It is quite easy:
Get the context data as usual. E.g. with iron-router, build a route like:
Router.route('Options', {
path: 'options',
data: function() {
var options = Options.findOne({owner: Meteor.userId()});
return options;
}
});
Build a new helper, that checks if the context data (this in the helper) is empty or not (example is for a global helper that works in every template):
UI.registerHelper("formType", function(){
if(_.isEmpty(this)) {
return 'insert'
} else {
return 'update';
}
});
Setup the template with the new helper:
<template name="Options">
<h1>Options</h1>
{{> quickForm collection="Options" doc=this id="optionsForm" type=formType omitFields="owner"}}
</template>
Everything should work now. If the database doesn't give a value back, the form will automatically switch to insert. So in my example, if you open the form the first time, it will use insert. The second time, it will use update.
thanks #peXd. this was very helpful. however I had an additional required field in my schema which was blocking the insert and it was silently failing to insert and since i stumbled into this solution through 8 trap doors i thought i'd post it in case it helps someone else.
i found this recommendation from aldeed [https://github.com/aldeed/meteor-autoform/issues/199] to use this error hook in client code:
AutoForm.addHooks(null, {
onError: function (name, error, template) {
console.log(name + " error:", error);
}
});
however this also failed with me saying AutoForm was not defined. this was odd to me because aldeed:autoform was being pulled into the project as a dependency. but just to double check i did meteor add aldeed:autoform to pull it in directly and suddenly AutoForm was available to add the hook to.

Angularjs, checking if radio buttons in form have been selected

I'm starting with AngularJS, and I'm building a multi-step form where user has to fill different pages. When finished a page, he's allowed to press a next button and fill the following page.
For the first page, I've built in the HMTL a form (named pageOneForm), with different text input fields, marked as required, and in the relative controller I'm doing this watch:
$scope.$watch('pageOneForm.$valid', function(validity) {
ModelData.actualPageCompleted = validity;
})
And it works like a charme. My model (ModelData) is updated.
I was trying to apply the same logic to the following part of the app, the second page. Instead of input text, the user has to select two options from 2 different radio buttons groups.
So I built in the html a list of buttons via ng-repeat :
<div ng-Controller="PageTwo" ng-show='data.actualPage == 2'>
<form name="pageTwoForm">
<h3>General Information > Knowledge About </h3>
<div>
<b>User</b>
<div ng-repeat="option in userOptions">
<input type="radio" name="userGroups" ng-model="data.knowledgeAboutUser" ng-value="option.id" id="{{option.id}}" required>{{option.text}}
</div>
<div ng-repeat="option in targetGroupUserOptions">
<input type="radio" name = "targetUserGroup" ng-model="data.knowledgeAboutTargetGroup" ng-value="option.id" id="{{option.id}}" required>{{option.text}}
</div>
</div>
</form>
and I've implemented the same code as above in its controller:
$scope.$watch('pageTwoForm.$valid', function(validity) {
ModelData.actualPageCompleted = validity;
})
but apparently it doesn't work, and in my model actualPageCompleted is always true...
What am I doing wrong?
Thanks
I did my best to create a controller with some dummy data to get a fiddle working with your example code. Here is the fiddle You need to force the $digest cycle to update your form's validity state on ng-click for the radio buttons (see this SO post for more details), which is why the method
$scope.forceDigest = function(){
setTimeout(function(){ $rootScope.$$phase || $rootScope.$apply(); });
};
is necessary. Alternatively, you can get rid of the method call and uncomment the html code
<h3 ng-show="false">{{data.knowledgeAboutTargetGroup}}</h3>
<h3 ng-show="false">{{data.knowledgeAboutUser}}</h3>
in the fiddle to force the form object to update as well.
And I would make sure that ModelData.actualPageCompleted is not retaining its true value from when pageOneForm.$valid became true and it was set.
I hope that this helps!

Data Binding between pages in Angularjs

I am trying to understand data binding in Angularjs.
What I want to do is establish binding between pages that is if I change the input on first.html, the data should automatically change in second.html.
For example,
This is first.html:
<div ng-controller="MyCtrl">
<input type="text" ng-model="value"/><br>
{{value}}
<input type="submit" value="Second page"/>
</div>
and say second.html has only this piece of code {{value}}.
and in the .js file we have $routeProvider which takes the template url as 'second.html' & the controller is 'MyCtrl'.
So the controller is:
MyApp.controller(function($scope){
$scope.value="somevalue";
})
By doing the above way the {{value}} on the second.html is getting the value "somevalue". Which is comming from the controller.
But if I change the input value dynamically that is on the first.html, the value on the second.html is not getting that value.
My question is how do I bind the value on second.html with first.html automatically.
To understand the question clearly, Suppose there is an input field for entering text and a submit button on first.html, then I want to get the Input value of the text field of the first.html on the second.html page on Submit.
Use a service and store your model there. Gloopy already has a good example of this here: https://stackoverflow.com/a/12009408/215945
Be sure to use an object property instead of a primitive type.
If you'd rather use $rootScope, then as above, define an object, rather than a primitive:
$rootScope.obj = { prop1: "somevalue" }`
then bind to that object property in your views:
<input type="text" ng-model="obj.prop1">
{{obj.prop1}}
If you attach your data to $rootScope if will survive transitions across controllers and be part of all $scopes (prototype inheritance magic)
//**attach in controller1:**
function MyCtrl1($rootScope) {
$rootScope.recs= { rec1 : "think philosophically" };
}
//**do nothing in controller for view2:**
function MyCtrl2($scope) {
//nothing
}
//**Markup for view2: automaticall makes use of data in $routeScope**
<p>Try doing this: {{recs.rec1 }}</p>
//**markup for view1 to respond to OPs question in comments**:
<input ng-model="recs.rec1" />
Update: Creating a custom service is a more scalable and structurally sound way to handle this, but the $rootScope method is the quick and dirty way.
Update2: added view1 markup to respond to OP question, edited example to take advantage of correct advice to use object rather than primitive.
Found the Solution to what I was looking for, The solution is in the Angular docs, here is the link http://docs.angularjs.org/cookbook/deeplinking.
Some part of the example on that link answers my question.
You should user $broadcast, $emit or scope communication. Try to avoid overloading the rootScope. It is as a bad practice as saving data into the application sessions.

Categories

Resources