Storing Ajax Searches with concete5


For the new interfaces I've been making, sorting and filtering on many different search fields via ajax is super easy.  But what do you do when you want to allow people to link to the search?  I have the feeling this will be part of a paid add-on soon so now code samples, but there is an overview an overview of what I'm doing.

It was kind of a slow day at work today.  Actually, I guess at home today - it was rainy and actually the first snow of the year fell today.  It was coming down in the morning so I called and asked if I could work from home instead of riding 45 minutes in 35 degree rain to get to work.  Thankfully my boss is actually a cyclist too, so he had no problem with it.

Still, there wasn't a lot of stuff to do today.  I had to figure out a problem with the captcha in concrete5 on one site.  The problem was that we first set up the site with the recaptcha library, but then they had us switch it back to securimage.  This was really easy to do, but there was a difference in the helper code between the two.  For recaptcha, you just need $captcha->display(), but for securimage you need $captcha->display() and $captcha->showInput() - so the input box wasn't there on the pages.  I fixed that, and then we found out that in Windows browsers the captcha didn't update when clicked on to get a new image.  That turned out to be a core bug - I reported it with a fix.  Then just now I thought about it, and decided to check the code again, clicking refreshes the image, but not the backend validation, so it fails no matter if you enter it right or not. So I'll have more work to do tomorrow on that.

After that I chatted on the IRC for a bit, and got a great idea on how to upgrade blocks that have multiple templates that are all fairly different.  Basically, on Add you show a dialog box with thumbnails from each template's folder that scale up to full images via zoom image or something, with radio buttons to choose which template to add.  Then a text field for Block Name.  Once you choose, show the rest of the add form.  So you could tailor your add/edit options based on template chosen.  Then put a "Change Template" button in your form to update it later.  Seems like it could be really powerful for making blocks.

The next thing I got working on was trying to save search results on the large project that we've been doing.  Right now it lets you store a bunch of search variables in arrays in the session, each time you click a checkbox in the sidebar it updates the filter and updates the page array.  But that doesn't actually change the page URL or anything, so there's no way to save the state that you are in.  It's a bit out of scope for the current project, but saving the search state is something that we mentioned as being potential addition to the interface if there was room in the budget.  I'm not sure if there is room or not, but I know that the next time I build one of these interfaces (and I have the feeling I'll be doing a lot more of them now) I'm going to have the same issue - so why not figure out how to save it?

I first off was looking at javascript solutions to serialize the form and submit it via get, then submit it.  So someone could link to the url and see the same search results.  The jQuery plugin I found was the jquery-bbq-plugin.  It seemed like it would work, but storing the form and decoding didn't seem like the best way to do it.  I started trying to think of other ways to do it, and came up with serializing the search arrays from the session, base64 encoding them, and storing them in a text field in the database.

This turned out to work really well.  I made up a little model that encoded / decoded whatever array you passed in as $data to the StoredSearch::add($data) method.  It has get by ID, so you can make a function on your search page controller called stored_searches and pass in the ID to retrieve the search.  If no search is supplied, it stores the session state by building an array out of the session data arrays and passing it to the StoredSearch model.  Then it redirects to the search with ID supplied, this time with a second parameter for if it's just been created or not.  If it's just been created, it sets a view variable "$is_first_load" before displaying the view.  All of the outputs of the stored_search also set a message and a status to display. If StoredSearch::getByID($id) returns a valid search object, the getSearchOptions() method is called on it.  That returns the search array from the session, you loop over it and replace the session keys with the new values from the object.  Super easy, and it should work to have the same StoredSearch model saving data for several different types of ajax search pages on the same site, each could use different objects in the session for it's variables, probably scoped by cID or something to make the stored_searches() method on the page controllers for each be the same on all page types.

If it's the first load, then it also gets a string for permalink to display, and changes the way that the search filters work.  Since the url is set to the search result, once you change the filter it needs to redirect back to the search page with no parameters.  So instead of loading the url via ajax, the url gets two more parameters, and is loaded with window.location.replace(url).  This is the same way that I was linking to search terms from the front page - they had to be set to reset the search when you click through, then erase the data for the filter out of the URL.  So I made a second parameter for $clear=false - if it's true then you clear the session, apply the new filter and redirect.  I made a third argument, $firstLoad = "false".  If that's true, then it also does the redirect, but doesn't clear out the session.  This means that the first time you change the filter, the window resets back to the base url for the page with your new options, and you can continue filtering without worrying about a page refresh resetting your search session by applying the saved search data from the url parmeters.

I also decided to store the cID and the number of views for each search in the database.  I might start storing more information than that - you could do search name, user id, etc, and allow people to store and share searches, email them, etc.  You could also expand it if you need more custom reporting allowing you to then make a StoredSearchList object that can filter out based on different options.  Instead of using a serialized array, you'd do a column for each session array you want to save, probably with the array stored as just imploded values.  Then you could make real easy like searches, and pull up reports on which pages are pulling up searches for which variables in different charts.  I made a method on the search that you can call like $search->recordView() that will increment each time it is retrieved, so you could see which links are getting shared the most.  An extension of that might be to find a way to check if a search with those parameters already exists and then retrieve that instead of creating a new search.  Then you could keep exact reports of which search combinations are most popular, not just which individual searches are popular and shared / linked / visited a lot.  I think knowing those combinations would be key.  Also for doing things like showing items that other people bought with that same search if you apply it to a product list in ecommerce or something.

I see a lot of future for this method of storing ajax search filters, there are at least a dozen or two different things I could think of just off the top of my head.  The more I think about how I want to set up these ajax filtered pages, the more I want to make a package for it.  Pretty sure I will sometime soon.

It was really nice to see how easy it was to make stored searches and retrieve them. When I was looking at that jquery plugin I was just hating the idea of doing those crazy, convoluted arrays and decoding them.  Using the StoredSearch model to process the session variables from a page controller method was pretty much perfect.  Instead of a super long URL and a lot of logic to process the search from get parameters and recreate it, it's just a function on the controller.  Call with no methods and save everything and generate the permalink.  Can it get any easier than that? I'm not sure, but I don't think there's really any better way to do this.  It seems so clean and functional.  The code is just so simple.  The stored search object is just 77 lines of code, and the function to save or retrieve the searches on the page type controller is just 47 lines of code.  I think that a bit of work and I could get that even smaller on the page controller side, but maybe bigger on the StoredSearch model side of things.

Pretty happy with how today went.  Didn't have a lot of billable hours, but I think that the code that I came up with will be really useful in the future.