MeteorJS: Collection.find fires multiple times instead of once - meteor

I have an app that when you select an industry from a drop down list a collection is updated where the attribute equals the selected industry.
JavaScript:
Template.selector.events({
'click div.select-block ul.dropdown-menu li': function(e) {
var selectedIndex = $(e.currentTarget).attr("rel");
var val = $('select#industryPicker option:eq(' + selectedIndex + ')').attr('value');
var oldVal = Session.get('currentIndustryOnet');
if(val != oldVal) {
Session.set('jobsLoaded', false);
Session.set('currentIndustryOnet', val);
Meteor.call('countByOnet', val, function(error, results){
if(results > 0) {
Session.set('jobsLoaded', true);
} else {
getJobsByIndustry(val);
}
});
}
}
});
var getJobsByIndustry = function(onet) {
if(typeof(onet) === "undefined")
alert("Must include an Onet code");
var params = "onet=" + onet + "&cn=100&rs=1&re=500";
return getJobs(params, onet);
}
var getJobs = function(params, onet) {
Meteor.call('retrieveJobs', params, function(error, results){
$('job', results.content).each(function(){
var jvid = $(this).find('jvid').text();
var job = Jobs.findOne({jvid: jvid});
if(!job) {
options = {}
options.title = $(this).find('title').text();
options.company = $(this).find('company').text();
options.address = $(this).find('location').text();
options.jvid = jvid;
options.onet = onet;
options.url = $(this).find('url').text();
options.dateacquired = $(this).find('dateacquired').text();
var id = createJob(options);
console.log("Job Created: " + id);
}
});
Session.set('jobsLoaded', true);
});
}
Template.list.events({
'click div.select-block ul.dropdown-menu li': function(e){
var selectedIndex = $(e.currentTarget).attr("rel");
var val = $('select#perPage option:eq(' + selectedIndex + ')').attr('value');
var oldVal = Session.get('perPage');
if(val != oldVal) {
Session.set('perPage', val);
Pagination.perPage(val);
}
}
});
Template.list.jobs = function() {
var jobs;
if(Session.get('currentIndustryOnet')) {
jobs = Jobs.find({onet: Session.get('currentIndustryOnet')}).fetch();
var addresses = _.chain(jobs)
.countBy('address')
.pairs()
.sortBy(function(j) {return -j[1];})
.map(function(j) {return j[0];})
.first(100)
.value();
gmaps.clearMap();
$.each(_.uniq(addresses), function(k, v){
var addr = v.split(', ');
Meteor.call('getCity', addr[0].toUpperCase(), addr[1], function(error, city){
if(city) {
var opts = {};
opts.lng = city.loc[1];
opts.lat = city.loc[0];
opts.population = city.pop;
gmaps.addMarker(opts);
}
});
})
return Pagination.collection(jobs);
} else {
jobs = Jobs.find()
Session.set('jobCount', jobs.count());
return Pagination.collection(jobs.fetch());
}
}
In Template.list.jobs if you console.log(addresses), it is called 4 different times. The browser console looks like this:
(2) 100
(2) 100
Any reason why this would fire multiple times?

As #musically_ut said it might be because of your session data.
Basically you must make the difference between reactive datasources and non reactive datasources.
Non reactive are standard javascript, nothing fancy.
The reactive ones however are monitored by Meteor and when one is updated (insert, update, delete, you name it), Meteor is going to execute again all parts which uses this datasource. Default reactive datasources are: collections and sessions. You can also create yours.
So when you update your session attribute, it is going to execute again all helper's methods which are using this datasource.
About the rendering, pages were rendered again in Meteor < 0.8, now with Blaze it is not the case anymore.
Here is a quick example for a better understanding:
The template first
<head>
<title>test</title>
</head>
<body>
{{> hello}}
</body>
<template name="hello">
<h1>{{getSession}}</h1>
<h1>{{getNonReactiveSession}}</h1>
<h1>{{getCollection}}</h1>
<input type="button" name="session" value="Session" />
<input type="button" name="collection" value="Collection" />
</template>
And the client code
if (Meteor.isClient) {
CollectionWhatever = new Meteor.Collection;
Template.hello.events({
'click input[name="session"]': function () {
Session.set('date', new Date());
},
'click input[name="collection"]': function () {
CollectionWhatever.insert({});
}
});
Template.hello.getSession = function () {
console.log('getSession');
return Session.get('date');
};
Template.hello.getNonReactiveSession = function () {
console.log('getNonReactiveSession');
var sessionVal = null;
new Deps.nonreactive(function () {
sessionVal = Session.get('date');
});
return sessionVal;
};
Template.hello.getCollection = function () {
console.log('getCollection');
return CollectionWhatever.find().count();
};
Template.hello.rendered = function () {
console.log('rendered');
}
}
If you click on a button it is going to update a datasource and the helper method which is using this datasource will be executed again.
Except for the non reactive session, with Deps.nonreactive you can make Meteor ignore the updates.
Do not hesitate to add logs to your app!
You can read:
Reactivity
Dependencies

Related

ngservice method called automatically and application stopped

my application have some dropdowns and one search button.after selection from dropdown click search button. It will display result based on selected values. I am using web api to fetch data from database. angular service will call those api methods
//for family dropdown
this.getProresultByseriesFamily = function (family) {
var res;
if (family !== 0) {
res = $http.get("/api/KendoCascading/GetProresult/" + family);
return res;
}
};
//for class dropdown
this.getFuseClassRes = function (fuseClass) {
var res;
if (fuseClass !== null) {
res = $http.get("/api/KendoCascading/GetResFuse/" + fuseClass);
return res;
}
};
//for series dropdown
this.getresSeries = function (seID) {
var res;
if (seID !== 0) {
res = $http.get("/api/KendoCascading/GetSeriesResult/" + seID);
return res;
}
};
controller
$scope.getProResult = function () {
if ($scope.SelectedCriteria != null || $scope.SelectedCriteria!="") {
var promise = ngservice.getProresultByseriesFamily($scope.SelectedCriteria);
promise.then(function (resp) {
console.log(resp.data);
$scope.Products = resp.data;
alert("successful1");
}, function (err) {
alert("Falied");
});
}
if ($scope.FuseModel != null || $scope.FuseModel != "") {
var promise = ngservice.getFuseClassRes($scope.FuseModel);
promise.then(function (resp) {
$scope.Products = resp.data;
alert("successful2");
}, function (err) {
alert("failed");
});
}
if ($scope.FuseSeriesModel != null || $scope.FuseSeriesModel != "") {
var promise = ngservice.getresSeries($scope.FuseSeriesModel);
promise.then(function (resp) {
$scope.Products = resp.data;
alert("successful");
}, function (err) {
alert("Failed");
});
}
};
this controller function used as a click event in view. If i call first service method the view populate the result. But after some time it is automatically calling the 3rd service method and application stopped working. What is going wrong? if any better solution exist please help me.
api is working fine . i have checked in browser. It is fine.
view
<Button type="button" ID="Btn_Search" Width="90" Class="btn btn-primary btn-sm" ng-click="getProResult();show=!show">Search</Button>
I think the problem is because sometimes your service returns undefined.
In the third case, which is the one with problem, if $scope.FuseSeriesModel === 0, ngservice.getresSeries($scope.FuseSeriesModel) will return undefined and it will throw an error because you are calling then on undefined.
I would recommend you to update your code to check for the promise before calling .then.
For example:
if ($scope.FuseSeriesModel != null || $scope.FuseSeriesModel != "") {
var promise = ngservice.getresSeries($scope.FuseSeriesModel);
if (promise) {
promise.then(function (resp) {
$scope.Products = resp.data;
alert("successful");
}, function (err) {
alert("Failed");
});
}
}
Another way is to update your if conditions to !!$scope.FuseSeriesModel (or Boolean($scope.FuseSeriesModel)). That way, your promise code will be called only when the value is Truthy which I think is what you really want to do:
if (Boolean($scope.FuseSeriesModel)) { // or $scope.FuseSeriesModel or !!$scope.FuseSeriesModel
var promise = ngservice.getresSeries($scope.FuseSeriesModel);
promise.then(function (resp) {
$scope.Products = resp.data;
alert("successful");
}, function (err) {
alert("Failed");
}
);

How to hide server controls untill angular load custom multi select dropdown

i am working with asp.net webform and angular.i have few asp.net server side controls in page and i have used a angular multi select dropdown which invoke server side function to get data. so when i am running my application then server side controls comes so fast but later angular multi select display which look odd.
so please see my code and suggest me how to refactor my code to sync the server side and client side UI appear at same time.
here is bit of my server side code
public ActionResult MultiSelectCountry()
{
ViewBag.Countries = GetCountries();
return View();
}
[NonAction]
public JsonResult GetCountries()
{
List<Country> oList = new List<Country>()
{
new Country {ID=1,Name="United Kingdom"},
new Country {ID=1,Name="United States"},
new Country {ID=1,Name="Italy"},
new Country {ID=1,Name="Germany"},
new Country {ID=1,Name="India"}
};
return Json(oList);
}
here is bit of my client side code
<body ng-app="app" ng-controller="MainCtrl">
<div class="col-sm-10 col-sm-offset-1">
<searchable-multiselect display-attr="Name"
selected-items="SelectedCountryList" all-items="CountryList"
add-item="addLanguageToUser(item)" remove-item="removeLanguageFromUser(item)" >
</searchable-multiselect>
</div>
<script data-require="jquery" data-semver="2.1.3" src="http://code.jquery.com/jquery-2.1.3.min.js"></script>
<script data-require="bootstrap" data-semver="3.3.2" src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
<script data-require="angular.js#1.3.x" src="https://code.angularjs.org/1.3.14/angular.js" data-semver="1.3.14"></script>
<script data-require="ui-bootstrap" src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.0/ui-bootstrap-tpls.min.js" data-semver="0.13.0"></script>
<script>
var _countries = #Html.Raw(Json.Encode(ViewBag.Countries));
var app = angular.module('app', ["ui.bootstrap"]);
var yourApp = yourApp || {};
yourApp.Settings = yourApp.Settings || {};
yourApp.Settings.BaseUrl = "#Url.Content("~")";
yourApp.Settings.CountryList = _countries["Data"];
angular.module("app").value("appSettings", yourApp);
</script>
<script src="~/Scripts/app.js"></script>
</body>
app.js code
app.controller('MainCtrl', function ($scope, appSettings) {
$scope.CountryList = appSettings.Settings.CountryList;
$scope.SelectedCountryList = [];
$scope.addLanguageToUser = function (country) {
$scope.SelectedCountryList.push(country)
};
$scope.removeLanguageFromUser = function (country) {
var idx = $scope.SelectedCountryList.indexOf(country);
$scope.SelectedCountryList.splice(idx, 1);
};
});
app.directive("searchableMultiselect", function ($timeout, appSettings) {
return {
templateUrl: appSettings.Settings.BaseUrl + 'Angular/Directives/searchableMultiselect.html',
restrict: 'AE',
scope: {
displayAttr: '#',
selectedItems: '=',
allItems: '=',
readOnly: '=',
addItem: '&',
removeItem: '&'
},
link: function (scope, element, attrs) {
element.bind('click', function (e) {
e.stopPropagation();
});
scope.width = element[0].getBoundingClientRect();
scope.updateSelectedItems = function (obj) {
var selectedObj;
for (i = 0; typeof scope.selectedItems !== 'undefined' && i < scope.selectedItems.length; i++) {
if (scope.selectedItems[i][scope.displayAttr].toUpperCase() === obj[scope.displayAttr].toUpperCase()) {
selectedObj = scope.selectedItems[i];
break;
}
}
if (typeof selectedObj === 'undefined') {
scope.addItem({ item: obj });
} else {
scope.removeItem({ item: selectedObj });
}
};
scope.isItemSelected = function (item) {
if (typeof scope.selectedItems === 'undefined') return false;
var tmpItem;
for (i = 0; i < scope.selectedItems.length; i++) {
tmpItem = scope.selectedItems[i];
if (typeof tmpItem !== 'undefined'
&& typeof tmpItem[scope.displayAttr] !== 'undefined'
&& typeof item[scope.displayAttr] !== 'undefined'
&& tmpItem[scope.displayAttr].toUpperCase() === item[scope.displayAttr].toUpperCase()) {
return true;
}
}
return false;
};
scope.commaDelimitedSelected = function () {
var list = "";
angular.forEach(scope.selectedItems, function (item, index) {
list += item[scope.displayAttr];
if (index < scope.selectedItems.length - 1) list += ', ';
});
return list.length ? list : "Nothing Selected";
}
}
}
});
my request is modify my code in such way as a result server side controls will be visible only when multi select is ready to show. all UI related things should be visible at same time. thanks
Something like
<div class="col-sm-10 col-sm-offset-1" ng-show="MainCtrl.CountryList.length>0"> should probably do the trick.

template rendered is not working properly in meteor JS

template rendered is not working
when user successfully login in to system i redirect to profile page that time data is not get but if i visit another page and come back to profile page that time it is working fine. also when i reload page that time also it is not working
here is code
Template.profile.rendered = function(){
var user_email = {};
user_email.mail = Session.get('email');
var imgName = Session.get('image');
Meteor.call("imgSend",imgName,function(error, result){
$('.user_profile_image').attr("src",result)
});
Meteor.call("getLinkMeta",user_email,function(error, result){
var link_all_info = [];
var walldata = [];
var total = result.length;
var processed = 0;
var t = result.forEach(function (entry){
var link_info = {};
link_info.link_id = entry._id;
Meteor.call("getCommentList",link_info, function (error, res){
if(error){
console.log("e");
}else{
entry.comments = res;
}
processed++
if(processed == total){
//walldata=result;
}
});
});
Template.profile.walldata = function(){
return result;
};
//return result;
});
}
Router.route('profile', {
path: '/profile',
data: function() {
/* Meteor.subscribe("Users");
Meteor.subscribe("Link");
Meteor.subscribe("Linkfav");
Meteor.subscribe("LinkLike");
Meteor.subscribe("LinkComment"); */
$("body").removeClass('home');
this.render('profile');
setTimeout(function(){
$('#username').html(Session.get('first_name'));
$('#profile_username').html(Session.get('first_name'));
$('#setting_name').val(Session.get('first_name'));
$('#setting_username').val(Session.get('first_name'));
$('#setting_email').val(Session.get('email'));
$('#user_id').val(Session.get('id'));
$('.setting_day').val(Session.get('day'));
$('.setting_month').val(Session.get('month'));
$('.setting_year').val(Session.get('year'));
if(Session.get('image')!= ''){
$('.user_profile_image').attr("src",Session.get('image'));
}
if(Session.get('gender') == 0){
$('#user_gender').html('Male');
}else{
$('#user_gender').html('Female');
}
$('#day').html(Session.get('day'));
$('#month').html(Session.get('month'));
$('#year').html(Session.get('year'));
},100);
},onBeforeAction:function(){
if(Session.get('email')){
this.next();
}else {
//this.next();
this.redirect('/');
}
}
});
When you refresh/reload the page Session values are get undefined. You can get the current user email using meteor.user(). You just have to replace you session.get('email') like this.
var user_email = {};
user_email.mail = Meteor.user().emails[0].address;
I hope that is what you are looking for.

Meteor define ReactiveVar to be accesible in .events and .helpers

I am trying to define a new ReactiveVar variable to be accessible in all the template sections (ex. .events, .helpers, .rendered ...etc) as shown in my code below, yet I am always getting an error:
Error: Exception in template helper:
ReferenceError: logData is not defined
Can someone please tell me what I am missing / doing wrong here? Thanks
Code:
Template.detailedreport.rendered = function() {
var logData = new ReactiveVar;
logData.set([]);
};
Template.detailedreport.helpers({
myCollection: function () {
return logData.get();
}
});
Template.detailedreport.events({
'submit form': function(e) {
e.preventDefault();
var now = Session.get("startDate");
var then = Session.get("endDate");
var custID = Session.get("customer");
var projID = Session.get("project");
Meteor.call('logSummary', now, then, projID, custID, function(error, data){
if(error)
return alert(error.reason);
logData.set(data);
});
}
});
You need to define the ReactiveVar on the template instance like this :
Template.detailedreport.created = function() {
this.logData = new ReactiveVar([]);
};
Then you'll be able to access it in helpers like this :
Template.detailedreport.helpers({
myCollection: function () {
return Template.instance().logData.get();
}
});
In events you can use the template argument :
Template.detailedreport.events({
'submit form': function(e, template) {
e.preventDefault();
var now = Session.get("startDate");
var then = Session.get("endDate");
var custID = Session.get("customer");
var projID = Session.get("project");
Meteor.call('logSummary', now, then, projID, custID, function(error, data){
if(error){
return alert(error.reason);
}
template.logData.set(data);
});
}
});

Changing the value of a Telerik RadEditor with Javascript/jQuery

I'm trying to manually clean the HTML of a Telerik RadEditor with Javascript but I can't seem to find the correct place to store the value so that it gets saved on post back.
Here's the JS I have:
$(function () {
jQuery.fixHash = function ($html) {
// modify $html
return $html;
};
$("#adminEditingArea input[id$='SaveButton']").unbind("click").click(function () {
$("iframe[id$='_contentIframe']").trigger("save");
// call .net postback
return false;
});
});
var editorSaveEventInit = false;
function InitSaveEvent() {
if (!editorSaveEventInit) {
var $EditFrames = $("iframe[id$='_contentIframe']");
if ($EditFrames && $EditFrames.length > 0) {
$EditFrames.bind("save", function (e) {
var $thisFrame = $(this);
var thisFrameContents = $thisFrame.contents();
if (thisFrameContents) {
var telerikContentIFrame = thisFrameContents.get(0);
var $body = $("body", telerikContentIFrame);
var html = $.fixHash($body).html();
$body.html(html);
// also tried storing the modified HTML in the textarea, but it doesn't seem to save:
//$thisFrame.prev("textarea").html(encodeURIComponent("<body>" + html + "</body>"));
}
});
editorSaveEventInit = true;
}
}
};
$(window).load(function () {
InitSaveEvent();
});
Is there any way to access the Telerik RadEditor object with JavaScript (using OnClientCommandExecuted()?) so that I can access the .get_html() and .set_html(value) functions? If not, what values do I need to set before posting back?
Why don't you use custom content filters.
Ah, just discovered Telerik's built-in $find() function: http://www.telerik.com/help/aspnet-ajax/editor_getingreferencetoradeditor.html
Edit: here's the solution I came up with for my InitSaveEvent() function:
var editorSaveEventInit = false;
function InitSaveEvent() {
if (!editorSaveEventInit) {
var $EditFrames = $("iframe[id$='_contentIframe']");
if ($EditFrames && $EditFrames.length > 0) {
$EditFrames.bind("save", function (e) {
var $thisFrame = $(this);
var thisFrameContents = $thisFrame.contents();
if (thisFrameContents) {
var telerikContentIFrame = thisFrameContents.get(0);
var $body = $("body", telerikContentIFrame);
var html = $.fixHash($body).html();
// SOLUTION!
var $radeditor = $thisFrame.parents("div.RadEditor.Telerik:eq(0)");
var editor = $find($radeditor.attr("id"));
editor.set_html(html);
// ☺
}
});
editorSaveEventInit = true;
}
}
};

Resources