Grails how to dynamically search based on varying search parameters - grails

Let's say we have to perform a search on a bunch of database records and the search criteria is defined by a few check boxes and a text field. So, if check box for 'US Citizen' is checked and the textfield 'name' is filled with "John Doe". Then my search in my Grails application will be something like:
def results = IndividualRecord.findAllWhere(citizenship: 'US', name: 'John Doe')
now, if someone also checks the box 'voter' then I'll change my search to:
def results = IndividualRecord.findAllWhere(citizenship: US, name: 'John Doe', voter: true)
In reality my application has a couple of text fields and 6 checkboxes. Obviously I cannot write a custom search function based on each different combination of criteria there's just too many combinations. What would be an efficient way to tackle this problem? So, I guess ideally there would be one search function that could take in the customized query parameters and search with those. I'm a bit lost and confused. Any help appreciated.

In reality my application has a couple of text fields and 6 checkboxes. Obviously I cannot write a custom search function based on each different combination of criteria there's just too many combinations
You don't really need to concern yourself with handling the combinations. If you can receive the field names and values as a Map, you can simply pass the Map to findAllWhere, e.g.
def search(Map predicates) {
IndividualRecord.findAllWhere(predicates)
}
If for some reason you don't like this approach, you can use a criteria query instead, e.g.
def search(Map predicates) {
IndividualRecord.withCriteria {
if (predicates.voter != null) {
eq 'voter', predicates.voter
}
if (predicates.citizenship) {
eq 'citizenship', predicates.citizenship
}
if (predicates.name) {
eq 'name', predicates.name
}
}
}

Related

angular ui.grid search by field

If I turn off enableFilter and roll my own input fields to search through the grid, how do I search only by one column?
See my plunker
$scope.searchGrid = function(searchTerm){
console.log("Term: " + searchTerm);
$scope.gridOptions.data = $filter('filter')(myData, searchTerm, undefined);
}
I want to have multiple input fields and send in the filter to each column as needed.
You would typically bind to a specific gridApi.grid.columns[x].filters[0].term, that's basically what the grid implementation of the filters does.
You'd end up with something like this:
http://plnkr.co/edit/2u56wGFUOCxPLp4ekEkT?p=preview
The other problem is that you now have filter boxes, which presumably you didn't want. You can suppress these by playing with the headerTemplates, but it is a bit of messing around.

ASP.NET MVC 3 - Filtering list with multiple fields

I have a list of Assets displayed in a HTML table, the data model for these is quite complicated; they each have a Manufacturer/Model pair, Location and Status just to name a few.
To filter by one field, it is simple something along the lines of
#Html.ActionLink(item.LocationName, "Index",
new { LocationID = item.LocationID.ToString() }, null)
This will produce a URL looking like
hxxp://Localhost/Assets/Index?LocationID=3
The problem arises when trying to filter by both multiple different fields and multiple similar fields, I cannot seem to find a way to produce URLs like
hxxp://Localhost/Assets/Index?LocationID=3,4
or
hxxp://Localhost/Assets/Index?LocationID=3&Status=Active
I may just be trying to treat MVC like Webforms but I don't see any way of passing all these combinations of filtered fields back to my controller to produce a table that shows the data the end user wants to see.
You can also try
Sprint.Filter

Insert link in String field if substring matches

I have a "comments" String field in my domain. On each save or update of the field I want to check if the field contains a sub string that matches a String from another field of the same domain (or different domain for that matter). I need to run through all the instances of that field to see if there is a match. If it matches I want to transform it into a link inside the comments field to a show action for that entry matching the sub string.
So for example, a comments field for a product with a serial number would note if the product has been replaced by another one by giving it's serial number in the comments field. Like: "This product was replaced by SN1234". I want to automatically transform SN1234 into a link to show the product with serial number SN1234.
What is the best way to go about this ? In the controller, in the GSP ? How ?
As long as the column you're trying to match on is indexed, you'll just need to do a query for the match and if found, modify your comment to include the URL. Controller or Service doesn't really matter for the lookup (although I would probably put it in a service). You'll want to be sure the search is not transactional so it will be as fast as it can be. No way I would do any of this in a GSP.
To insert the link, you could a simple find and replace. Once you know which text you want turned into the link, pseudo code follows:
def comment = "This product was replaced by SN1234"
def match = "SN1234"
def link = g.link(action: "show", controller: "product", id: "${product.id}", match)
comment = comment.replace(match, link)
Which you would then end up with
"This product was replaced by <a href='/product/1234'>SN1234</a>"
There may be more efficient ways to do this, but this is a good place to start.
You can use GORM events to do it in your domain. So whenever the domain is inserted/updated you can check that your field has been changed. Then you can insert your link.
def beforeInsert() {
yourMethod()
}
def beforeUpdate() {
if (isDirty('yourField')) {
yourMethod()
}
}

Is it possible to name (or tag) FormStack fields with simple identifiers?

This question assumes familiarity with FormStack, a drag-and-drop WYSIWYG online form builder.
Background
A client is using FormStack to manage forms. Currently, form submissions are emailed, then manually entered into a database. Predictably, my task is to automate this process. This is easy enough using FormStack's WebHooks API: I can have form submissions sent to a URL, e.g. a PHP script, and happily parse away.
Question
Is it possible to name (or tag) FormStack fields with simple identifiers?
The client needs to be able to customize the form such that multiple fields may feed into the same database column.* FormStack however, as far as I can tell, provides only a way to specify a field label, e.g. Which of these trips interest you?, not a programmer-friendly identifier, e.g. Trip. My script would have to string-compare labels (which, due to their length, are more prone to typos) to determine what to do. What are some sensible workarounds to this problem?
Clarifications*
The reason there can exist multiple fields that feed into the same database column, is that the client uses conditional fields. For example, one field might ask, Where are you studying abroad? If the user selects "Europe", a conditional field might appear, asking Which of these trips interest you?, with choices pertaining to Europe. If the user selects "Africa" however, a similar field might appear, e.g. Which of these trips interest you?, but with choices pertaining to Africa. In FormStack, these are actually two distinct fields. However, as you can imagine, the values belong in the same database column, Trip.
I have settled on a hack for now. FormStack allows HTML markup in labels, e.g. Which of these trips interest you? <!--Trip-->. The client is willing to "tag" fields in this way.
Here's a snippet of the code that parses such tags, in case it might help someone else:
require_once 'Formstack.php';
$formstack = new Formstack($apiKey);
$form = $formstack->form($_POST['FormID']);
$taggedFields = array();
foreach ($form['fields'] as $field)
{
if (preg_match('/<!--\s*([0-9A-Za-z]+)\s*-->/',
$field['label'],
$matches))
{
$taggedFields[$matches[1]] = $_POST[$field['id']];
}
}
In fact, I've had to make it a little bit more sophisticated. Some FormStack field-types serialize input (in a horrific way). For example, FormStack's Name field-type takes multiple fields (prefix, first, middle, last, initial, suffix), and concatenates the results into a string:
'first = Andrew
initial = W
last = Cheong'
To handle this, I've written my code to handle such syntax in labels as Tell us your name! <!--FirstName=first--> <!--LastName=last--> <!--MiddleInitial=initial-->
The code follows.
require_once 'Formstack.php';
$formstack = new Formstack($apiKey);
$form = $formstack->form($_POST['FormID']);
$taggedFields = array();
foreach ($form['fields'] as $field)
{
if (preg_match_all('/<!--\s*([0-9A-Za-z]+)\s*(?:=\s*(\w+))?-->/',
$field['label'],
$matches,
PREG_SET_ORDER))
{
foreach ($matches as $captures)
{
if (count($captures) == 3 &&
preg_match('/(?:^|\n|\r)'.$captures[2].' = ([^\n\r]+)/',
$_POST[$field['id']],
$subcaptures))
{
$taggedFields[$captures[1]] = $subcaptures[1];
}
else
{
$taggedFields[$captures[1]] = $_POST[$field['id']];
}
}
}
}
Hopefully, FormStack will soon add a native way to name or tag fields!

Apostrophe CMS: Engine Creation

I'm attempting to create a Product Engine for Apostrophe. I'm having trouble extending the Page Settings form, at the moment I want to add a simple textarea to add a synopsis to the page - eventually I want to add Product settings but I need to get the basics working first.
I've created a form and a settings partial, it's displaying fine and saving the data (with the help of a little hack - might not be correct). The trouble I'm having is when you edit a page the data is not being pulled back in to the form. To be honest I probably doing something fundamentally wrong but I lack experience in Symfony.
My table schema
ccProduct:
tableName: cc_product
actAs:
Timestampable: ~
columns:
page_id:
type: integer
notnull: true
synopsis:
type: text
relations:
Page:
class: aPage
local: page_id
foreign: id
type: one
onDelete: CASCADE
My form ccProductEngineForm.class.php
class ccProductEngineForm extends ccProductForm
{
public function __construct($object = null, $options = array(), $CSRFSecret = null)
{
// when editing the page the values are not show, this is an attempt to get it to work - it still doesn't :(
$page_id = sfContext::getInstance()->getRequest()->getParameter('id');
sfContext::getInstance()->getRequest()->setParameter('page_id', $page_id);
$ccProduct = Doctrine::getTable('ccProduct')->findOneByPageId($page_id);
if ($ccProduct) {
sfContext::getInstance()->getRequest()->setParameter('id', $ccProduct->getId());
}
// aPageForm object is passed in
parent::__construct(null, $options, $CSRFSecret); // construct normally
//$this->mergeForm(new aPageForm($object, $options, $CSRFSecret)); // merge the aPageForm - Nope, ignore it!?
}
public function setup() {
parent::setup();
$this->useFields(array('synopsis'));
$this->widgetSchema->setNameFormat('enginesettings[%s]');
$this->widgetSchema->setFormFormatterName('aPageSettings');
}
protected function doSave($con = null)
{
// page_id is missing! possible bug? BaseaActions.class.php ~ 520
$this->values['page_id'] = sfContext::getInstance()->getRequest()->getParameter('enginesettings[pageid]');
parent::doSave($con);
}
}
Thanks in advance for any help
EDIT:
Thanks for your answer Tom, I'll try to add a little more detail.
I was aware that a page object is passed into the Engine, but I wasn't exactly sure what do to with it - see my confused line of code:
//$this->mergeForm(new aPageForm($object, $options, $CSRFSecret)); // merge the aPageForm - Nope, ignore it!?
To clarify my 'product' is a page that uses the ccProduct engine. I now want to add extra information to that page. Does that make sense? In your words..
Are you trying to actually create a unique product that has its sole "home" on a product engine page? That's what subclassing ccProductForm would do
Yes :)
EDIT 2:
Following Tom's first suggestion (Apostrophe CMS: Engine Creation) I was able to extend the aPage table with my extra fields and the Engine is now saving these.
However, the standard aPageTable::getPagesInfo function isn't returning the fields I saved. I assume I'll have to select these separately?
EDIT 3:
aPageTable::retrieveBySlug() will do the job :)
REVISITED
I decided to revisit this and try Tom's second approach..
The other approach (if for whatever reason you don't want extra columns in aPage) is to keep your ccProduct table and fetch the relevant one
I managed to get this working, my ccProductEngine form constructor now looks like this..
class ccProductEngineForm extends ccProductForm
{
public function __construct($aPage = null, $options = array(), $CSRFSecret = null)
{
$page_id = $aPage->getId();
if ($page_id) {
$product = Doctrine_Core::getTable('ccProduct')->findOneByPage_id($page_id);
if ($product) {
$ccProduct = $product;
} else {
$ccProduct = new ccProduct();
}
}
parent::__construct($ccProduct, $options, $CSRFSecret);
}
I hope this helps someone :)
The main thing to remember is that your engine settings form receives a page object as the first parameter to the constructor, and you need to associate whatever your data is with that page object. Usually the engine settings form is a subclass of aPageForm, but it does not have to be. All that is required is that you associate your product object(s) with the page object in some way. Depending on your goals you probably want a refClass that creates a one-to-many relationship between product engine pages and products, and a form for manipulating those relationships.
From your code it is difficult for me to guess what you really want to do. Are you trying to actually create a unique product that has its sole "home" on a product engine page? That's what subclassing ccProductForm would do. Or do you just want to select an existing product from the product table and associate it with each engine page? Or do you want to select one or more products and associate them with the engine page?
Stuffing things into the request object is definitely not the way to go (:
Please clarify and I can help you further.
Tom Boutell, Apostrophe senior developer
There are two approaches you could follow here.
One is to just extend the schema of aPage in your project level config/doctrine/schema.yml file:
aPage:
columns:
synopsis:
type: text
Now every aPage object has a synopsis column, which will be null by default, and your engine settings form can just manipulate that one column. Your engine form subclasses aPageForm. You don't need a constructor at all (the default one will suit you), and your configure() method is just:
$this->useFields(array('synopsis'));
Boom, you have a textarea for the synopsis that appears when the page type is set to this engine. You don't need a ccProduct table at all.
The other approach (if for whatever reason you don't want extra columns in aPage) is to keep your ccProduct table and fetch the relevant one. Your engine form class then does not subclass aPageForm, and your constructor has to use the page passed to it to fetch the related ccProduct object (using a Doctrine relation) or create a new one if there is none yet. This is not difficult, but so far it looks like you can keep it even simpler by just adding a column to aPage.

Resources