Meteor - button click not updating value - node.js

I am trying to get a random document in the collection and display it on the page. It is successful every time I load the page, but I want a button to do the work as well.
main.html
<head>
<title>test</title>
</head>
<body>
<h1>Random Question</h1>
{{> question}}
</body>
<template name="question">
<button>Click Me</button>
{{#each object}}
{{question}}
{{a}}
{{b}}
{{c}}
{{d}}
{{answer}}
{{points}}
{{/each}}
</template>
main.js
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import './main.html';
Resolutions = new Mongo.Collection('quiz');
Template.question.created = function () {
var random = get_random();
this.question = new ReactiveDict();
this.question.set('object', random);
};
function get_random(){
var collection_size = Resolutions.find().count();
var random = Math.floor(Random.fraction() * collection_size);
// choose a random item by skipping N items
var item = Resolutions.findOne({},{
skip: random
});
var objArray = $.makeArray(item);
return objArray;
}
Template.question.helpers({
object: function () {
return get_random();
}
});
Template.question.events({
'click button': function (event, template) {
// increment the counter when button is clicked
var random = get_random();
template.question.set('object', random);
}
});
There is no error message when I load the page or click the button.
Any help is appreciated.
Btw, what is the object inside "this.question.set('object', random);". Maybe that's where my issue is.

You can considerably simplify your code and also solve your problem by not picking a random object in your helper - that will run many times, even when you don't expect it to. Also since you're only viewing a single object, use {{#with }} instead of {{#each }} - this will avoid the array conversion step.
html:
<template name="question">
<button>Click Me</button>
{{#with object}}
{{question}}
{{a}}
{{b}}
{{c}}
{{d}}
{{answer}}
{{points}}
{{/with}}
</template>
js:
import { Template } from 'meteor/templating';
import './main.html';
Resolutions = new Mongo.Collection('quiz');
Template.question.created = function () {
setRandom(); // initialize the random selection
};
function setRandom(){
var collection_size = Resolutions.find().count();
var random = Math.floor(Random.fraction() * collection_size);
Session.set('random',random);
}
Template.question.helpers({
object: function () {
return Resolutions.findOne({},{ skip: Session.get('random') });
}
});
Template.question.events({
'click button': function (event, template) {
setRandom();
}
});

Related

Vue 3: Bind ref value to object property

I'm running into an issue where I'm trying to bind an object property to a ref in Vue, using the new composition API. I'm expecting the template to re-render with the new value after setting the ref value, but I'm however getting a RefImpl {} instead. How would I solve this?
<template>
<v-card>
<v-card-text class="pa-2">
<div v-for="(social, index) in socials" :key="index">
<p>{{ social.value }}</p>
</div>
</v-card-text>
</v-card>
</template>
<script>
import { onMounted, ref } from "#vue/composition-api/dist/vue-composition-api";
export default {
setup() {
const testVariable = ref(0);
const socials = [
{
value: testVariable,
}
];
onMounted(() => {
setTimeout(() => testVariable.value = 100, 1000);
});
return {
socials,
}
},
}
</script>
<style scoped></style>
Your socials variable does not unref inner refs in template. Basically what you have to do in your template is using social.value.value. So I think renaming that variable would be better to something like
const socials = [
{
variable: testVariable,
}
];
So that you could do social.variable.value.
Details from Vue docs:
Note the unwrapping only applies to top-level properties - nested access to refs will not be unwrapped: Read More
Looks like your code works:
const { onMounted, ref } = Vue
const app = Vue.createApp({
setup() {
const testVariable = ref(0);
const socials = [{ value: testVariable, }];
onMounted(() => {
setTimeout(() => testVariable.value = 100, 1000);
});
return { socials, }
},
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3.2.29/dist/vue.global.prod.js"></script>
<div id="demo">
<div v-for="(social, index) in socials" :key="index">
<p>{{ social.value }}</p>
</div>
</div>

Display single item with Vue.js

I have a list of items where the title is a link to display a detailed view of the item. Click the title and it correctly goes to url + Id. In the Vue tolls the detail page retrieves the item with matching ID but as and array not an object and the template does not display any properties - what am I missing?
<script>
import axios from "axios";
export default {
name: "Report",
data() {
return {
report: {}
};
},
mounted: function() {
this.getReport();
},
methods: {
getReport() {
let uri = "http://localhost:5000/api/reports/" + this.$route.params.id;
axios.get(uri).then(response => {
this.report = response.data;
});
}
}
};
</script>
The template is so
<template>
<v-content>
<h1>report detail page</h1>
<p>content will go here</p>-
<h3>{{ report.month }}</h3>
<pre>{{ report._id }}</pre>
</v-content>
</template>
any comments appreciated
url + Id
It sounds like your issue is that you are receiving an array not an object.
You can pull out objects encapsulated inside arrays easily.
For example, if we had the following data:
var bus1 = {passengers:10, shift:1}
var busArr = [bus1]
which we can assert: busArr === [{passengers:10, shift:1}]
We could then pull out bus1 by referencing the index 0:
var bus1New = busArr[0]
If you want to avoid the data transformation and just output the structure you can consider a v-for in your template.
<p v-for="val in report">
_id: {{val._id}}
<br>
month: {{val.month}}
</p>

How to access elements in the current template in meteor?

I have a template like this,
<template name = "foo">
<p id="loading" >LOADING...</p>
<p> {{theResult}} </p>
</template>
This is how I create foos,
// foos = [a, b, c, d]. With each button click I add a new item to the array
{{#each foos}}
{{> foo .}}
{{/each}}
And how a foo works,
Template.foo.created = function(){
var name = Template.currentData();
api_call(name, function(err, result){
Session.set(name, result);
});
}
Template.foo.helpers({
'theResult': function(){
var name = Template.currentData();
if(Session.get(name)) {
$("#loading").hide();
return Session.get(name);
} else {
return "";
}
}
})
So my expectation is to when the data came from the api_call, to hide "LOADING..." para, and to show the result in theResult.
The result is showing correctly. My problem is "LOADING..." is only get hidden on the top most foo. Not the other ones.
How can I fix this?
EDIT:
As suggested instead of,
$("#loading").hide();
I used,
Template.instance().$("#loading").hide();
This didn't work too :)
This is how I'd do it
Template... if theResult is undefined, the else path will be rendered.
<template name="foo">
{{#with theResult}}<p> {{this}} </p>
{{else}}<p id="loading" >LOADING...</p>
{{/with}}
</template>
Javascript... theResult is a simple Session.get call
Template.foo.helpers({
theResult: function(){
var name = Template.currentData();
return name && Session.get(name);
}
});
Thanks to Meteor templating engine, you can access a template scoped jQuery object that will only return elements within the corresponding template.
Template.foo.helpers({
'someText': function(){
var template = Template.instance();
template.$('p').changeSomeattr();
return Session.get('myPara');
}
});

Custom Handlebars Helper with partial as hash argument

I'm trying to create a custom handlebars helper, and I want to be able to pass it a "base-template" and a "partial"..
So what it should do is render the base template and then render whatever partials is passed as the second parameter.
I have the following right now:
module.exports.register = function(Handlebars, options) {
var assembleOpts = options || {};
Handlebars.registerHelper("sgComponent", function (template, partial, options) {
// Default options
var opts = {
cwd: '',
src: '',
glob: {}
};
options = _.defaults(options.hash, assembleOpts.sgComponent, opts);
var partialContent, partialContext;
// Join path to 'cwd' if defined in the helper's options
var cwd = path.join.bind(null, options.cwd, '');
var src = path.join.bind(null, options.src, '');
glob.find(src(partial), options.glob).map(function(path) {
partialContext = yfm.extract(path).context;
partialContent = yfm.extract(path).content;
});
return glob.find(cwd(template), options.glob).map(function(path) {
var context = yfm.extract(path).context;
var content = yfm.extract(path).content;
return {
path: path,
context: processContext(grunt, partialContext),
content: content
};
}).map(function (obj) {
var template = Handlebars.compile(obj.content);
return new Handlebars.SafeString(template({content: obj.context}));
});
});
var processContext = function(grunt, context) {
grunt.config.data = _.defaults(context || {}, _.cloneDeep(grunt.config.data));
return grunt.config.process(grunt.config.data);
};
};
And right now I'm using my helper like this:
{{{sgComponent 'path/to/basetemplate/basetemplate.hbs' 'path/to/partial/partial.hbs'}}}
I'm a little stuck right now. At the moment I can only figure out how to render either the base template or the partial.. Or render the base template but with the context from the partial (it's yaml font matter) What I would like to achieve is the basetemplate being rendered and the partials content being render inside of it, with whatever context defined in the partial.
Like so (base template):
<div class="sg-component">
<!-- Markup -->
<div class="sg-component__markup">
{{partial}}
</div>
<!-- Documentation -->
<div class="sg-component__documentation">
{{#markdown}}
~~~markup
{{partial}}
~~~
{{/markdown}}
</div>
</div>
Partial:
---
context: context stuff here
---
<h1 class="title--huge">This is a very large header</h1>
<h2 class="title--xlarge">This is a large header</h2>
<h3 class="title--large">This is a medium header</h3>
<h4 class="title--medium">This is a moderate header</h4>
<h5 class="title--small">This is a small header</h5>
<h6 class="title--xsmall">This is a tiny header</h6>
Thanks in advance!
Dan
So, I fixed it my self! Hurray..
I sat down it thought it through and came to the conclusion that I only needed to register the second hash argument as a partial.
So I added this after the Handlebars.compile(obj.content);
Handlebars.registerPartial("sgComponentContent", partial);
And then within my basetemplate I can now use {{> sgComponentContent}}
Awesome!

Trigger 'click' or 'onselectionchanging' on WinJS ListView

I a WinJS page with the following HTML snippet:
<div class="productivity-view">
<div class="categorylist" aria-label="Category List">
</div>
<div class="itemlist" aria-label="Work Item List">
</div>
</div>
I am able to programmatically initialize two lists:
var categories = new WinJS.Binding.List(list),
categoryListEl = document.querySelector(".categorylist"),
catList = new WinJS.UI.ListView(categoryListEl, {
itemDataSource: categories.dataSource,
itemTemplate: document.querySelector('.categoryitemtemplate'),
onselectionchanging: function(event) {
var items = event.detail.newSelection.getItems();
items.done(function(selections) {
var selection = selections[0],
item = selection.data,
boxes = categoryListEl.querySelectorAll('.win-itembox');
boxes[catList.currentItem.index].classList.remove('active');
boxes[selection.index].classList.add('active');
workItemHeader.textContent = item.title;
workList.itemDataSource = new WinJS.Binding.List(item.workitems).dataSource;
});
}
});
var workItemListEl = document.querySelector(".itemlist"),
workList = new WinJS.UI.ListView(workItemListEl, {
itemTemplate: document.querySelector('.workitemtemplate'),
onselectionchanging: function() {}
});
The code above listens for the onselectionchanging event on the first list, in which case the event data carries some information used to fill out the second list.
How can I programmatically trigger the onselectionchanging on the first item in the first list?
We figured it out. Solution code below. We wanted to trigger selection of the very first item in the first list, which would then put some information into the second list 'onselectchanging'. This involved listening to the 'onloadingstatechanged' event for the 'complete' state, and then adding the first item in the list to the first list's selection (the missing piece of API knowledge).
var catList = new WinJS.UI.ListView(categoryListEl, {
...
onselectionchanging: function (event) {
...
},
onloadingstatechanged: function () {
if (this.winControl.loadingState === "complete") {
// try to select the first item in the list
catList.selection.add({ key: 0, index: 0, hasFocus: true, showFocus: false });
}
}
});
var workItemListEl = document.querySelector("#itemListControl"),
workList = new WinJS.UI.ListView(workItemListEl, {
...,
onselectionchanging: function () {
....
}
});

Resources