Filter and search using Textbox - meteor

This question is a follow-up of My Previous Question on Filtering DropdownList
I am adding an extra feature and for that I want to Filter values using a textbox. Here is the code for filter
var filterAndLimitResults = function (cursor) {
if (!cursor) {
return [];
}
var raw = cursor.fetch();
var currentSearchTitle = searchTitle.get();
if(!(!currentSearchTitle || currentSearchTitle == "")) {
filtered = _.filter(filtered, function (item) {
return item.title === currentSearchTitle ;
console.log(item.title === currentSearchTitle);
});
}
var currentLimit =limit.get();
//enforce the limit
if (currentLimit ) {
filtered = _.first(filtered, currentLimit );
}
return filtered;
};
and this is the keyup event i am doing on the search textbox
"keyup #search-title":function(e,t){
if(e.which === 27){
searchTitle.set("");
}
else {
var text = $(e.target.val);
searchTitle.set(text)
console.log(searchTitle.set(text));
}
}
With this i am able to return total collection objects on each keypress in the console but it is not filtering the values in the listing and it vanishies all the listings from the UI. Please help

You are almost right. Your keyUp event is ok but you could also avoid to use jQuery like this:
"keyup .textInput": function(e, t) {
var searchString = e.currentTarget.value;
switch (e.which) {
case 27:
e.currentTarget.value = "";
searchTitle.set("");
break;
default:
searchTitle.set(searchString);
}
}
Note that I use a switch in case you would want to add shortcuts for specific searches, like cmd+maj+c to search only for cities (it might be a little overkill)
Concerning the search function, I assume you want to search among the titles of your items, within the current dropdown filtering.
You then need to add an extra step to do this. You also need to set it before the other filters. See my example below, you insert it after var filtered = [];:
var filtered = [];
var currentSearchTitle = searchTitle.get();
if(!currentSearchTitle || currentSearchTitle == "") {
filtered = raw;
} else {
currentSearchTitle = currentSearchTitle .replace(".", "\\.");//for regex
var regEx = new RegExp(currentSearchTitle , "i");
filtered = _.filter(raw, function(item) {
return (item && item.title && item.title.match(regEx));
});
}
// your other filtering tasks
return filtered;
Also, take the time to understand what the code does, you must not just copy paste it.
This code is strongly inspired by the work of meteorkitchen author, #Perak. I adapted it but I have not tested it "as-is".

Related

FullCalendar: show reversed list views

How can I reverse the events in the list views, so that the event with the most futuristic date appears at the beginning (top)?
#F.Mora your solution is almost perfect but in our case we add some custom classNames and have multiple items under each headline.
Here is our enhanced version :
eventAfterAllRender: function(view) {
var renderedEvents = $('.fc-list-table tr');
var reorderedEvents = [];
var blockEvents = null;
renderedEvents.map(function(key, event) {
if ($(event).hasClass('fc-list-heading')) {
if (blockEvents) {
reorderedEvents.unshift(blockEvents.children());
}
blockEvents = $('<tbody></tbody>');
}
blockEvents.append(event);
});
reorderedEvents.unshift(blockEvents.children());
$('.fc-list-table tbody').html(reorderedEvents);
}
#CarComp,
As ADyson commented in the comment to the OP, your best bet if you want do not want to deal with the dom after the html has been rendered is to download the source and make the modification there in the ListView renderSegList function.
Reverse the order of iteration through the list that it is being created and then you will have what you are looking for.
This will, of course, apply to all ListView implementations of the calendar. There would need to be an option added to toggle back and forth, which would be a bit more involved.
For anyone still looking for this, inverted event lists using jquery:
eventAfterAllRender: function(view) {
var eventosRendered = $('#timeline tr');
var eventosInversa = [];
var headingPendiente = null;
eventosRendered.map(function(key, evento) {
switch(evento.className) {
case 'fc-list-heading':
if (headingPendiente) {
eventosInversa.unshift(headingPendiente);
}
headingPendiente = evento;
break;
case 'fc-list-item':
eventosInversa.unshift(evento);
break;
}
});
eventosInversa.unshift(headingPendiente);
$('#timeline tbody').append(eventosInversa);
}
Here's the version I use (fullCalendar v4):
datesRender: function(info) {
var list = $(info.el).find('.fc-list-table tbody');
list.find('.fc-list-heading').each((i,heading) => {
var children = $(heading).nextUntil('.fc-list-heading')
list.prepend(children)
list.prepend(heading)
})
},
I used this for fullCalendar v5. It´s based on #Yo1 answer
eventsSet: function(dateInfo){
var renderedEvents = $('.fc-list-table tr');
var reorderedEvents = [];
var blockEvents = null;
renderedEvents.map(function(key, event) {
if ($(event).hasClass('fc-list-day')) {
if (blockEvents) {
reorderedEvents.unshift(blockEvents.children());
}
blockEvents = $('<tbody></tbody>');
}
blockEvents.append(event);
});
if (blockEvents){
reorderedEvents.unshift(blockEvents.children());
$('.fc-list-table tbody').html(reorderedEvents);
}
},

How to make TagsInput to work with both auto complete & free text

UPDATE
This issue is already discussed in github here
I am using tagsinput with typeahead in bootstrap 3. The problem which I am experiencing is with the value in case if user selects the existing tag. Display text shows it right but .val() returns its actual object. Below is the code
$('#tags').tagsinput({
//itemValue: 'value',
typeahead: {
source: function (query) {
//tags = [];
//map = {};
return $.getJSON('VirtualRoomService.asmx/GetTags?pid=' + $("#<%=hdnPID.ClientID%>").val() + '&tok=' + query)
//, function (data) {
// $.each(data, function (i, tag) {
// map[tag.TagValue] = tag;
// tags.push(tag.TagValue);
// });
// return process(tags);
//});
}
}
//freeElementSelector: "#freeTexts"
});
The problem with above code is that it results as below while fetching tags from web method
This happens when user select the existing tag. New tags no issues. I tried setting itemValue & itemText of tagsinput but not worked. Hence I decided a work-around of this problem. Since I could able get the json string as ['IRDAI", Object], if can somehow parse these object & get the actual tag value then I get the expected result of the code I am looking at.
Below is what it appears in tags input as [object Object] for text selected by user from auto populated drop down
[![enter imt
If I i specify TagId & TagValue to itemValue & itemText as below code
$('#tags').tagsinput({
itemValue: 'TagId',
itemText: 'TagValue',
typeahead: {
source: function (query) {
//tags = [];
//map = {};
return $.getJSON('VirtualRoomService.asmx/GetTags?pid=' + $("#<%=hdnPID.ClientID%>").val() + '&tok=' + query)
//, function (data) {
// $.each(data, function (i, tag) {
// //map[tag.TagValue] = tag;
// tags.push(tag.TagValue);
// });
//});
// return process(tags);
}
}
//freeElementSelector: "#freeTexts"
});
Then the result is displaying as below when below code is executed
var arr = junit.Tags.split(',');
for (var i = 0; i < arr.length; i++) {
$('#tags').tagsinput('add', arr[i]);
}
Given your example JSON response from your data source:
[
{"TagId":"1", "TagValue":"eSign"},
{"TagId":"2", "TagValue":"eInsurance Account"}
]
You'll need to tell tagsinput how to map the attributes from your response objects using itemValue and itemText in your tagsinput config object. It looks like you may have started down that path, but didn't reach the conclusion, which should look something like:
$('#tags').tagsinput({
itemValue: 'TagId',
itemText: 'TagValue',
typeahead: {
source: function (query) {
return $.getJSON('VirtualRoomService.asmx/GetTags?pid=' + $("#<%=hdnPID.ClientID%>").val() + '&tok=' + query);
}
}
});
Be sure to checkout the tagsinput examples.
This may not be the clean solution but I got around this issue through below parsing method. Hope this helps someone.
var items = $('#tags').tagsinput("items");
var tags = '';
for(i = 0; i < items.length; i++)
{
if(JSON.stringify(items[i]).indexOf('{') >= 0) {
tags += items[i].TagValue;
tags += ',';
} else {
tags += items[i];
tags += ',';
}
}

Reactive cursor without updating UI for added record

I am trying to make a newsfeed similar to twitter, where new records are not added to the UI (a button appears with new records count), but updates, change reactively the UI.
I have a collection called NewsItems and I a use a basic reactive cursor (NewsItems.find({})) for my feed. UI is a Blaze template with a each loop.
Subscription is done on a route level (iron router).
Any idea how to implement this kind of behavior using meteor reactivity ?
Thanks,
The trick is to have one more attribute on the NewsItem Collection Say show which is a boolean. NewsItem should have default value of show as false
The Each Loop Should display only Feeds with show == true and button should show the count of all the items with show == false
On Button click update all the elements in the Collection with show == false to show = true
this will make sure that all your feeds are shown .
As and when a new feed comes the Button count will also increase reactively .
Hope this Helps
The idea is to update the local collection (yourCollectionArticles._collection): all articles are {show: false} by default except the first data list (in order not to have a white page).
You detect first collection load using :
Meteor.subscribe("articles", {
onReady: function () {
articlesReady = true;
}
});
Then you observe new added data using
newsItems = NewsItems.find({})
newsItems.observeChanges({
addedBefore: (id, article, before)=> {
if (articlesReady) {
article.show = false;
NewsItems._collection.update({_id: id}, article);
}
else {
article.show = true;
NewsItems._collection.update({_id: id}, article);
}
}
});
Here is a working example: https://gist.github.com/mounibec/9bc90953eb9f3e04a2b3.
Finally I managed it using a session variable for the current date /time:
Template.newsFeed.onCreated(function () {
var tpl = this;
tpl.loaded = new ReactiveVar(0);
tpl.limit = new ReactiveVar(30);
Session.set('newsfeedTime', new Date());
tpl.autorun(function () {
var limit = tpl.limit.get();
var time = Session.get('newsfeedTime');
var subscription = tpl.subscribe('lazyload-newsfeed', time, limit);
var subscriptionCount = tpl.subscribe('news-count', time);
if (subscription.ready()) {
tpl.loaded.set(limit);
}
});
tpl.news = function() {
return NewsItems.find({creationTime: {$lt: Session.get('newsfeedTime')}},
{sort: {relevancy: -1 }},
{limit: tpl.loaded.get()});
},
tpl.countRecent = function() {
return Counts.get('recentCount');
},
tpl.displayCount = function() {
return Counts.get('displayCount');
}
});
Template.newsFeed.events({
'click .load-new': function (evt, tpl) {
evt.preventDefault();
var time = new Date();
var limit = tpl.limit.get();
var countNewsToAdd = tpl.countRecent();
limit += countNewsToAdd;
tpl.limit.set(limit);
Session.set('newsfeedTime', new Date());
}
});

How can I Boolean based on whether my search string is a subset of the class?

I have a Wordpress site with a long table of people and data and I need to add a search filter which shows only the people who match the typed in words. Here is the code I'm using:
$(document).ready(function() {
$('input[name=searchFilterInput]').keyup(function() {
var searchFilterVal = $('input[name=searchFilterInput]').val();
searchFilterVal = searchFilterVal.replace(/ /g, '-');
searchFilterVal = searchFilterVal.toLowerCase();
if(searchFilterVal == '') {
$('tr.hide').fadeIn('slow').removeClass('hide');
} else {
$('tr.fellows').each(function() {
if(!$(this).hasClass(searchFilterVal)) {
$(this).fadeOut('normal').addClass('hide');
} else {
$(this).fadeIn('slow').removeClass('hide');
}
});
}
});
});
This works great as long as the input exactly matches the class. I need if(!$(this).hasClass(searchFilterVal)) {
to basically say "If (this) .hasClass(if the input matches any portion of the class)"
Does that make sense? Here is the page:
http://cambridgefellows.com/directory-of-fellows/?searchFilterInput=Media
Is is the second search field - the one on the right hand side of the drop down menus.
I'm sorry if this question is not asked clearly - please let me know if I can make it more clear. Thanks!
Try this .. I've cheked it , it's working
$(document).ready(function() {
$('input[name=searchFilterInput]').keyup(function() {
var searchFilterVal = $('input[name=searchFilterInput]').val();
searchFilterVal = searchFilterVal.replace(/ /g, '-');
searchFilterVal = searchFilterVal.toLowerCase();
if(searchFilterVal == '') {
$('tr.hide').fadeIn('slow').removeClass('hide');
} else {
$('tr.fellows').each(function() {
var pattern = $(this).attr('class'); // the pattern to be matched
var match = pattern.match(searchFilterVal);//If pattern matches it returns the match
if(!match) {
$(this).fadeOut('normal').addClass('hide');
} else {
$(this).fadeIn('slow').removeClass('hide');
}
});
}
});
});

Filter results from Google Autocomplete

Is there a way to get the results from Google Autocomplete API before it's displayed below the input? I want to show results from any country except U.S.A.
I found this question: Google Maps API V3 - Anyway to retrieve Autocomplete results instead of dropdown rendering it? but it's not useful, because the method getQueryPredictions only returns 5 elements.
This is an example with UK and US Results: http://jsfiddle.net/LVdBK/
Is it possible?
I used the jquery autocomplete widget and called the google methods manually.
For our case, we only wanted to show addresses in Michigan, US.
Since Google doesn't allow filtering out responses to that degree you have to do it manually.
Override the source function of the jquery autocomplete
Call the google autocompleteService.getQueryPredictions method
Filter out the results you want and return them as the "response" callback of the jquery autocomplete.
Optionally, if you need more detail about the selected item from Google, override the select function of the jquery autocomplete and make a call to Google's PlacesService.getDetails method.
The below assumes you have the Google api reference with the "places" library.
<script src="https://maps.googleapis.com/maps/api/js?key=[yourKeyHere]&libraries=places&v=weekly" defer></script>
var _autoCompleteService; // defined globally in script
var _placesService; // defined globally in script
//...
// setup autocomplete wrapper for google places
// starting point in our city
var defaultBounds = new google.maps.LatLngBounds(
new google.maps.LatLng('42.9655426','-85.6769166'),
new google.maps.LatLng('42.9655426','-85.6769166'));
if (_autoCompleteService == null) {
_autoCompleteService = new google.maps.places.AutocompleteService();
}
$("#CustomerAddress_Street").autocomplete({
minLength: 2,
source: function (request, response) {
if (request.term != '') {
var googleRequest = {
input: request.term,
bounds: defaultBounds,
types: ["geocode"],
componentRestrictions: { 'country': ['us'] },
fields: ['geometry', 'formatted_address']
}
_autoCompleteService.getQueryPredictions(googleRequest, function (predictions) {
var michiganOnly = new Array(); // array to hold only addresses in Michigan
for (var i = 0; i < predictions.length; i++) {
if (predictions[i].terms.length > 0) {
// find the State term. Could probably assume it's predictions[4], but not sure if it is guaranteed.
for (var j = 0; j < predictions[i].terms.length; j++) {
if (predictions[i].terms[j].value.length == 2) {
if (predictions[i].terms[j].value.toUpperCase() == 'MI') {
michiganOnly.push(predictions[i]);
}
}
}
}
}
response(michiganOnly);
});
}
},
select: function (event, ui) {
if (ui != null) {
var item = ui.item;
var request = {
placeId: ui.item.place_id
}
if (_placesService == null) {
$("body").append("<div id='GoogleAttribution'></div>"); // PlacesService() requires a field to put it's attribution image in. For now, just put on on the body
_placesService = new google.maps.places.PlacesService(document.getElementById('GoogleAttribution'));
}
_placesService.getDetails(request, function (result, status) {
if (result != null) {
const place = result;
if (!place.geometry) {
// User entered the name of a Place that was not suggested and
// pressed the Enter key, or the Place Details request failed.
//window.alert("No details available for input: '" + place.name + "'");
return;
}
else {
var latitude = place.geometry.location.lat();
var longitude = place.geometry.location.lng();
// do something with Lat/Lng
}
}
});
}
}
}).autocomplete("instance")._renderItem = function (ul, item) {
// item is the prediction object returned from our call to getQueryPredictions
// return the prediction object's "description" property or do something else
return $("<li>")
.append("<div>" + item.description + "</div>")
.appendTo(ul);
};
$("#CustomerAddress_Street").autocomplete("instance")._renderMenu = function (ul, items) {
// Google's terms require attribution, so when building the menu, append an item pointing to their image
var that = this;
$.each(items, function (index, item) {
that._renderItemData(ul, item);
});
$(ul).append("<li class='ui-menu-item'><div style='display:flex;justify-content:flex-end;'><img src='https://maps.gstatic.com/mapfiles/api-3/images/powered-by-google-on-white3.png' /></div></li>")
}

Resources