I created a little example for myself to test some stuff with Meteor. But right now it looks like I can't subscribe to a collection, I published on the server side. I hope somebody can tell me where the bug is.
server/model.js
Test = new Meteor.Collection("test");
if (Test.find().count() < 1) {
Test.insert({id: 1,
name: "test1"});
Test.insert({id: 2,
name: "test2"});
}
Meteor.publish('test', function () {
return Test.find();
});
client/test.js
Meteor.subscribe("test");
Test = new Meteor.Collection("test");
Template.hello.test = function () {
console.log(Test.find().count());//returns 0
return Test.findOne();
}
Template.hello.events = {
'click input' : function () {
// template data, if any, is available in 'this'
if (typeof console !== 'undefined')
console.log("You pressed the button");
}
};
client/test.html
<head>
<title>test</title>
</head>
<body>
{{> hello}}
</body>
<template name="hello">
<h1>Hello World!</h1>
{{#with test}}
ID: {{id}} Name: {{name}}
{{/with}}
<input type="button" value="Click" />
</template>
EDIT 1
I want to change the object test, findOne() returns. Let's say for adding an attribute avg which contains the average value of two numbers (test.number1 and test.number2). In my opinion this should look like the following code. But javascript is not synchronous, so this won't work.
Template.hello.test = function () {
var test = Test.findOne();
test.avg = (test.number1 + test.number2) / 2;
return test;
}
EDIT 2
This code worked for me. Now I have to rethink why this solution with 'if (test)' just works with findOne() without a selector in my original project.
Template.hello.test = function () {
var avg = 0, total = 0, cursor = Test.find(), count = cursor.count();
cursor.forEach(function(e)
{
total += e.number;
});
avg = total / count;
var test = Test.findOne({id: 1});
if (test) {
test.avg = avg;
}
return test;
}
The latency the client db uses to replicate data might cause the situation wherein the cursor reckons no results. This especially occurs when the template is immediately rendered as the app loads.
One workaround is to observe query documents as they enter the result set. Hence, something like the following for example happens to work pretty well:
Meteor.subscribe("Coll");
var cursor = Coll.find();
cursor.observe({
"added": function (doc) {
... something...
}
})
Try to surround {{#with test}}...{{/with}} with {{#if}}...{{/if}} statement (because in first data push test does not have id and name fields):
<head>
<title>test</title>
</head>
<body>
{{> hello}}
</body>
<template name="hello">
<h1>Hello World!</h1>
{{#if test}}
{{#with test}}
ID: {{id}} Name: {{name}}
{{/with}}
{{/if}}
<input type="button" value="Click" />
</template>
As a result:
UPDATE:
This code performs calculation of average of field number in all records:
model.js:
Test = new Meteor.Collection("test");
Test.remove({});
if (Test.find().count() < 1)
{
Test.insert({id: 1,
name: "test1",
number: 13});
Test.insert({id: 2,
name: "test2",
number: 75});
}
test.js
Test = new Meteor.Collection("test");
Template.hello.test = function () {
var avg = 0, total = 0, cursor = Test.find(), count = cursor.count();
cursor.forEach(function(e)
{
total += e.number;
});
avg = total / count;
return { "obj": Test.findOne(), "avg": avg };
}
UPDATE 2:
This code snippet works for me:
var test = Test.findOne();
if (test)
{
test.rnd = Math.random();
}
return test;
Maybe you should try to wrap assignment code into if statement too?
Related
I'm trying to learn Javascript by creating a Quiz App like it's mentioned here: Learn Javascript Properly I have trouble trying to do the more advanced quiz, I'm using Handlebars as my templating engine. I've stored the questions in an external JSON file, and the inputs are just a bunch of radio buttons.
index.html:
<!DOCTYPE html>
<html>
<head>
<title>Quiz Application</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div id="container">
<div id="quiz">
</div>
<button id="next-question">Next Question</button>
</div>
<!-- Templates -->
<script id="titleTemplate" type="text/x-handlebars-template">
<div id="quiz-question">{{ title }}</div>
</script>
<script id="choicesTemplate" type="text/x-handlebars-template">
<div id="choices">
<form id="choicesForm">
{{#each choices}}
<div class="choice">
<input type="radio" name="choices" id="choice{{#index}}" value="{{ #index }}">
<label for="choice{{ #index }}" value="{{ #index }}">{{ this }}</label>
</div>
{{/each}}
</form>
</div>
</script>
<script type="text/javascript" src="js/jquery-3.1.0.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.5/handlebars.js"></script>
<script type="text/javascript" src="js/app.js"></script>
</body>
</html>
I've explained everything in the comments
app.js:
"use strict";
var Render = {
question: function(questions, currentIndex) {
var titleTemplate = Handlebars.compile($("#titleTemplate").html());
var choicesTemplate = Handlebars.compile($("#choicesTemplate").html());
var currentQuestion = questions[currentIndex];
Render.title(titleTemplate, currentQuestion);
Render.choices(choicesTemplate, currentQuestion);
},
title: function(titleTemplate, currentQuestion) {
$("#quiz").html(titleTemplate(currentQuestion));
},
choices: function(choicesTemplate, currentQuestion) {
$('#quiz-question').append(choicesTemplate(currentQuestion));
},
};
var Quiz = {
// Tracks the current questions
currentIndex: 0,
// The number of the correct answers
// I want to increment this when the user
// answers the correct question, for some reason it doesn't increment.
correctAnswers: 0,
// I first call this method to get the questions from JSON file
// since you can no longer have synchronous requests
getQuestions: function() {
$.getJSON('questions.json', Quiz.start);
},
// Starts the quiz
start: function(questions) {
// Check if the user is passed the last question
// in this case, if 6 > 5
if(Quiz.currentIndex + 1 > questions.length) {
Quiz.displayFinalScore(questions);
} else {
// Render the next question using the currentIndex
Render.question(questions, Quiz.currentIndex);
Quiz.handleQuestion(questions);
}
},
handleQuestion: function(questions) {
// Get's the correct answer from the JSON
var correctAnswer = questions[Quiz.currentIndex].correctAnswer;
// Check if the radio button is checked
if($("input[name=choices]:checked", "#choicesForm") == correctAnswer) {
// This doesn't increment like it suppose to
// It still stays at 0.
Quiz.correctAnswers += 1;
}
// Increment the index to change to the next question
Quiz.currentIndex += 1;
},
displayFinalScore: function(questions) {
// Simple console log
console.log("You have scored: " + Quiz.correctAnswers + " out of " + questions.length);
}
};
$(function() {
Quiz.getQuestions();
$("#next-question").on("click", Quiz.getQuestions);
})
As you can see I've explained in the comments, The problem is in incrementing the correctAnswers for some reason it doesn't get to the point where it compares both of the variables even if I choose the correct answer from the radio buttons.
questions.json:
[
{
"title":"When did the programming language C++ came out?",
"choices":[
1997,
1995,
2000,
1998
],
"correctAnswer":3
},
{
"title":"When Node.js came out?",
"choices":[
2010,
2011,
2009,
2006
],
"correctAnswer":2
},
{
"title":"What brand of laptop do I have?",
"choices":[
"HP",
"Acer",
"Dell",
"Lenovo"
],
"correctAnswer":0
},
{
"title":"How old am I?",
"choices":[
12,
20,
9,
16
],
"correctAnswer":3
},
{
"title":"How old is Google?",
"choices":[
12,
20,
18,
16
],
"correctAnswer":2
}
]
There are several issues with your code and they are:
In the start function you should change if condition expression to match array indexes, which in JavaScript are starting from 0 and the last element of array have index questions.length - 1.
In the start function you first render new question and then query DOM for selected element, which is already destroyed at this phase. So you need to first handleQuestion and only then render new one.
I also moved currentIndex increment code to the last step (after render)
Since handleQuestion should not be executed at the first render, I added this exotic Quiz.currentIndex && Quiz.handleQuestion(questions);, which actually means: "If currentIndex is 0, which in boolean expressions transforms to false, don't run the right part of boolean condition, which is Quiz.handleQuestion(questions)".
To fetch a numeric value of selected input, you should use parseInt($("input[name=choices]:checked", "#choicesForm").val(), 10)
The resulting code should look like this:
JavaScript
// Starts the quiz
start: function(questions) {
// Check if the user is passed the last question
// in this case, if 5 >= 5
if(Quiz.currentIndex >= questions.length) {
Quiz.displayFinalScore(questions);
} else {
Quiz.currentIndex && Quiz.handleQuestion(questions);
// Render the next question using the currentIndex
Render.question(questions, Quiz.currentIndex++);
}
},
handleQuestion: function(questions) {
// Get's the correct answer from the JSON
var correctAnswer = questions[Quiz.currentIndex-1].correctAnswer;
// Check if the radio button is checked
if(parseInt($("input[name=choices]:checked", "#choicesForm").val(), 10) === correctAnswer) {
// This doesn't increment like it suppose to
// It still stays at 0.
Quiz.correctAnswers += 1;
}
}
Live Demo
https://plnkr.co/edit/03IGvb6IZiw6QbziUpw6?p=preview
I'm new to AngularJS, so sometimes when I do some mistake that is obvious, I still can't figure out what is going wrong with my code. So saying, here is my doubt:
HTML code:
<body ng-controller = "Ctrl">
<script id="Page6.html" type="text/ng-template">
<div class="list card" style="background-color: beige">
<div class="item item-icon-left">
<i class="icon ion-home"></i>
<input type="text" placeholder = "Enter display name" ng-model="user.nam">
</div>
<a ng-click = "saveedit(user)"<button class="button button-clear">SAVE DETAILS</button></a>
</div>
</script>
</body>
CONTROLLER.JS
.controller('Ctrl',function($scope,$rootScope,ContactService){
$rootScope.saveedit=function(user) {
ContactService.save({names: user.nam, image:"images.jpg"},ContactService.getid("Donkey"));
}
});
THIS IS THE SERVICE:
.service('ContactService', function () {
var items = [
{ id: 1, names: 'Dolphin', image: 'dolphin.jpg',}, { id: 2, names: 'Donkey', image: 'donkey.jpg'}, { id: 3, empid: 'FG2043', image: 'penguin.jpg'}];
var im = [{image: ''}];
var ctr=0;
var uid=3;
this.save = function (contact,id) {
ctr=0;
for (i=0;i<items.length;i++) {
if(items[i].id == id)
{
im[0].image= items[i].image;
ctr=100;
break;
}
}
uid = (uid+1);
contact.id = uid;
items.push(contact);
if (ctr==100 ) {
alert("in save putting the image");
items[contact.id].image = im[0].image; //doubt
alert("finished putting image");
}
}
//simply search items list for given id
//and returns the object if found
this.getid = function (name) {
for (i=0;i<items.length;i++) {
if (items[i].names == name) {
return (i+1);
}
}
}
//simply returns the items list
this.list = function () {
return items;
}
});
The problem I am facing is this: Everything works, except one thing. In ContactService, push() function, the line I have commented as //doubt is not getting executed.
The alert before it "in save putting the image" runs, but the alert "finished putting image" doesn't. What is the mistake there??
The problem here is that you're using the id's, which start at 1, to navigate in an array whose indexes start at 0.
To access the most recently pushed element, you should rather do :
items[contact.id - 1].image = im[0].image;
But you actually don't need to access the array : items[contact.id - 1] will return the object that you just pushed, and which is already referenced by variable contact, so you could just do :
contact.image = im[0].image;
I am new to Knockout and have been trying to follow code examples and the documentation, but keep running into an issue. My data bindings printing the Knockout observable function, not the actual values held by my observable fields. I can get the value if I evaluate the field using (), but if you do this you do not get any live data-binding / updates.
Below are some code snippets from my project that are directly related to the issue I am describing:
HTML
<div class="col-xs-6">
<div data-bind="foreach: leftColSocialAPIs">
<div class="social-metric">
<img data-bind="attr: { src: iconPath }" />
<strong data-bind="text: name"></strong>:
<span data-bind="text: totalCount"></span>
</div>
</div>
</div>
Note: leftColSocialAPIs contains an array of SocialAPIs. I can show that code too if needed.
Initializing the totalcount attribute
var SocialAPI = (function (_super) {
__extends(SocialAPI, _super);
function SocialAPI(json) {
_super.call(this, json);
this.totalCount = ko.observable(0);
this.templateName = "social-template";
}
SocialAPI.prototype.querySuccess = function () {
this.isLoaded(true);
appManager.increaseBadgeCount(this.totalCount());
ga('send', 'event', 'API Load', 'API Load - ' + this.name, appManager.getRedactedURL());
};
SocialAPI.prototype.toJSON = function () {
var self = this;
return {
name: self.name,
isActive: self.isActive(),
type: "social"
};
};
return SocialAPI;
})(API);
Updating totalcount attribute for LinkedIn
var LinkedIn = (function (_super) {
__extends(LinkedIn, _super);
function LinkedIn(json) {
json.name = "LinkedIn";
json.iconPath = "/images/icons/linkedin-16x16.png";
_super.call(this, json);
}
LinkedIn.prototype.queryData = function () {
this.isLoaded(false);
this.totalCount(0);
$.get("http://www.linkedin.com/countserv/count/share", { "url": appManager.getURL(), "format": "json" }, this.queryCallback.bind(this), "json").fail(this.queryFail.bind(this));
};
LinkedIn.prototype.queryCallback = function (results) {
if (results != undefined) {
results.count = parseInt(results.count);
this.totalCount(isNaN(results.count) ? 0 : results.count);
}
this.querySuccess();
};
return LinkedIn;
})(SocialAPI);
In the <span data-bind="text: totalCount"></span>, I expect to see a number ranging from 0-Integer.MAX. Instead I see the following:
As you can see, its outputting the knockout function itself, not the value of the function. Every code example I've seen, including those in the official documentation, says that I should be seeing the value, not the function. What am I doing wrong here? I can provide the full application code if needed.
Not sure, but KO view models obviously tend to bind own (not inherited through prototypes) observable properties only. So you should rewrite your code to supply totalCount observable for every social network separately.
I've wrote a helper for the 3 conditions of users being logged in. I've verified that the CurrentUsers collection is being populated on user login with console.log on client.js and browser console. I'm not sure if I'm going about this wrong or if its a little error. There are no error messages in server console or browser console but nothing shows up for the condition of 0 users being logged in.
JS:
CurrentUsers = new Meteor.Collection("currentUsers")
if (Meteor.isClient) {
Template.lobby.nousers = function() {
return CurrentUsers.find().count() === 0;
}
Template.lobby.oneuser = function(){
return CurrentUsers.find().count() === 1;
}
Template.lobby.twousers = function(){
return CurrentUsers.find().count() === 2;
}
}
if (Meteor.isServer) {
Meteor._onLogin = function (userId){
if(CurrentUsers.find({user: userId}).count()===0){
CurrentUsers.insert({user: userId})
}
}
Meteor._onLogout = function (userId){
CurrentUsers.remove({user: userId})
}
}
HTML:
<head>
<title>bubblepopper</title>
</head>
<body>
{{loginButtons align = "right"}}
</body>
<template name = "lobby">
{{#if nousers}}
<div class = "nouser">
Hello please sign in to enter the game lobby.
</div>
{{/if}}
</template>
You are missing {{> lobby}} in your body.
<body>
{{loginButtons align = "right"}}
{{> lobby}}
</body>
Also, as far as I'm aware Meteor doesn't offer login/logout hooks, so Meteor._onLogin and Meteor._onLogout won't work out of the box: https://github.com/meteor/meteor/issues/1074
This event-hooks package might be interesting for you.
I've changed the meteor example leaderboard into a voting app. I have some documents with an array and in this array there are 6 values. The sum of this 6 values works fine, but not updating and showing the values in my app.
The values are only updating, if I click on them. The problem is, that I get the booknames (it's a voting app for books) from the "selected_books" variable (previously selected_players), but I don't know how I can get the book names.
By the way: _id are the book names.
I will give you some code snippets and hope, somebody have a solution.
This is a document from my database:
{
_id: "A Dance With Dragons: Part 1",
isbn: 9780007466061,
flag: 20130901,
score20130714: [1,2,3,4,5,0],
}
parts of my html file:
<template name="voting">
...
<div class="span5">
{{#each books}}
{{> book}}
{{/each}}
</div>
...
</template>
<template name="book">
<div class="book {{selected}}">
<span class="name">{{_id}}</span>
<span class="totalscore">{{totalscore}}</span>
</div>
</template>
and parts of my Javascript file:
Template.voting.books = function () {
var total = 0;
var book = Session.get("selected_book");
Books.find({_id:book}).map(function(doc) {
for (i=0; i<6; i++) {
total += parseInt(doc.score20130714[i], 10);
}
});
Books.update({_id:book}, {$set: {totalscore: total}});
return Books.find({flag: 20130901}, {sort: {totalscore: -1, _id: 1}});
};
Thanks in advance
Don't update data in the helper where you fetch it! Use a second helper for aggregating information or a transform for modifying data items. Example:
Template.voting.books = function() {
return Books.find({}, {sort: {totalscore: -1, _id: 1}});
};
Template.books.totalscore = function() {
var total = 0;
for(var i=0; i<6; i++) {
total += this.score[i];
}
return total;
};
As a side note, DO NOT USE the construct for (i=0; i<6; i++), it's deadly. Always declare your index variables: for (var i=0; i<6; i++).