display search results made in Template events in Meteor - javascript

I have a meteor project in which, with 3 radio buttons, I can choose different variables of my search; then, with a click on the button submit of the form, the query to do in mongo is created.
This happens in events:
//global var
var resultCursor;
...
Template.device.events({
click .btn-search": function (event) {
event.preventDefault();
[...]
resultsCURSOR = myCollection.find(query, queryOptions);
})
Now, I have to display the results in the page with an helper.
I tried saving the cursor in a global var, and pass it to the helper (I cannot save in a Session var, as described here - it doesn't work)
Template.device.helpers({
searchResults: function() {
return resultCursor;
}
})
And in html
{{#each flightSearchResults}}
<div class="flight-list">
<p>Pilot: {{this.pilot}}</p>
<p>Time: {{this.date}}</p>
</div>
{{/each}}
But this does not work. I found a kind of solution here but I have to move the entire query to helpers and before doing this... is this the only solution? And is that a good practice solution?

Put the query (not the cursor) in a Session variable, like this:
Template.device.events({
click .btn-search": function (event) {
event.preventDefault();
[...]
Session.set('query', query);
})
Change your helper (note that the name was wrong):
Template.device.helpers({
flightSearchResults: function() {
return myCollection.find(Session.get('query'), queryOptions);
}
});
You don't need to change your html, and you can now remove all occurrences of resultCursor.

Related

How to listen to event made in another JS file

I have a button with a 'click' event listener, this event is made in 'example1.js', I need to create another event lister in 'example2.js' but the logic in this listener depends on what is returned from the first one.
example1.js:
button.addEventListener('click', () => {
const confirmation = confirm('Are you sure you want to do that?');
<some code here>
});
example2.js:
button.addEventListener('click', () => {
if (<confirmation from 1st listener>) {
do some stuff here...
}
});
I am aware of custom events:
const confirmationResponse = new CustomEvent('customEventListener', {detail: {isConfirm: confirmation}});
button.addEventListener('customEventListener', (event) => {
console.log(event.detail)
});
button.dispatchEvent(confirmationResponse);
I am not sure how to implement the logic between the two files since all tutorials I found are in one file, which would make the variable accessible.
If you're using classes, make it a field
Export the variable and import it in the second file
But before doing that, I would consider why you're in need to do that. If those buttons are in the same form, component, etc., they probably should sit in one class/file/etc. (my opinion)
EDIT: btw referring to title, you're not listening to another event, but reading a variable changed in another event :P

How can I pass data to an element's change() method?

I wrote an app where multiple users can edit a database in realtime. I am using socket-io to keep all users' pages up to date with any changes to the database.
All input values broadcast a change.
Say I bind a function to an input's change event:
$(".input-field").change(function(ev) {
codeThatChangesOtherInputValuesOnMyPage1;
codeThatChangesOtherInputValuesOnMyPage2;
codeThatChangesOtherInputValuesOnMyPage3;
codeThatChangesOtherInputValuesOnMyPage4;
codeThatChangesOtherInputValuesOnMyPage5;
codeThatChangesOtherInputValuesOnMyPage6;
codeThatChangesOtherInputValuesOnMyPage7;
codeThatChangesOtherInputValuesOnMyPage8;
var tableColumn = $(ev.target).attr('table-col');
var newFieldValue = $(ev.target).val()
broadcastChange(tableColumn, newFieldValue); // this is pseudo code for a socket-io emit()
});
socket.on('runThisWhenReceivingBroadcastFromServer', function(response) {
// response.data has the input element id of element I should update.
// Get the input field i should update
var theInputField = getInputField(response.data)
$(theInputField).val(getNewInputValue(response.data))
$(theInputField).change();
// I call change because I want all my code in the input's change function to run, except for the last line.
});
I have already fixed this problem, but I am repeating myself by just copying all my code from on change function and pasting it in the broadcast receiving and then just omitting the broadcastChange line. But i want to follow DRY (Don't Repeat Yourself).
Also codeThatChangesOtherInputValuesOnMyPage1; is just that, code. Its tons of code. How would you go about restructuring the code?
My first thought was to do something like this (pseudo code):
$(".input-field").change(function(ev) {
codeThatChangesOtherInputValuesOnMyPage1;
codeThatChangesOtherInputValuesOnMyPage2;
codeThatChangesOtherInputValuesOnMyPage3;
codeThatChangesOtherInputValuesOnMyPage4;
codeThatChangesOtherInputValuesOnMyPage5;
codeThatChangesOtherInputValuesOnMyPage6;
codeThatChangesOtherInputValuesOnMyPage7;
codeThatChangesOtherInputValuesOnMyPage8;
var tableColumn = $(ev.target).attr('table-col');
var newFieldValue = $(ev.target).val()
if (!ev.data.comingFromBroadcastReceiverFunction) {
broadcastChange(tableColumn, newFieldValue); // this is pseudo code for a socket-io emit()
}
});
but you can't pass data to change(); only when binding the function.
What do you guys think is the functional programming approach to this?
If you are copying and pasting code over and over, you could avoid that by separating the duplicate code into a new function, and have both the change and the socket functions call it.
For example, it could work like this:
function updateInputValue(inputField) {
codeThatChangesOtherInputValuesOnMyPage1;
codeThatChangesOtherInputValuesOnMyPage2;
codeThatChangesOtherInputValuesOnMyPage3;
codeThatChangesOtherInputValuesOnMyPage4;
codeThatChangesOtherInputValuesOnMyPage5;
codeThatChangesOtherInputValuesOnMyPage6;
codeThatChangesOtherInputValuesOnMyPage7;
codeThatChangesOtherInputValuesOnMyPage8;
}
$(".input-field").change(function(ev) {
updateInputValue($(ev.target));
var tableColumn = $(ev.target).attr('table-col');
var newFieldValue = $(ev.target).val()
broadcastChange(tableColumn, newFieldValue); // this is pseudo code for a socket-io emit()
});
socket.on('runThisWhenReceivingBroadcastFromServer', function(response) {
// response.data has the input element id of element I should update.
// Get the input field i should update
var theInputField = getInputField(response.data)
$(theInputField).val(getNewInputValue(response.data))
updateInputValue($(theInputField));
// I call change because I want all my code in the input's change function to run, except for the last line.
});

Meteor: onhashchange does not fire

I have a weird situation. It seems Meteor with Iron::Router is overriding somewhere the onhashchange event however I was unsuccessfully to track it down.
Basically if I listen for that event, it never fires for some reason. I looked and searched everywhere and can not even find any reference to onhashchange in the Meteor code base.
if(Meteor.isClient) {
window.addEventListener('hashchange', function() {
alert('changed');
});
}
This never fires - although the event is properly registered. In plain vanilla it works fine .. so I assume it's somewhere being overwritten.. any insights will be appreciated.
http://jsfiddle.net/L2dj3o7n/
Oh one more thing, this is how my URL's look like right now for testing:
http://localhost:3000/#/workflow
http://localhost:3000/#/settings/account
http://localhost:3000/#/group/add
etc
From the Router Parameters section of the Iron Router guide:
If there is a query string or hash fragment in the url, you can access
those using the query and hash properties of the this.params object.
// given the url: "/post/5?q=s#hashFrag"
Router.route('/post/:_id',
function () {
var id = this.params._id;
var query = this.params.query; // query.q -> "s"
var hash = this.params.hash; // "hashFrag"
});
Note: If you want to rerun a function when the hash changes you can do
this:
// get a handle for the controller.
// in a template helper this would be
// var controller = Iron.controller();
var controller = this;
// reactive getParams method which will invalidate the comp if any part of the params change
// including the hash.
var params = controller.getParams();
getParams is reactive, so if the hash updates, the getParams() call should invalidate and trigger a new computation for whatever you're using it for. For instance, if you wanted to dynamically render a template depending on the hash value, you should be able to do something like this...
HTML:
<template name='myTemplate'>
{{> Template.dynamic template=getTemplateFromHash}}
</template>
JS:
Template.myTemplate.helpers({
getTemplateFromHash: function() {
var hash = Iron.controller().getParams().hash;
... // do whatever you need to do with the hash to figure out the template to render
}
});

Meteor: Tracker.autorun / observeChanges & collections not working as expected

I am new to using meteor, so I am hoping to receive a very basic explanation of how these functions work, and how I am supposed to be using them. Otherwise, if there is a method that would be better suited to what I am hoping to achieve, the knowledge would be appreciated.
The functionality I was hoping to achieve:
I have a Mongo collection that contains a number value within documents that are assigned to specific users.
I will use the value that I get from the document to populate a width: xx% in some css in-line styling on a 'progressbar'. But the other use I have for it is performing some kind of 'reactive' function that runs whenever this value changes which can update the background color of this progress bar dynamically based off of the current value of the progressbar. Think 'red' for low and 'green' for high:
project.html:
<template name="progressBar">
<div id="progress-bar" style="width:{{curValue}}; background-color:*dynamicColor*;"></div>
</template>
project.js:
Progress = new Mongo.Collection("progress");
Template.progressBar.helpers({
curValue: function () {
return Progress.findOne({user: Meteor.userId()}).curValue;
}
});
The above sometimes works. But it doesn't seem to be reliable and isn't working for me right now. I get errors about cannot read property 'curValue' of undefined. From what I have researched online, that means that I am trying to access this document before the collection has loaded. But I really cannot find a direct solution or wrap my head around how I am supposed to be structuring this to avoid that error.
The next problem is observing changes to that value and running a function to change the background color if it does change.
Here are a few of the types of autorun/observe pieces of code I have tried to make work:
Session.set('currentValue', Progress.findOne({user:Meteor.userId()}).curValue);
Tracker.autorun(function(){
var currentValue = Session.get('currentValue');
updateColor(currentValue);
});
var currentValue = Progress.findOne({user:Meteor.userId()}).curValue);
var handle = currentValue.observeChanges({
changed: function (id, currentValue) {
updateColor(currentValue);
}
});
To sum up the question/problem:
I want to use a value from a mongo db document in some in-line css, and also track changes on that value. When the value changes, I want a function to run that will update the background color of a div.
Update
Using #Ethaan 's answer below, I was able to correct my subscription/template usage of my collection data. I did a bit more digging and come to a greater understanding of the publish/subscribe methods and learned how to properly use the callbacks on subscriptions to run my Tracker.autorun function at the appropriate time after my collection had loaded. I was able to expand on the answer given to me below to include a reactive Tracker.autorun that will run a function for me to update my color based on my document value.
The code that I ultimately got working is as follows:
project.js
if (Meteor.isClient) {
Progress = new Mongo.Collection("progress");
Template.content.onCreated(function () {
var self = this;
self.autorun(function () {
self.subscribe("progress", function(){
Tracker.autorun(function(){
var query = Progress.findOne({user: Meteor.userId()}).level;
changeColor(query);
});
});
});
});
Template.content.helpers({
value: function(){
return Progress.findOne({user: Meteor.userId()}).level;
}
});
function changeColor(value){
//run some code
}
}
if (Meteor.isServer) {
Progress = new Mongo.Collection("progress");
Meteor.publish("progress", function () {
return Progress.find({user: this.userId});
});
}
project.html
<head>
<title>Project</title>
</head>
<body>
{{> standard}}
</body>
<template name="standard">
...
{{> content}}
</template>
<template name="content">
{{#if Template.subscriptionsReady}}
{{value}}
{{else}}
0
{{/if}}
</template>
that means that I am trying to access this document before the
collection has loaded
Seems like you get the problem, now lets get ride to some possible solutions.
Meteor version 1.1
If you are using the new meteor version 1.1 (you can check running meteor --version)
use this.
First on the onCreated function use this.
Template.progressBar.onCreated(function () {
var self = this;
self.autorun(function () {
self.subscribe("Progress");
});
});
See more about subscriptionReady on the DOCS.
Now on the HTML use like this.
<template name="progress">
{{#if Template.subscriptionsReady}}
<div id="progress-bar" style="width:{{curValue}}; background-color:*dynamicColor*;"></div>
{{else}}
{{> spinner}} <!-- or whatever you have to put on the loading -->
{{/if}}
</template>
Meteor under 1.0.4
You can have on the router something like a waitOn:function(){}
waitOn:function(){
Meteor.subscribe("Progress");
}
or since helper are asynchronous do something like this (not recommendable).
Template.progressBar.helpers({
curValue: function () {
query = Progress.findOne({user: Meteor.userId()}).curValue;
if(query != undefined){
return query;
}else{
console.log("collection isn't ready")
}
}
});

Getting an assigned jQuery variable to "re-query"

I'm trying to use jQuery to build a home-made validator. I think I found a limitation in jQuery: When assigning a jQuery value to a json variable, then using jQuery to add more DOM elements to the current page that fit the variable's query, there doesn't seem to be a way to access those DOM elements added to the page which fit the json variable's query.
Please consider the following code:
var add_form = {
$name_label: $("#add-form Label[for='Name']"),
$name: $("#add-form #Name"),
$description_label: $("#add-form Label[for='Description']"),
$description: $("#add-form #Description"),
$submit_button: $("#add-form input#Add"),
$errors: $("#add-form .error"),
error_marker: "<span class='error'> *</span>"
}
function ValidateForm() {
var isValid = true;
add_form.$errors.remove();
if (add_form.$name.val().length < 1 ) {
add_form.$name_label.after(add_form.error_marker);
isValid = false;
}
if (add_form.$description.val().length < 1) {
add_form.$description_label.after(add_form.error_marker);
isValid = false;
}
return isValid
}
$(function(){
add_form.$submit_button.live("click", function(e){
e.preventDefault();
if(ValidateForm())
{
//ajax form submission...
}
});
})
An example is availible here: http://jsfiddle.net/Macxj/3/
First, I make a json variable to represent the html add form. Then, I make a function to validate the form. Last, I bind the click event of the form's submit button to validating the form.
Notice that I'm using the jQuery after() method to put a span containing an '*' after every invalid field label in the form. Also notice that I'm clearing the asterisks of the previous submission attempt from the form before re-validating it (this is what fails).
Apparently, the call to add_form.$errors.remove(); doesn't work because the $errors variable only points to the DOM elements that matched its query when it was created. At that point in time, none of the labels were suffixed with error_marker variable.
Thus, the jQuery variable doesn't recognize the matching elements of it's query when trying to remove them because they didn't exist when the variable was first assigned. It would be nice if a jQuery variable HAD AN eval() METHOD that would re-evaluate its containing query to see if any new DOM elements matched it. But alas...
Any suggestions?
Thanks in advance!
You are correct that a jQuery object is not "live" – that is, the set of elements in the jQuery object is not dynamically updated. (This is a good thing.)
If you really want to update an arbitrary jQuery object, you can get the selector used to create the object from .selector:
var els = $('#form input');
els.selector // '#form input'
So you could do els = $(els.selector); to re-query the DOM.
Note, however, that if you modified the collection after the initial selector (functions like add, filter, children, etc.), or if the jQuery object was created without using a selector (by passing a DOMElement), then .selector will be pretty much useless, since the selector will be empty, incorrect, or even potentially invalid.
Better is to re-structure your code in such a way that you aren't holding on to a stale jQuery object; the other answers make some good suggestions.
Also, please make sure you're validating input server-side too!
For the objects that are going to be changing, instead of making the JSON object reference a static value, make it a function:
$errors: function() { return $("#add-form .error"); },
since it's a function, it will re-evaluate the error fields every time you call add_form.$errors().
Your approach to the problem has got many structural problems:
Avoid using global variables
There may not always be just one form on the page that you are validating. What if one day you will decide that you will have several forms. Your add_form variable is a global variable and therefore would be conflicting.
Do not use the submit button click event for detecting form submissions.
What if a form is submitted by a js call like $("form").submit(); or by the enter key?
Store the selectors instead of the DOM objects if you are not certain that the objects already exist at the creation time of the configuration object.
.live is deprecated. Use .on instead.
It is 3. that will solve your actual problem, but I strongly recommend addressing all 4 issues.
For 2, the best place to attach the validator is not on the submit button, but on submit event of the form. Something like this:
$("#add-form").submit(function(event) {
event.preventDefault();
if (validateForm(this))
$.ajax({
url: $(this).attr("action"),
data: $(this).serialize(),
//ETC
});
});
Note how the form is now also much easier to access. Your configuration object no longer needs to store a reference to the submit button.
Your configuration object could now be simplified to be something like this:
{
name_label: "Label[for='Name']",
name: "#Name",
description_label: "Label[for='Description']",
description: "#Description",
errors: ".error",
error_marker: "<span class='error'> *</span>"
}
Within validateForm, you can use these selector as follows:
var $name_label = $(configuration.name_label, this); //Finds the label within the current form.
Now, to allow different configuration parameters for each form use something like this:
function enableValidation(form, configuration) {
$.extend(configuration, {
//Default configuration parameters here.
});
function validateForm(form) {
//Your original function here with modifications.
}
$(form).submit(funciton(e) {
if (!validateForm(this))
e.preventDefault();
});
}
function enableAjax(form) {
$(form).submit(function(e){
if (!e.isDefaultPrevented()) {
e.preventDefault();
$.ajax(...);
}
});
}
$(function() {
enableValidation("#add-form", {/*specialized config parameters here*/});
enableAjax("#add-form");
});

Categories

Resources