Meteor.autorun() not working on client when insert occures - meteor

i have been knocking my head for 2 days now in that .
am creating a search engine, am creating queries dynamically using Meteor Framwork, the queries are working fine and when i search i can rebind the UI (Table in My Case) with the dynamic data query output.
however if an insert/update/delete operation occures the data object
and the UI (html Table) is not updating.
which means that the template is not re-rendered when the data object changes.
Template.search.rendered = function () {
Meteor.autorun(function() {
alarmsData = Alarms.find(getSearchSelector($('#searchTxt').val(), $('#startTimeTxt').val(), $('#endTimeTxt').val())).fetch()
console.log("rendered")
//alarmsData = Alarms.find({},{sort: {timestamp: "desc"} }).fetch();
searchControls(alarmsData)
getConsole(alarmsData, ".console")
$('#badge').html(alarmsData.length)
})
}
the get console function is just reading the array from teh search and creating an html table (this is working fine)
as for the begining i am creating a simple query as the default for my search. and then am changing this query whenever user changes the search criteria. i can notice that only the first instance of teh data object is kept and tracked for changes, so if the second search criteria resides within the first one, it's updating the UI, if not nothing happenes
i have used Meteor.autorun(function(){}) function however i traced it's execution with console.log and i can see it's no excuting when i insert data in the database for the same collection.

One, I believe you are trying to use Deps.autorun. Also, there is nothing in your autorun that seems to be dependent on a reactive source. Since alarmsData is taking a snapshot of data it won't care when Alarms has data changing.
Second, I would probably approach this with a redirect. I would compile my data, and redirect to the same page, allowing the server to handle the querying for me. This easily allows you to jump to this page from anywhere else with a prefilled query in the parameters (because the route would then handle it) and also gives a visual change to the navigation bar when a search has happened (just like every other search engine). You would do something like this on a button click:
var query = {},
path;
query.text = encodeURIComponent($('#searchTxt').val()),
query.start = encodeURIComponent($('#startTimeTxt').val()),
query.end = encodeURIComponent($('#endTimeTxt').val()),
// redirect to current path
path = Router.routes[Router.current().route.name].path({}, {
query: query
});
Router.go( path );
In your router you would just pass the query into your server and route as a data object (assuming you are using iron-router):
this.route( "search", {
path: "/search",
waitOn: function() {
return [
Meteor.subscribe( "searchAlarms", _.omit( this.params, "hash" ) ),
]
},
data: function () {
return { "query": _.omit( this.params, "hash" ) };
}
});
This will not only give you the query data that was used for the search (in your template) but the server can now handle the search for you! Your Alarms data now holds all of the documents needed to display to the user and you no longer need to subscribe to all your Alarms. This is also great because it is automatically reactive. So if a new Alarm matches your query filter it will automatically be passed down to the client and displayed to the user without needing to setup any extra dependencies/autoruns.
Note though, that if you are subscribing to Alarms elsewhere you will still need to do filtering client-side.

What a strange meteor code…
The "rendered" code method code is called once you will be rendering the search template
getSearchSelector($('#searchTxt').val() is not reactive, my advise is to use the session variable to put your search criteria inside and use this same session to inject the find parameters inside.
Are you looking for displaying all the alarms Data ?
function getAlarms()
{
var text = Session.get("text");
var from = Session.get("start");
var to = Session.get("end");
var filter = getSearchSelector(text, from, to);
return Alarms.find(filter);
}
Template.search.alarms = function () {
return getAlarms();
}
Template.search.alarmsCount = function () {
return getAlarms().count();
}
Template.search.events({
'keypress input[name=text]' : function(e,o)
{
var val = $("input[name= text]").val()
Session.set("text", val);
},
'keypress input[name=start]' : function(e,o)
{
var val = $("input[name=start]").val()
Session.set("start", val);
},
'keypress input[name=end]' : function(e,o)
{
var val = $("input[name=end]").val()
Session.set("end", val);
}
});
// And your template will look something like:
<template name="search">
Search alarms
<input type="text" name="text" placeholder="Enter your text here…"/>
<input type="text" name="start" placeholder="start time"/>
<input type="text" name="end" placeholder="end time/>
There is {{alarmsCount}} alarms(s);
{{#each alarms}}
Alarm object: {{.}}
{{/each}}
</template>

I Guess its Solved it by using Session.set & get, and automatically subscribing to the Serevr and send the dynamic Query.
Check the below Code
Template.zConsole.rendered = function () {
Session.set("obj", getSearchSelector($('#searchTxt').val(), $('#startTimeTxt').val(), $('#endTimeTxt').val()))
Deps.autorun(function (){
Meteor.subscribe("dynamicAlarms", Session.get("obj"))
console.log("Count from AutoRun ==> " + Alarms.find(Session.get("obj")).count())
})
}
on the server
Meteor.publish('dynamicAlarms',function (searchObj) {
return Alarms.find(searchObj)
})
& it works perfect with less code.

Related

how to create a global variable for every entrance to the website?

I have global array that works just fine and stores the URL's of the chosen images from the user after i click submit in the form.
the problem is when i want to submit another form, the global array will still have the URL's of the previous submission.
what i want to do is to create an array for every user to store his URL's, one he click submit, the array will be dropped or deleted. so if there were multiple users using the same function, every one of them will have his own array to store his URL's
How do i do this?
this is what i have tried but when i click on submit on the form page, nothing happens
first, this is the method that returns the url of the chosen image by the user, the method exists in both folder (both/file.js)
storeUrlInDatabaseSS: function( url ) {
check( url, String );
Modules.both.checkUrlValidity( url );
try {
return url;
} catch( exception ) {
return exception;
}
}
then i created the session variables in the client side (client/file.js)
Session.set("screenshots", []);
Session.set("i", 0);
var screenshots = Session.get("screenshots");
var i = Session.get("i");
and here i store the url in the array
let _addUrlToDatabaseSS = ( url ) => {
screenshots[i++] = url;
Session.set("screenshots", screenshots);
};
and am using Meteor Collection Hook Package
and i added these two lines of code which should be excited after the user press submit, they exist inside "client/files.js" directory
Products.before.insert(function (userId, doc) {
doc.screenShots = Session.get("screenshots");
});
now whenever i click submit nothing happens, i think the problem is because nothing is actually stored inside the screenShots attribute in the collection here
screenShots: {
type: [String]
},
when i set the screenShots attribute to an empty array by default like the code below, the submit button works
screenShots: {
type: [String],
autoValue: function() {
return [];
}
},
I tried to use the other way of using AutoForm.hooks
AutoForm.hooks({
submitPostForm: {
before: {
insert: function(doc) {
doc.$set.screenShots = Session.get("screenshots");
}
}
}
});
the is my form in the .html file
{{> quickForm collection="Products" id="submitPostForm"
type="method" meteormethod="submitPost" omitFields="createdAt, previewImage, screenShots, sourceCode, userID"}}
and this is the method triggered once the user submit the form, it exist in the server side.
submitPost: function (app) {
// Console.log('new App:', app);
check(app, {
title: String,
description: String,
category: String,
price: Number
});
Products.insert(app);
}
for some reason my before hook isn't working and i can't see why!
what am i doing wrong here?
One of the ways to create a global array per user is to use Session. This way it is also possible to persist the data across the app (only client-side).
Simple way to use Session is thus:
Create an array in Session called url_list:
Session.set("url_list", []);
Retrieve the array from Session:
var url_list = Session.get("url_list");
Make changes to url_list:
url_list.push(someData);
Store url_list in the Session again:
Session.set("url_list", url_list);
Note: Session can only be used on client-side and all related code should be on the client-side.
More about Session.
PERSISTING DATA TO SERVER-SIDE:
The best way to persist the url_list to the server, would be to insert a new document into the database collection containing the Session data.
insertToDB = function() {
var url_list = Session.get('url_list');
Products.insert({
'url_list': url_list
});
Session.set('url_list', []); // To empty the client-side list
}

Is there a way to update the URL in Flow Router without a refresh/redirect?

Is there a way to update a part of the URL reactively without using FlowRouter.go() while using React and react-layout?
I want to change the value in the document that is used to get the document from the DB. For example, if I have a route like ~/users/:username and update the username field in the document, I then have to user FlowRouter.go('profile', {data}) to direct the user to that new URL. The "old" route is gone.
Below is the working version I have, but there are two issues:
I have to use FlowRouter.go(), which is actually a full page refresh (and going back would be a 404).
I still get errors in the console because for a brief moment the reactive data for the component is actually wrong.
Relevant parts of the component are like this:
...
mixins: [ReactMeteorData],
getMeteorData() {
let data = {};
let users = Meteor.subscribe('user', {this.props.username});
if (user.ready())
data.user = user;
return data;
}
...
updateName(username) {
Users.update({_id:this.data.user._id}, {$set:{username}}, null, (e,r) => {
if (!e)
FlowRouter.go('profile', {username});
});
},
...
The route is like this:
FlowRouter.route('/users/:username', {
name: 'profile',
action(params) {
ReactLayout.render(Main, {content: <UserProfile {...params} />});
}
});
The errors I get in the console are:
Exception from Tracker recompute function:
and
TypeError: Cannot read property '_id' of undefined

Meteor client side collection needs to have all data populated before anything else

I'm trying to use a client side collection as a site configuration system. I insert documents representing my different pages, and the iron-router and navigation tabs all use them to determine what pages they are and what templates are represented by them. Each page uses a {{> contentTemplate}} inclusion helper to load it's relevant template.
It all works great, when the data has all loaded. When I restart the app on certain pages, the data hasn't loaded yet, and I receive the Exception from Deps recompute function: Error: Expected null or template in return value from inclusion function, found: undefined error.
Here's my javascript:
StoriesArray = [
{ category: 'teaching', contentTemplate: 'teachingHome', title: 'Teaching Home'},
...
];
Stories = new Meteor.Collection(null);
StoriesArray.forEach(function (story, index) {
story._id = index + '';
Stories.insert(story);
});
// in main.js
Template.teachingPost.contentTemplate = function() {
console.log(this);
console.log(this.contentTemplate);
return Template[this.contentTemplate];
};
// in router.js
this.route('teaching', {
layoutTemplate: 'teachingPost',
data: function() { return Stories.findOne({contentTemplate: 'teachingHome', category: 'teaching'}); }
});
The console logs in the contentTemplate helper above log twice, the first time as this:
Object {} main.js?1f560c50f23d9012c6b6dd54469bb32b99aa4285:45
undefined main.js?1f560c50f23d9012c6b6dd54469bb32b99aa4285:46
and the second time as this:
Object {category: "teaching", contentTemplate: "teachingHome", title: "Teaching Home"} main.js?1f560c50f23d9012c6b6dd54469bb32b99aa4285:45
teachingHome main.js?1f560c50f23d9012c6b6dd54469bb32b99aa4285:46
so the router is simply trying to load this data too early.
I've tried putting the StoriesArray loading process into different files all over my app, including lib, and even tried putting it into Meteor.startup, but it's always the same result.
The normal iron-router waitOn/subscription pattern doesn't really apply here, since this is a client side collection built with null, that has no server representation. I don't want this to have server representation, because this is static content that there's no need to go to my server for.
How do I ensure this information is done before continuing?
Untested, but per Iron Router's docs on waitOn:
Returning a subscription handle, or anything with a ready method from the waitOn function will add the handle to a wait list.
Also in general it's better to use find with data, rather than findOne, as find will return an empty cursor when the collection is empty as opposed to findOne returning undefined. So try this:
// in router.js
this.route('teaching', {
layoutTemplate: 'teachingPost',
data: function() {
return Stories.find({contentTemplate: 'teachingHome', category: 'teaching'});
},
waitOn: function() {
var handle = {};
handle.ready = function() {
if (Stories.find().count() !== 0)
return true;
else
return false;
}
return handle;
}
});
And adjust your Template.teachingPost.contentTemplate function to work with a cursor rather than an object.

Why is Meteor removing an object when observing a collection?

Background
I have "Lists" and "Products" collections, Products belong to a List
A List has a description, from which products are generated
On startup, a new List is created that's unique for that visitor
The List id is stored in the Session
What I Want
I want Products to be generated when the description of a List changes.
The first step is that when the list for the current visitor is changed, I want a new product to be inserted.
I get the feeling I'm going about this totally wrong...
The Problem
The product is inserted, appears in the browser for a split second, then vanishes. It's been removed by Meteor.
Code
Products = new Meteor.Collection("products");
Lists = new Meteor.Collection("lists");
if (Meteor.isClient) {
Meteor.startup(function () {
var my_list_id = Lists.insert({description: "Default list"});
Session.set("my_list", my_list_id);
var observed = Lists.find({_id: my_list_id}).observe({
changed: function (newDocument, oldDocument) {
Products.insert({list: newDocument._id, name: newDocument.description});
}
});
});
toggleElement = function (elementName) {
if(editedElementIs(elementName)) {
var newListDescription = $('textarea').val();
Lists.update(Session.get("my_list"), {description: newListDescription});
setEditedElement("");
} else {
setEditedElement(elementName);
}
};
// Including the rest in case I've misunderstood something.
// I don't see how any of this could cause the issue.
setEditedElement = function (elementName) {
return Session.set("edited_element", elementName);
};
editedElementIs = function (elementName) {
return Session.get("edited_element") == elementName;
};
Handlebars.registerHelper('editedElementIs', editedElementIs);
Handlebars.registerHelper('products', function() {
return Products.find({list: Session.get("my_list")});
});
Template.list_form.listDescription = function () {
return Lists.findOne({_id: Session.get("my_list")}).description;
};
Template.adminbar.events({
'click a#editlist' : function () {
toggleElement("list");
},
'click a#editsidebar' : function () {
toggleElement("sidebar");
}
});
}
if (Meteor.isServer) {
Meteor.startup(function () {
});
}
What I've Tried
Obviously, I can just do this:
if(editedElementIs(elementName)) {
var newListDescription = $('textarea').val();
Products.insert({list: Session.get("my_list"), name: newListDescription});
Lists.update(Session.get("my_list"), {description: newListDescription});
...
But that's writing clumsy update code that I'd like to house in an observer.
It looked like the product was being removed. So I've observed when a product is removed thus:
Products.find({list:my_list_id}).observe({
removed: function (oldDocument) {
throw error("wow");
console.log("Removed Product" + oldDocument);
}
})
and this observer is called immediately after the Product is inserted.
I get the stack trace:
at Object.Products.find.observe.removed (http://localhost:3000/ListyMeteor.js?2d867b7481df6389658be864b54d864151e87da5:22:15)
at Object.cursor.observeChanges.removed (http://localhost:3000/packages/minimongo/minimongo.js?daa88dc39d67b40b11d6d6809d72361f9ef6a760:909:52)
at http://localhost:3000/packages/minimongo/minimongo.js?daa88dc39d67b40b11d6d6809d72361f9ef6a760:275:15
at _.extend.runTask (http://localhost:3000/packages/meteor/fiber_stubs_client.js?52687e0196bc1d3184ae5ea434a8859275702d94:30:11)
at _.extend.flush (http://localhost:3000/packages/meteor/fiber_stubs_client.js?52687e0196bc1d3184ae5ea434a8859275702d94:58:10)
at _.extend.drain (http://localhost:3000/packages/meteor/fiber_stubs_client.js?52687e0196bc1d3184ae5ea434a8859275702d94:66:12)
at LocalCollection.remove (http://localhost:3000/packages/minimongo/minimongo.js?daa88dc39d67b40b11d6d6809d72361f9ef6a760:500:22)
at Object.self._connection.registerStore.update (http://localhost:3000/packages/mongo-livedata/collection.js?682caa185350aa26968d4ffc274579a33922f0e6:109:32)
at Object.store.(anonymous function) [as update] (http://localhost:3000/packages/livedata/livedata_connection.js?5d09753571656c685bb10c7970eebfbf23d35ef8:404:48)
at http://localhost:3000/packages/livedata/livedata_connection.js?5d09753571656c685bb10c7970eebfbf23d35ef8:984:19
It looks like Meteor is flushing the Products collection on the client side.
I'm clearly misunderstanding how Meteor works.
Any ideas on why this is happening?
Update 1
It looks like this is happening because insert is being called within an observer:
Why does meteor undo changes to collections nested in an observer method?
I'll post back here once I confirm.
Is autosubscribe turned on or off?
If you turn autosubscribe off, it could happen that your client updates the server copy and then on a subsequent update from the server - does not get all the items because its not subscribed to that collection.
Easiest way to check is to query the mongo db -
meteor mongo
Query the mongo db if your product has been added to the document.
If it has, then it is an autosubscribe issue -
You will have to create publish (on server) and subscribe (on client) methods as given here http://docs.meteor.com/#meteor_publish

ASP.NET MVC: Save multiple values on autocomplete

I have a mysql database with the tables "deliverables", "tags" and "deliverables_has_tags". I want to link tags to a deliverable.
This is what I do in my javascript file:
<script type="text/javascript" language="javascript">
$(function () {
var object = {};
$.ajax({
type: "GET",
url: "/Deliverable/Tags",
dataType: "json",
success: function (data) {
object.tags = data;
}
});
function split(val) {
return val.split(/,\s*/);
}
function extractLast(term) {
return split(term).pop();
}
$("#tags")
// don't navigate away from the field on tab when selecting an item
.bind("keydown", function (event) {
if (event.keyCode === $.ui.keyCode.TAB &&
$(this).data("ui-autocomplete").menu.active) {
event.preventDefault();
}
})
.autocomplete({
minLength: 0,
source: function (request, response) {
// delegate back to autocomplete, but extract the last term
response($.ui.autocomplete.filter(
object.tags, extractLast(request.term)));
},
focus: function () {
// prevent value inserted on focus
return false;
},
select: function (event, ui) {
var terms = split(this.value);
// remove the current input
terms.pop();
// add the selected item
terms.push(ui.item.value);
// add placeholder to get the comma-and-space at the end
terms.push("");
this.value = terms.join(", ");
return false;
}
});
});
</script>
I can add multiple tags in my textbox.
But now I want to save this in my repository.
In my Action method in controller:
repository.AddDeliverable(model.Title, model.Description, model.UsernameID, data, datatwo, model.VideoUrl, model.AfstudeerrichtingID, model.ProjectID);
Tags action:
public JsonResult Tags()
{
var data = (repository.GetTags()).ToArray();
return Json(data, JsonRequestBehavior.AllowGet);
}
In my repository:
public IQueryable<string> GetTags()
{
return from tag in entities.tags
orderby tag.tag_name
select tag.tag_name;
}
I have no clue how to save this in my database.
Can anybody help me?
If I correctly understood your question, you have implemented your tag handling as follows:
There is MVC action method that returns the view with input placeholder containing no data
The placeholder itself is probably input type=text with id=tags
On 'dom ready' you fire ajax request to retrieve your tags from database, json-serialized as array; when it arrives you store it to tags variable (no error handling(!))
At the same time you decorate your input with jqueryui autocomplete that reacts on user input and returns items from the tags variable
Since input already contains tags (comma separated), your filter is first letters of the last tag
So, you have a situation when user has input a few comma separated tags (probably some of them can be new) and now wants to save it to the database. For each input, if that is a known tag you have to store it to "deliverables_has_tags". If there is a new tag, you have to store it both to "tags" and "deliverables_has_tags".
Most common scenario would be having a 'Save' button to start saving process.
Let's analyze what you have to do in the process.
1) Button click
On button click you use js to convert your comma separated tags string
using logic like split(term) to the array, and serialize it. You can
do serialization using serializeArray and manually create JSON
object, or serialize the whole form using
$('#yourForm').serialize(). I would choose the first option
because that way I get more control over JSON format and avoid
problems with MVC default model binder.
2) Ajax call
When the JSON object is ready to be sent, you fire an ajax POST
request to your MVC POST action method. When you save state always
avoid GET because new versions of browsers can scan thru your page and
actively preload urls using GET requests. You don't want this here. Of
course, use your data as a data-parameter in the ajax call.
3) Action method
When the request arrives, you have to process it in your controller
using a new action method. Typically in this case you will have
something like public JsonResult SaveTags(SaveTagsModel saveTags) {
... } which saves tags using your repository and returns result that
says something like 'OK' or 'ERROR' (sth like
response.isSaved=true/false). Tricky part can be designing view model
according to your JSON object - this could help. And regarding
collections this could be valuable info.
When saving, use transaction to ensure everything is saved at once.
First check if each tag exists in the database and insert those who
don't exist. After that, check for each tag if there is appropriate
n-n relation in deliverables_has_tags and insert it if there isn't.
I believe that you should use same repository encapsulation for both
operations.
In the post action, include FormCollection collection as argument and gather your tags from that. There is no automatic way. You could implement some custom model binding, but that is probably not worth the effort.

Resources