Grails Searchable Plugin for multiple domain classes and multiple search fields with a single submit button - plugins

I have multiple domain objects and having one to many, many to many relationships and search data comes from couple of tables and it is always same. I implemented Searchable plugin in my app and able to retrieve results when I have single search field like this:
<g:form url='[controller: "searchable", action: "searchContact"]' id="searchableForm" name="searchableForm" method="get">
<g:textField name="query" value="${params.query}" size="40"/>
<input type="submit" value="Search Contact" />
</g:form>.
But I have multiple text fields, check boxes and g:select boxes to get searchTerm. Based on any one of fields or multiple search selections I have to get search results. How to include all search fields in between and having a single submit button for all the params. Here is my search action code:
def searchContact = {
if (!params.query) {
return [:]
}
try {
String searchTerm = params.query
println searchTerm
return [searchResult: searchableService.search(searchTerm, params)]
} catch (SearchEngineQueryParseException ex) {
return [parseException: true]
}
}
Quick suggestions are appreciated.

You can pass all the terms in one String query separating each token/word by a space, so for example if you have two Domain classes one called Person and another one called Job and you search for "John" and "Engineer", your String query should be "John Engineer" and that should get you both domain objects.
Is that kind of what you are looking for?

Related

Flutter Firestore only return user overview ListTile when field contains specific words

I am listing users in a CustomScrollView/SliversList,ListTiles. I have a String field in my firestore and only want to return ListTile of a user, where his String field contains specific words (more than 2). For example, the users fields contain: "Apples, Ice, Bananas, Soup, Peaches, e.g...." and i want to list all users which have apples and bananas inside the field. how can i achieve this?
The only way to do it at the moment (with the way you have it set up) is actually pulling the value and doing a string "contains" or splitting the string into an array and check whether the value is within that array, otherwise I'd advise to refactor that field and make it into an array, that way you can perform a native arrayContainsAny against your field.
For you it will look like this (with your current implementation):
// ... after pulling all users' documents
// let's say your field is called 'foodField':
var criteria = 'Banana';
var fieldContent = doc.data()['foodField'];
// you can either do this:
if (fieldContent.toLowerCase().contains(criteria.toLowerCase())) {
// ...
}
// or you can tokenize it depending on your purposes...
var foodTokens = fieldContent.split(',').map((f) => f.toLowerCase());
if (foodTokens.contains(criteria.toLowerCase()) {
// ...
}
If your Firestore field was an array type, then you could've just done that, while querying:
FirebaseFirestore.instance.collection('users').where('foodField', arrayContainsAny: ['Banana', 'Apples'])
Which then would give you only the users whose foodField contain that value.
As you can see from previous questions on querying where text contains a substring, Firestore does not currently support such text searches. The typical solutions are to either perform part of your filtering in your application code as Roman answered, or to integrate a third-party full-text search solution.
In your specific case though, your string seems to be a list of words, so I'd recommend considering to change your data model to an array of the individual values in there:
"foodFields": ["Apples", "Ice", "Banana", "Soup", "Peaches"]
You can then use array field operators in the query.
While there is no array-contains-all operator, using array-contains you can at least filter on one value in the database, and with array-contains-any you can do on OR like condition.
Another data model would be to store the individual values in a map field with value true for each of them:
"foodFields": {
"Apples": true,
"Ice": true,
"Banana": true,
"Soup": true,
"Peaches": true
}
With such a structure you can perform an AND like query with:
collectionRef
.where('foodFields.Apples', isEqualTo: true)
.where('foodFields.Bananas', isEqualTo: true)

How to approach dynamically generated Firestore queries depending on nested user created Sub Collections?

/Countries/Lebanon/Governorates/Mount Lebanon/Districts/Chouf/Cities/Wadi al-Zayneh/Data/Products/Main Categories/Restaurants & Bakeries/Sub Categories/Snack/Sub Categories/Abo Arab Cafe
So as you can see, this is a snippet from my current Firestore structure. So many deeply nested collections. The issue is, I want to keep going deeper as long as a collection called 'Sub Categories' is found which in that case I would render them in the UI. And when eventually I reach a level where 'Sub Categories' is not found, I will render a different UI and show the actual products (The last document "Abo Arab Cafe" contains all the products as maps). The pattern of how many Sub Categories there are is unexpectable and can be modified by the end user.
How can I keep checking for Sub Categories? How to manage my queries in a way that they are dynamically generated at each level at the client-side?
I use Flutter. Here is my current queries structure:
import 'package:cloud_firestore/cloud_firestore.dart';
class FirebaseServices {
final FirebaseFirestore _db = FirebaseFirestore.instance;
CollectionReference mainCategoryCollectionReference() {
CollectionReference mainCategoryCollectionReference = _db.collection(
'/Countries/Lebanon/Governorates/Mount Lebanon/Districts/Chouf/Cities/Wadi al-Zayneh/Data/Products/Main Categories');
return mainCategoryCollectionReference;
}
CollectionReference subCategoryCollectionReference(
String parentSelectedCategory) {
CollectionReference mainCategoryCollectionReference = _db.collection(
'/Countries/Lebanon/Governorates/Mount Lebanon/Districts/Chouf/Cities/Wadi al-Zayneh/Data/Products/Main Categories/$parentSelectedCategory/Sub Categories');
return mainCategoryCollectionReference;
}
bool checkIfSubCategoriesExist(CollectionReference collectionReference) {
bool subCategoriesExist;
collectionReference.get().then((value) => {
subCategoriesExist = value.docs.isNotEmpty,
print('SubCategoriesExist: $subCategoriesExist')
});
return subCategoriesExist;
}
}
This works only if I know for certain how many levels of deepness there are, but since this can be modified by the user, it won't work.
Sorry for the very long question I had no idea how to explain it properly and clearly. Thank you in advance!
The structure is all wrong, there is no point in the structure being this deeply nested. The structure of the database needs to match what has to appear in the UI.
Assuming this is a worldwide application since you are using countries then you have to do the following:
Collection
Document
Fields
Countries
Random ID
countryName - arrayOfDistrict- arrayOfGovernorates
3 Fields under each document id, containing information about the country.
Then regarding Resturants:
Collection
Document
Fields
SubCollection
subCollectionId
Fields
Resturant
Random ID
resturant_name- resturant_location - info_about_resturant
Menu
randomId
dish_name - price -...
The problem with your db structure is that it is very nested instead of making a flat structure and that right now you are harcoding the whole path.
Using the above structure, you can create a dropdown with list of countries if the user chooses Lebanon, then you get the districts and the governorates. Then you can do a call to get the resturants that are inside each district, since in the documents inside Resturant collection you can get location of each resturant and name.
After that on click of each resturant, you will get the data inside the subcollection that will contain the full menu.
I think I found the solution with the help of a friend!
Since the checkIfSubCategoriesExist function is always checking on the very last reached level(using the collectionReference argument) whether Sub Categories exists or not, he suggested that in case it does exist, I can append to its argument collectionReference the new "Sub Categories" String to the path as a variable! This way I can query on it and voila!

Search for child items matching certain name values

This page describes how to retrieve an item, (immediate) child items and searching in Sitecore9 using the RESTful API (via PostMan).
What it doesn't appear to say is how to combine those queries.
I would like to search the children of an item which is specified by path. So, currently, I have this returning an item:
GET https://{{sitecorehost}}/sitecore/api/ssc/aggregate/content/Items('{{sitecorehome}}/banners-tests')?sc_apikey={{sitecore-master-apikey}}
I also have this returning the child items of that item:
GET https://{{sitecorehost}}/sitecore/api/ssc/aggregate/content/Items('{{sitecorehome}}/banners-tests/Subcontent/Image and Texts')/Children?sc_apikey={{sitecore-master-apikey}}
However, because the children are not immediate children - they are two levels down at /Subcontent/Image and Texts - I cannot request them. Yes, I could search for them, but then any items would come back with the matching criteria and I only want to search items under that particular path.
I would like something which, I imagine, would look something like this:
GET https://{{sitecorehost}}/sitecore/api/ssc/aggregate/content/Items?sc_apikey={{sitecore-master-apikey}}&$filter=Name eq 'banner' and Path eq 'banners-tests'
Or perhaps this:
GET https://{{sitecorehost}}/sitecore/api/ssc/aggregate/content/Items('{{sitecorehome}}/banners-tests')/Children?sc_apikey={{sitecore-master-apikey}}&$filter=Name eq 'banner'
But these do not work.
#Matt We can do filtering based on the item path. For example, considering item path as :
'sitecore/content/home/tenant1/Subcontent/Image and Texts/neededitem' - the one needed
'sitecore/content/home/tenant1/Subcontent/Image and
Texts/item1/neededitem/notneededitem' - the one we need to exclude
since '/' is not a valid character in the Sitecore item name and indicates the children of the needed item.
Hence, it can be used as a filter in javascript.
So we can split by 'Image and Texts' and then find the items.
For example, consider an array of results and let us say object with a collection of items is items and item path of each item is denoted by Path(let's say, this can be some other property as well) property
let items = [{
Path: 'sitecore/content/home/tenant1/Subcontent/Image and Texts/neededitem',
anotherProperty: 'text-val1'
}, {
Path: 'sitecore/content/home/tenant1/Subcontent/Image and Texts/item1/neededitem/notneededitem',
anotherProperty: 'text-val2'
}];
const results = items.filter(item => {
const splittedPath = item.Path.split('Image and Texts');
if (splittedPath[1].split("/").length <= 2) {
return item;
}
});
console.log(results);
In case your SSC controller (C#) is custom one and having access to Sitecore Context object or Sitecore APIs then the GetChildren() method of Item class will bring children of first level only.
I hope this helps.

How to retrieve only part of a document back in a call

I need to retrieve only part of a document and call it via a helper so that I can render a subtemplate multiple times as the part I require to pull from the db is an array of object itself. I have the following as the fields. What I need to do with my helper is only retrieve the ordersDispatch array of one particular document which would be uniquely called by the tripNumber field.
I have tried several things but nothing has come close to only having an array of the objects in the orderDisptach field be returned in a fashion that it can be used by the helper to render my subtemplate for each object in the array.
{
tripNumber: companyRecord.lastTripNum + 1,
custID: $('input:hidden[name=orderCustomerId]').val(),
custContact: $('input:text[name=customerContact]').val(),
custEmail: $('input:text[name=customerEmail]').val(),
trailerSealNum: $('input:text[name=trailerSealNum]').val(),
orderBroker: $('input:text[name=orderBroker]').val(),
orderEquipment: $('input:text[name=orderEquipment]').val(),
orderLoadNum: $('input:text[name=orderLoadNum]').val(),
orderPlacedDate: $('input:text[name=orderPlacedDate]').val(),
orderPrivateNotes: $('textarea[name=orderPrivateNotes]').val(),
orderPublicNotes: $('textarea[name=orderPublicNotes]').val(),
orderCurrency: $("input[name=orderCurrency]:checked").val(),
orderCharges: $('input:text[name=orderCharges]').val(),
orderFUELCheck: $('input:checkbox[name=orderFUELCheck]').is(':checked'),
orderFUELPerc: $('input:text[name=orderFUELPerc]').val(),
orderFUELTotal: $('input:text[name=orderFUELTotal]').val(),
orderGSTCheck: $('input:checkbox[name=orderGSTCheck]').is(':checked'),
orderGSTPerc: $('input:text[name=orderGSTPerc]').val(),
orderGSTTotal: $('input:text[name=orderGSTTotal]').val(),
orderPSTCheck: $('input:checkbox[name=orderPSTCheck]').is(':checked'),
orderPSTPerc: $('input:text[name=orderPSTPerc]').val(),
orderPSTTotal: $('input:text[name=orderPSTTotal]').val(),
orderTAXCheck: $('input:checkbox[name=orderTAXCheck]').is(':checked'),
orderTAXPerc: $('input:text[name=orderTAXPerc]').val(),
orderTAXTotal: $('input:text[name=orderTAXTotal]').val(),
orderTotalCharges: $('input:text[name=orderTotalCharges]').val(),
ordeBlockInvoicing: $('input:checkbox[name=ordeBlockInvoicing]').is(':checked'),
orderExtraCharges: orderExtraCharges,
orderPickups: puLocations,
orderDeliveries: delLocations,
orderDispatch: dispatchLocations,
createdDate: new Date(),
createdUser: currentUser.username
Any help in building a helper that will accomplish this would be greatly appreciated as I am new to meteor and mongo.
The following helper should give you what you need:
Template.oneTrip.helpers({
orderDispatch: function(tn){
return Trips.findOne({ tripNumber: tn }).orderDispatch;
}
});
Trips.findOne({ tripNumber: tn }) gets you an individual document and .orderDispatch returns the value of the orderDispatch key which in your case will be an array.
html:
<template name="oneTrip">
{{#each orderDispatch this._id}} <!-- assuming you've already set the data context to an individual order -->
{{this}} <!-- now the data context is an element of the orderDispatch array -->
{{/each}}
</template>

Having a list of strings represented in a database using ORMLite

First of I am new to ORMLite. I would like my model class to have a field which is a list of strings, that would eventually hold a list of tags for my model object.
Which ORMLite annotations should I use?
Firstly I don't want to have a table of all tags, and then use the #ForeignCollectionField.
Also I thought of using the #DatabaseField(dataType=DataType.SERIALIZABLE) annotation, but it turns out that List<String> doesn't implement the Serializable interface.
What are your suggestions?
First of all, List doesn't implement Serializable but ArrayList certainly does as well as most of the common collection implementations. But storing a huge list is probably not the best of doing this from a pure object model standpoint.
So why don't you want to have a table of all tags? That's the best way from a pure model standpoint. It will require a 2nd query if you need them every time. That's the way hibernate would store a list or array of tags.
After reading your comment #creen, I still think you do want a table of tags. Your model class would then have:
#ForeignCollectionField
Collection<Tag> tags;
The tags table would not have a single tag named "red" with multiple model classes referring to it but multiple "red" entries. It would look like:
model_id name
1 "red"
1 "blue"
2 "red"
3 "blue"
3 "black"
Whenever you are removing the model object, you would first do a tags.clear(); which would remove all of the tags associated with that model from the tags table. You would not have to do any extra cleanup or anything.
No need to go for #ForeignCollectionField for simple String Array
Change your code
#DatabaseField(dataType=DataType.SERIALIZABLE)
List<String> users;
to
#DatabaseField(dataType = DataType.SERIALIZABLE)
String[] users;
Database doesn't want to store dynamically grow able arrays. That is the reason it allows only static array like string[] and not List.
I added two properties... one that gets written to the database as a csv string and the other that translates this:
[Ignore]
public List<string> Roles
{
get
{
return new List<string>(RolesCsv.Split(new char[] { ',' }));
}
set
{
RolesCsv = string.Join(",", value);
}
}
public string RolesCsv { get; set; }