Trouble with helpers and db calls - mongodb

Just started to learn a Meteor and stuck with a silly problem. I have a collection "Images" and i trying to get one random image from her. But in the browser's console it's says that "Cannot read property 'url' of undefined", but if type a db's "findOne" method in the console there is a record that i wanted.
A client code:
Template.main.helpers({
img: function () {
return Images.findOne({rand: {$gte: Math.random()}}).url;
}
});
Collection:
Images = Meteor.Collection('images');
On a server's side i've got simple fixtures for initial tests
if(Images.find().count() === 0){
Images.insert({url: "http://domain.com/test1.jpg", rand: Math.random()});
Images.insert({url: "http://domain.com/test2.jpg", rand: Math.random()});
Images.insert({url: "http://domain.com/test3.jpg", rand: Math.random()});
}
And simple template:
<head>
<title>Test</title>
</head>
<body>
{{> main}}
</body>
<template name="main">
{{img}}
</template>
P.S. I'm working under the Win 8.

First, I'm going to assume that the images are published to the client. You should verify that Images.find().count() > 0 is true in your browser console (not in your template code).
Next, you should read about how to add guards to your template code to fix the error you are seeing. This article should explain what you need to know.
Finally, unless you actually need rand on your documents, there are better ways to accomplish a random selection. Your question says you are looking for one image, but it's possible that 1 or 0 could be returned with your code. Instead you could use the built-in Random package. Give this a try:
Template.main.helpers({
img: function() {
var image = Random.choice(Images.find().fetch());
return image && image.url;
}
});
Although, I think it makes more sense to return the image object:
Template.main.helpers({
image: function() {
return Random.choice(Images.find().fetch());
}
});
And then select the url in your template (no guards are needed in this case):
<template name="main">
<img src="{{image.url}}">
</template>

You need to be careful with your assumptions when you load a template. When your web page has loaded on the web browser, it won't necessarily have any data in it (your Images.findOne() query could return null)
You just need to take account of this possibility
var image = Images.findOne({rand: {$gte: Math.random()}});
return image && image.url

Related

Using a Vue3 component as a Leaflet popup

This previous SO question shows how we can use a Vue2 component as the content of a LeafletJS popup. I've been unable to get this working with Vue3.
Extracting the relevant section of my code, I have:
<script setup lang="ts">
import { ref } from 'vue'
import L, { type Content } from 'leaflet'
import type { FeatureCollection, Feature } from 'geojson'
import LeafletPopup from '#/components/LeafletPopup.vue'
// This ref will be matched by Vue to the element with the same ref name
const popupDialogElement = ref(null)
function addFeaturePopup(feature:Feature, layer:L.GeoJSON) {
if (popupDialogElement?.value !== null) {
const content:Content = popupDialogElement.value as HTMLElement
layer.bindPopup(() => content.$el)
}
}
</script>
<template>
<div class="map-container">
<section id="map">
</section>
<leaflet-popup ref="popupDialogElement" v-show="false">
</leaflet-popup>
</div>
</template>
This does produce a popup when I click on the map, but it has no content.
If, instead, I change line 14 to:
layer.bindPopup(() => content.$el.innerHTML)
then I do get a popup with the HTML markup I expect, but unsurprisingly I lose all of the Vue behaviours I need (event handling, etc).
Inspecting the addFeaturePopup function in the JS debugger, the content does seem to be an instance of HTMLElement, so I'm not sure why it's not working to pass it to Leaflet's bindPopup method. I assume this has something to do with how Vue3 handles references, but as yet I can't see a way around it.
Update 2022-06-09
As requested, here's the console.log output: I've put it in a gist as it's quite long
So just to document the solution I ended up using, I needed to add an additional style rule in addition to the general skeleton outlined in the question:
<style>
.leaflet-popup-content >* {
display: block !important;
}
</style>
This overrides the display:none that is attached to the DOM node by v-show=false. It would be nice not to need the !important, but I wasn't able to make the rule selective enough in my experiments.

Using search feature in Ionic framework

I am a UI person and very new to ionic framework.. I wanted to add search feature in my android app built using Ionic framework. After a research i found that I will need to use this plugin https://github.com/djett41/ionic-filter-bar. but there is no detail documentation available. Can anyone please guide how to use this plugin working. I have made all setup but stuck with actual code.
First of all you must install the plugin. You can use bower for that:
bower install ionic-filter-bar --save
and it will copy all the javascript and css needed in the lib folder inside www.
Then you must add the references to the css to your index.html:
<link href="lib/ionic-filter-bar/dist/ionic.filter.bar.css" rel="stylesheet">
same thing for the javascript:
<script src="lib/ionic-filter-bar/dist/ionic.filter.bar.js"></script>
You have to inject the module jett.ionic.filter.bar you your main module:
var app = angular.module('app', [
'ionic',
'jett.ionic.filter.bar'
]);
and you must reference the service $ionicFilterBar in your controller:
angular.module('app')
.controller('home', function($scope, $ionicFilterBar){
});
Now you can start using it.
In my sample I want to trigger the search-box when the user clicks/taps on a icon in the header. I would add this HTML to the view:
<ion-nav-buttons side="secondary">
<button class="button button-icon icon ion-ios-search-strong" ng-click="showFilterBar()">
</button>
</ion-nav-buttons>
The action trigger an event in my controller showFilterBar:
$scope.showFilterBar = function () {
var filterBarInstance = $ionicFilterBar.show({
cancelText: "<i class='ion-ios-close-outline'></i>",
items: $scope.places,
update: function (filteredItems, filterText) {
$scope.places = filteredItems;
}
});
};
which creates the $ionicFilterBar and shows it.
As you can see here I am using an array of objects $scope.places
$scope.places = [{name:'New York'}, {name: 'London'}, {name: 'Milan'}, {name:'Paris'}];
which I have linked to the items member of my $ionicFilterBar. The update method will give me in filteredItems the items (places) filtered.
You can play with this plunker.
Another option is to use the plugin to actually fetch some data remotely through $http.
If we want to achieve this we can use the update function again.
Now we don't need to bind the items to our array of objects cause we won't need the filtered elements.
We will use the filterText to perform some action:
$scope.showFilterBar = function () {
var filterBarInstance = $ionicFilterBar.show({
cancelText: "<i class='ion-ios-close-outline'></i>",
// items: $scope.places,
update: function (filteredItems, filterText) {
if (filterText) {
console.log(filterText);
$scope.fetchPlaces(filterText);
}
}
});
};
We will call another function which will, maybe, call $http and return some data which we can bind to our array of objects:
$scope.fetchPlaces = function(searchText)
{
$scope.places = <result of $http call>;
}
Another plunker here.
PS:
If you want to configure it using some sort of customization you must do it in your configuration using the provider $ionicFilterBarConfigProvider:
angular.module('app')
.config(function($ionicFilterBarConfigProvider){
$ionicFilterBarConfigProvider.clear('ion-ios-close-empty');
})
PPS:
In my plunker I've included the css and the script directly copying it from the source.
UPDATE:
Someone asked not to replace the list with the updated one.
My cheap and dirty solution is to check if the filterText contains some values. If it's empty (no searches) we go throught each element an set a property found = false otherwise we compare the places array we the filteredItems array.
Matching elements will be marked as found.
function allNotFound(filteredItems) {
angular.forEach($scope.places, function(item){
item.found = false;
});
}
function matchingItems(filteredItems) {
angular.forEach($scope.places, function(item){
var found = $filter('filter')(filteredItems, {name: item.name});
if (found && found.length > 0) {
console.log('found', item.name);
item.found = true;
} else {
item.found = false;
console.log('not found', item.name);
}
});
and now we can integrate the filter bar this way:
$scope.showFilterBar = function () {
var filterBarInstance = $ionicFilterBar.show({
cancelText: "<i class='ion-ios-close-outline'></i>",
items: $scope.places,
update: function (filteredItems, filterText) {
if (!filterText) {
allNotFound();
} else {
matchingItems(filteredItems);
}
}
});
};
We can use the found attribute of the object to change the style of the element.
As always, a Plunker to show how it works.
Ionic uses Angular, and Angular include an atributte filter very useful. Look this: https://docs.angularjs.org/api/ng/filter/filter and the example there. Regards

DOM elements not accessible after onsen pageinit in ons.ready

I am using the Onsen framework with jQuery and jQuery mobile, it appears that there is no way to catch the event that fires once the new page is loaded.
My current code, which executes in the index.html file (the master page)
<script src="scripts/jQuery.js"></script>
<script src="scripts/jquery.mobile.custom.min.js"></script>
<script src="scripts/app.js"></script>
<script>
ons.bootstrap();
ons.ready(function() {
$(document.body).on('pageinit', '#recentPage', function() {
initRecentPage();
});
});
in app.js is the following code
function initRecentPage() {
$("#yourReports").on("tap", ".showReport", recentShowReport);
var content = document.getElementById("yourReports");
ons.compile(content);
}
and the HTML:
<ons-page id="recentPage">
<ons-toolbar id="myToolbar">
<div id="toolBarTitle" class="center">Recent Checks</div>
<div class="right">
<ons-toolbar-button ng-click="mySlidingMenu.toggleMenu()">
<ons-icon icon="bars"></ons-icon>
</ons-toolbar-button>
</div>
</ons-toolbar>
<ons-scroller>
<h3 class="headingTitle"> Checks</h3>
<div id="Free" class="tabArea">
<ons-list id="yourReports">
</ons-list>
<ons-button id="clearFreeRecentButton">
<span id="clearRecentText" class="bold">Clear Recent Checks</span>
</ons-button>
</div>
</ons-scroller>
</ons-page>
in this instance the variable 'content' is always null. I've debuged significantly, and the element I am trying to get is not present when this event fires. It is loaded later.
So, the question is, how do I ensure that all of the content is present before using a selector. It feels like this is an onsen specific issue.
In the end I could only find one reliable way of making this work.
Essentially I had to wait, using setTimeout 300 milliseconds for the DOM elements to be ready. It feels like a hack, but honestly there is no other reliable way of making this behave. The app is in the app store and works well, so even though it seems like a hack, it works:
$(document).on('pageinit', '#homePage', function() {
initHomePage();
});
function initHomePage() {
setTimeout(function() {
setUpHomePage();
}, 300);
}
Move your $(document.body).on('pageinit', '#recentPage', function() { outside of ons.ready block.
JS
ons.bootstrap();
ons.ready(function() {
console.log("ready");
});
$(document.body).on('pageinit', '#recentPage', function() {
initRecentPage();
});
function initRecentPage() {
//$("#yourReports").on("tap", ".showReport", recentShowReport);
var content = document.getElementById("yourReports");
alert(content)
ons.compile(content);
}
I commented out a line because I do not have access to that "recentShowReport"
You can see how it works here: 'http://codepen.io/vnguyen972/pen/xCqDe'
The alert will show that 'content' is not NULL.
Hope this helps.

infinite loop when trying to retrieve data from controller in view using angular $resource.Query() method

I'm building a small sails.js + angular.js app.
Here is a fiddle that roughly shows what my code looks like: http://jsfiddle.net/WEk3F/
index: function(req, res, next) {
Food.find({}, function foundFoods(err, foods) {
if (err) return next(err);
var data = {
name1: "test1",
name2: "test2"
}
res.view(
'food/index', {
foods: data
});
});
},
<div ng-app="myApp">
<div ng-controller="FoodController">
<ul>
<li ng-repeat="food in foods">
{{food.name}}
</li>
</ul>
<form>
<label for="name">Name:</label>
<input name="name" ng-model="editableFood.name" />
</form>
</div>
</div>
My problem is, that whenever i try to retrieve the data from my controller, i don't get those 2 items but instead it renders more and more items and just doesn't stop. even the page gets slow and unresponsive and almost freezes.
When i say
$scope.foods = [{"name": "test1"},{"name": "test2"}];
instead of
$scope.foods = Food.query();
it works. but i want the data to be coming from the backend via the controller.
the other methods (add, update etc) of the angular.js $resource module work fine for me.
/food maps to the index action of my FoodController and just returns some fixed test data
i found out what the problem was.
the angular.js $resource should only be used in a restful way, so the GET request to my food/index should return an array of objects.
in my case this wasn't the case. instead i used the index action of my controller to render a view and the result was therefor html.
so my options are to define a new route, that the $resource takes for the Query() command or define a new action that i use for the view/html stuff and use the index action again for pure restful response.
thx #sam hunter for pointing me to the right direction
the infinite loop that i received is explained here: One dimensional array of strings being parsed to 2d by angular resource
i love stack overflow

jquery / ajax form not passing button data

I thought the HTML spec stated that buttons click in a form pass their value, and button "not clicked" did not get passed. Like check boxes... I always check for the button value and sometimes I'll do different processing depending on which button was used to submit..
I have started using AJAX (specifically jquery) to submit my form data - but the button data is NEVER passed - is there something I'm missing? is there soemthing I can do to pass that data?
simple code might look like this
<form id="frmPost" method="post" action="page.php" class="bbForm" >
<input type="text" name="heading" id="heading" />
<input type="submit" name="btnA" value="Process It!" />
<input type="submit" name="btnB" value="Re-rout it somewhere Else!" />
</form>
<script>
$( function() { //once the doc has loaded
//handle the forms
$( '.bbForm' ).live( 'submit', function() { // catch the form's submit event
$.ajax({ // create an AJAX call...
data: $( this ).serialize(), // get the form data
type: $( this ).attr( 'method' ), // GET or POST
url: $( this ).attr( 'action' ), // the file to call
success: function( response ) { // on success..
$('#ui-tabs-1').html( response );
}
});
return false; // cancel original event to prevent form submitting
});
});
</script>
On the processing page - ONLY the "heading" field appears, neither the btnA or btnB regardless of whichever is clicked...
if it can't be 'fixed' can someone explain why the Ajax call doesn't follow "standard" form behavior?
thx
I found this to be an interesting issue so I figured I would do a bit of digging into the jquery source code and api documentation.
My findings:
Your issue has nothing to do with an ajax call and everything to do with the $.serialize() function. It simply is not coded to return <input type="submit"> or even <button type="submit"> I tried both. There is a regex expression that is run against the set of elements in the form to be serialized and it arbitrarily excludes the submit button unfortunately.
jQuery source code (I modified for debugging purposes but everything is still semantically intact):
serialize: function() {
var data = jQuery.param( this.serializeArray() );
return data;
},
serializeArray: function() {
var elementMap = this.map(function(){
return this.elements ? jQuery.makeArray( this.elements ) : this;
});
var filtered = elementMap.filter(function(){
var regexTest1= rselectTextarea.test( this.nodeName );
var regexTest2 = rinput.test( this.type ); //input submit will fail here thus never serialized as part of the form
var output = this.name && !this.disabled &&
( this.checked || regexTest2|| regexTest2);
return output;
});
var output = filtered.map(function( i, elem ){
var val = jQuery( this ).val();
return val == null ?
null :
jQuery.isArray( val ) ?
jQuery.map( val, function( val, i ){
return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
}) :
{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
}).get();
return output;
}
Now examining the jQuery documentation, you meet all the requirements for it to behave as expected (http://api.jquery.com/serialize/):
Note: Only "successful controls" are serialized to the string. No submit button value is serialized since the form was not submitted using a button. For a form element's value to be included in the serialized string, the element must have a name attribute. Values from checkboxes and radio buttons (inputs of type "radio" or "checkbox") are included only if they are checked. Data from file select elements is not serialized.
the "successful controls link branches out to the W3 spec and you definitely nailed the expected behavior on the spec.
Short lame answer: I think it is teh broken! Report for bug fix!!!
I've run into a rather unusual issue with this. I'm working on a project and have two separate php pages where one has html on the page separate from the php code and one is echoing html from inside php code. When I use the .serialize on the one that has the separate html code it works correctly. It sends my submit button value in its ajax call to another php page. But in the one with the html echoed from the php script I try to do the same thing and get completely different results. It will send all of the other info in the form but not the value of the submit button. All I need it to do is send whether or not I pushed "Delete" or "Update". I'm not asking for help (violating the rules of asking for help on another persons post) but I thought this info might be helpful in figuring out where the break down is occurring. I'll be looking for a solution and will post back here if I figure anything out.