The Task
Picture the typical search-bar and list of results. With framework7 in my Cordova app we can of course implement this easily even with a virtual list where elements aren't all being rendered. But what I'm trying to implement is a search-bar which will effect two virtual lists. The reason for this is as part of the app I'm creating there will be a list for new and refurbished equipment. Each list is on a different slider tab on the same page, so only one list will be seen at any one moment. But I need to use a singular search-bar to filter both.
What I've done so far
Currently the slider with two separate virtual lists is set up, the search bar is initiated. Is there any good way to allow the searchbar to apply to both lists without going crazy with the customSearch true parameter. If that is the only option how would I keep the default methods for filtering lists, I simply want to apply it twice.
I've also considered creating a second searchbar with display:none and having it copy input over from the seen searchbar. Then hidden searchbar could apply to one list while the seen one would apply to the other. But that would be really hacky and not neat at all.
Sorry If this is a bit unclear, I'm not sure how best to approach the challenge, thanks for any help
I initialise my two virtual lists, I make the search function accessable from outside of that. Then I initalise my searchbar with customSearch true, on search I use my virtual list (vl) array and search them individually using the filter and query functions available. It was a bit of a pain but this works perfectly fine. The vl[1] in this example is just a copy of vl[0] since I haven't actually set it up yet.
function virtualSearchAll(query,items){//search query
var foundItems = [];
for (var i = 0; i < items.length; i++) {
// Check if title contains query string
if (items[i].internal_descriptn.toLowerCase().indexOf(query.toLowerCase().trim()) >= 0) foundItems.push(i);
}
// Return array with indexes of matched items
return foundItems;
}
var vl = [];
vl[0] = myApp.virtualList('.vl0', {//initialise new products
items: selectProd,
template: '<li data-value="{{model_id}}" class="item-content"><div class="item-inner"><div class="item-title list-title">{{internal_descriptn}}</div></div></li>',
searchAll: function(query,items) {
return virtualSearchAll(query, items);
}
});
vl[1] = myApp.virtualList('.vl1', {//initialise test/referb products
items: [{internal_descriptn:"test desc",model_id:"wehayy"}],
template: '<li data-value="{{model_id}}" class="item-content"><div class="item-inner"><div class="item-title list-title">{{internal_descriptn}}</div></div></li>',
searchAll: function(query,items) {
return virtualSearchAll(query, items);
}
});
var mySearchbar = myApp.searchbar('.searchbar', {
customSearch: true,
searchIn: '.item-title',
onSearch: function(s) {
previousQuery = s.query;
for (let n in vl) {
let vlItems = virtualSearchAll(s.query,vl[n].items);
vl[n].filterItems(vlItems);
if(vlItems.length === 0) {//if the search has no results then show no results element else hide it
$('.vl'+n+'-not-found').show();
} else {
$('.vl'+n+'-not-found').hide();
}
}
highlightRows();
}
});
I should add that highlightRows() is part of a different functionality which needs to be refreshed on every search. You can ignore that
Related
Introduction
I coded a portfolio website for a friend of mine as a university project.
I started to learn Vue.js and started to dive into JavaScript in general.
http://janpzimmermann.com
In some cases I'm still struggling with all the new stuff. Therefore, I'm mixing Vue.js with jQuery and JavaScript. I know this isn't best practice.
But after spending years and years with mostly html and css (and sometimes a little PHP) some things are still new to me. ;)
The Problem
I created a gallery grid (the content is loaded via Vue) and wanted to be able to filter the content via navigation.
Therefore, I came across the following method:
https://www.w3schools.com/howto/howto_js_filter_elements.asp
/* content filter */
filterSelection("all");
function filterSelection(c) {
var x, i;
x = document.getElementsByClassName("content-filter");
if (c == "all") c = "";
// Add the "view" class (display:block) to the filtered elements, and remove the "view" class from the elements that are not selected
for (i = 0; i < x.length; i++) {
w3RemoveClass(x[i], "view");
if (x[i].className.indexOf(c) > -1) w3AddClass(x[i], "view");
}
}
// Show filtered elements
function w3AddClass(element, name) {
var i, arr1, arr2;
arr1 = element.className.split(" ");
arr2 = name.split(" ");
for (i = 0; i < arr2.length; i++) {
if (arr1.indexOf(arr2[i]) == -1) {
element.className += " " + arr2[i];
}
}
}
// Hide elements that are not selected
function w3RemoveClass(element, name) {
var i, arr1, arr2;
arr1 = element.className.split(" ");
arr2 = name.split(" ");
for (i = 0; i < arr2.length; i++) {
while (arr1.indexOf(arr2[i]) > -1) {
arr1.splice(arr1.indexOf(arr2[i]), 1);
}
}
element.className = arr1.join(" ");
}
Unfortunately, there seems to be a bug.
When I open a project, don't close it with the close button and navigate to a new category and open a project there, the project which was opened before is added to the DOM again (even if it doesn't belong to the category!).
I neither couldn't find the bug, yet. Nor I was able to be sure it's not a fault of Vue.
But I tried to replace the JavaScript filter with a jQuery one (this one worked with data-attributes), sadly this wasn't working for me. As I just could add one attribute per item. But sometimes a project belongs to more than one category. (this one: https://jsfiddle.net/k5g6wcw3/21/)
// Variable
var posts = $('.post');
posts.hide();
// Click function
$( ".sort" ).click(function() {
// Get data of category
var customType = $( this ).data('filter'); // category
console.log(customType);
console.log(posts.length); // Length of articles
posts
.hide()
.filter(function () {
return $(this).data('cat') === customType;
})
.show();
});
// All
$( "#showAll" ).click(function() {
$( ".post" ).show();
});
Further thoughts
I know this should be also possible to do with vue routes and maybe vuex, but couldn't find a way how to do it which was understandable to me.
thanks
Mixing Vue and jQuery like this is going to make things much, much, much more difficult than they need to be. The problem you ran into with your first filter was because Vue didn't know about the DOM modifications your javascript filter was doing, so overwrote them on its next update. You're going to have exactly the same problem with the jQuery filter, if you get it working.
So don't do that.
Right now you're letting Vue draw a full list of items, then trying to crawl through the DOM after the fact adding data attributes and hiding the elements you want filtered out. This is a lot of extra work (for both you and the browser), and will fail whenever Vue does a redraw (because it will blow away the changes you made externally.)
Instead, put the attributes in the data you're feeding to Vue, and let Vue filter it before it draws the DOM. Which is a thing that is pretty simple to do in Vue.
(You had mentioned the need for multiple categories per project; here's a quick example of a computed property for that:
data: {
currentFilter: 'photo', // set this from a route param, or a v-model, or whatever
projects: [
{name: "Project one", categories: ['photo', 'book']},
{name: "Project two", categories: ['website']},
{name: "Project 3", categories: ['photo']}
// ...
]
},
computed: {
filteredProjects() {
return this.projects.filter(project => {
return project.categories.indexOf(this.currentFilter) > -1
})
}
}
Have your template v-for over filteredProjects instead of projects, and you're done. Whenever data.currentFilter changes, the list will redraw, filtered by the new value, automatically.)
It's possible to use jQuery from within Vue, but it requires a pretty good understanding of what the framework is doing so you don't wind up creating conflicts between it and your external code. (And I've yet to find a case where it wasn't simpler to just rewrite the jQuery thing as a Vue component anyway.) The same is true of other modern frameworks like React or Angular; these all work on a very different model than the DOM-first strategy jQuery tends to fall back on. Especially when learning, you'll have a much easier time of it if you stick to just one framework at a time instead of trying to mix things together.
Here is a drop down list in SmartClient: http://www.smartclient.com/#dropdownGrid.
I want to make a selection using JavaScript. Like, I run some JavaScript in console, and the drop list will select a specific item.
I did some research, found a code snap to do this (the code is in Java, but I think there should be similar functions in JavaScript):
Record rec = perdomainGrid.getRecordList().find("domaine_id", domaine_id);
perdomainGrid.selectSingleRecord(rec);
If I want to make selection, first I need to obtain perdomainGrid object. In my above giving link, the drop down list id in GWT is exampleForm (can be seen in dropDownGrid.js tab). I try to get the object by:
var form = isc.DynamicForm.getById("exampleForm");
form does exist, but there is no getRecordList() function on it, there is selectSingleRecord() function on it though.
I try to check form's class by form.className, its value is normal. I don't know what does that mean.
I'm kind of confused now. Could somebody help me on this?
isc_SelectItem_5 has a function called pickValue(), it takes one parameter SKU. This function can be used to select item.
var itemName = "Letter Tray Front Load Tenex 200 Class Blk #23001";
var data = isc_SelectItem_5.optionDataSource.cacheData;
var targetSKU = data.find(function(e) {
if (e.itemName == itemName) {
return e;
}
}).SKU;
isc_SelectItem_5.pickValue(targetSKU);
I am building a meteor based webapp. One of the pages has a drop down menu, which the user has to select options (various school districts) from. The selection needs to be detected, followed by querying the database filtering documents based on the selection and counting the documents returned, followed by rendering a template (a chart built using highcharts.js)
Code as follows:
Template.districtDropdown.events({
'change' : function(event, template){
event.preventDefault();
var selectedValue = template.$("#selectDistrict").val();
console.log("You Selected " + selectedValue);
var filter = {
find: {
'School District' : selectedValue
}
};
Meteor.subscribe('aggByDistrict', filter);
productNames2 = _.uniq(CombinedData.find().map( function(doc) { return doc.Product; }));
console.log(productNames2);
var productValues2 = [];
for(var i = 0; i < productNames2.length; i++) {
productValues2.push(CombinedData.find({'Product' : productNames2[i]}).count())
};
console.log(productValues2);
}
});
I'm facing three issues.
The console on the client side throws an error "productNames2" has not been defined as soon as the page loads, pointing to the line which has the for loop (even before I've made any selection).
The first time I click on one of the options in the drop down menu, I get empty arrays (the two console.log(productNames2) and console.log(productValues2), but when I click on some other option, it works the second time. No idea why.
I want to render a template {{> highchart2}} after the user has selected an option from the drop down and the two arrays (productNames2, productValues2) have been populated.
Can anyone give me ideas on how I can go about resolving these issues?
Several problems with your code
Subscribe needs to be put in Template.districtDropdown.created. If you subscribe during the events, there might be the postpone during the subscription and no data available during the events
event should be attached to a DOM component. As in change #selectDistrict and then, you select the value like this var selectedValue = $(event.target).val();
I have a list linked to a store filled with Facebook friends. It contains around 350 records.
There is a searchfield at the top of the list which triggers the following function on keyup:
filterList: function (value) {
// console.time(value);
if (value === null) return;
var searchRegExp = new RegExp(value, 'g' + 'i'),
all = Ext.getStore('Friends'),
recent = Ext.getStore('Recent'),
myFilter;
all.clearFilter();
recent.clearFilter();
// console.log(value, searchRegExp);
myFilter = function (record) {
return searchRegExp.test(record.get('name'));
}
all.filter(myFilter);
recent.filter(myFilter);
// console.timeEnd(value);
},
Now, this used to work fine with ST2.1.1 but since I upgraded the app to ST2.2. It's really slow. It even makes Safari freeze and crash on iOS...
This is what the logs give :
t /t/gi Friends.js:147
t: 457ms Friends.js:155
ti /ti/gi Friends.js:147
ti: 6329ms Friends.js:155
tit /tit/gi Friends.js:147
tit: 7389ms Friends.js:155
tito /tito/gi Friends.js:147
tito: 7137ms
Does anyone know why it behaves like this now, or does anyone have a better filter method.
Update
Calling clearFilter with a true paramater seems to speed up things, but it's not as fast as before yet.
Update
It actually has nothing to do with filtering the store.
It has to do with rendering list-items. Sencha apparently create a list-item for every record I have in the store instead of just creating a couple of list-items and reusing them
Could there be an obvious reason it's behaving this way ?
Do you have the "infinite" config on your list set to true?
http://docs.sencha.com/touch/2.2.0/#!/api/Ext.dataview.List-cfg-infinite
You probably don't want the list rendering 300+ rows at once, so setting that flag will reduce the amount of DOM that gets generated.
I'm using JQuery UI's Autocomplete to load a list of users into a custom control and display them in the regular suggestion list. On page load, I'm calling this field's search() method to populate the form with some initial data.
The problem is that the on page load event displays the suggestion list, as well as populating the custom control. How can I disable the suggestion list for the first query only?
I've tried monkey-patching various methods, and they either break or do nothing.
Of course, the alternative is setting a timeout on a function call to close it, but this will create an ugly flicker I'd like to avoid.
In the end, I overwrote Autocomplete's _suggest method like so:
suggest = function(items) {
ul = this.menu.element.empty().zIndex( this.element.zIndex() + 1 );
this._renderMenu(ul, items);
this.menu.deactivate();
this.menu.refresh();
/* these four lines are the difference between regular autocomplete and this autocomplete */
if (!this.first)
ul.show();
else
this.first = false;
this._resizeMenu();
args = {
of: this.element
}
ul.position($.extend(args, this.options.position));
if (this.options.autoFocus)
this.menu.next(new $.Event("mouseover"));
/* initialisation*/
field.autocomplete().data('autocomplete').first = true;
field.autocomplete().data('autocomplete')._renderItem = renderItem;
field.autocomplete().data('autocomplete')._suggest = suggest;
It's not the most elegant solution, but it works.