Extending Mahou; Gotchas and Snippets
Spent a bit of time yesterday working on sprucing up mahou.appspot.com. Previously it was only performing image searches, but I decided to kick it up a couple of notches and take advantage of more of Yahoo! BOSS Search Mashup Framework's latent functionality.
The most obvious change is that it now supports web, image and news results. Presentationally, the image search remains much more compelling than the other two, but I was able to refactor the code aggressively enought to facilitate further customization (WLWebResultsView.j
--which handles displaying the web search results--is about 100 lines without whitespace, WLImageResultsView.j
--displays images--is about 65).
I added a couple of menus, the Search
menu allows you to chose the number of results per query, and Web
has an option to filter via Delicious
. When you enable filter via Delicious
then Web search results will be filtered throught the Delicious popular RSS feed for that topic.
In effect, the filtering stiffles results rather badly, but it does provide high quality results. It's mostly an experiment at this stage, which actually describe the entirety of Mahou: Mahou is my small and misguided attempt to examine and experiment with search engines interface and functionality1. From now on it should be a thirty minute vacation to trial a new idea, so perhaps some interesting ideas will evolve in this meager garden.
Now I'll discuss a few of the awkward points in development, as well as examine a few interesting spots in the code.
Displaying Text Well is Hard
Cappuccino remains awkward when attempting to display text. In all fairness, Cocoa is poor in this regard as well. I think there is the potential for Cappuccino to outshine Cocoa in that regard, especially if they bypassed the horrific NSAttributedString approach to formating and simply respected the meaning of formatting html tags (in particular, em and strong).
Moving in that direction I extended the textfield to strip out html tags, with the WLHTMLTextField.j
class. In the future I hope to extend it to allow rendering as well, but for the time being it only allows ignoring or stripping HTML.
import <Foundation/CPObject.j>
import <AppKit/CPTextField.j>
WLStripHTML = 0,
WLIgnoreHTML = 1,
WLRepresentHTML = 2;
@implementation WLHTMLTextField : CPTextField
{
int _htmlMode;
}
-(void)setHTMLMode: (int)anInt {
_htmlMode = anInt;
}
-(int)htmlMode {
if (!_htmlMode) {
_htmlMode = WLStripHTML;
}
return _htmlMode;
}
-(void)setStringValue: (CPString)aString {
var mode = [self htmlMode];
if (mode == WLStripHTML) {
aString = aString.replace(/<</span>S[^>]*>/g,'');
}
[super setStringValue:aString];
}
Usage is simple.
// top of file
import "WLHTMLTextField.j"
// somewhere in the midst of your code
var field = [[WLHTMLTextField alloc] initAtFrame:CGRectMakeZero()];
// is in WLStripHTML mode by default, so this is optional.
[field setHTMLMode:WLStripHTML];
[field setString:@"<strong>Blah blah blah</strong><em>Yep.</em>"];
[field sizeToFit];
This was necessary because Yahoo! BOSS is returning its content with strong and emphasis tags (to display where the string matched the search query, among other things), but Cappuccino doesn't know how to handle the tags. Thus the poor man's solution is to strip out the tags.
I am a poor man.
Collection Views Gotchas
CPCollectionView
is a subclass of CPView
which facilitates displaying a number of smaller views, arranged in columns and rows to take maximum advantage of available space. It provides a great amount of functonality for cheap, and the Flickr demo code is an excellent tutor on the topic.
That said, the situation is imperfect. In particular there are two awkward scenarios I ran into:
CPCollectionView
s are populated byCPCollectionViewItem
s, which contain a view representing the object that the view item represents. For example, Mahou recieves an array of dictionaries containing search results. I pass the array to a collection view, and then the collection view passes one dictionary to each of the view items.This works very well. However, its quite awkward to determine the amount of space available for a view item's view. I'm having trouble explaining myself on this point, mostly because I suspect I am making a large mistake and missing an obvious solution.
However, to the best of my efforts I was unable of determining the collectio view's size at the time of creating a collection view item. This makes relatively positioning items awkward. Instead you have to simply define a small range of min/max size for the collection view items, and then you can absolutely position contents within the collection view item.
Well satori comes a day too late, but reexamining the docs, I could have solved this issue with judicious usage of the
setMaxNumberOfColumns
method.I ran into an extremely odd caching error. If I searched for images for
Japan
and then searched for images forAmerica
then it would properly clear out the first searches images. However, for the web search if I did the same it wouldn't display theAmerica
searches properly.The code that handles releasing and resetting the represented objects is all handled by those classes' superclass, so I was at a complete loss why the web view wasn't properly functioning.
The gotcha here is that
CPCollectionView
reusesCPCollectionViewItem
s, so that your subclass ofCPCollectionViewItem
has to be able to update its contents properly when thesetRepresentedObject:
method is called.
Python Backend
Cappuccino handles almost all the functionality, so your backend becomes a simple API. In this case, the Python code for Mahou is less than one hundred lines of code. For example, the entirety of the backend for handling image searching is this:
class ImageSearchHandler(webapp.RequestHandler):
def get(self):
query = console.strfix(self.request.get("query"))
count = int(console.strfix(self.request.get("count")))
offset = int(console.strfix(self.request.get("offset")))
data = ysearch.search(query,vertical="images",count=count,start=offset);
images = db.create(data=data)
serialized = simplejson.dumps(images.rows)
self.response.out.write(serialized)
Thats pretty minimal. The code for the web search is slightly more complex because it will filter by Delicious results if that option is enabled, but even so it's still just twelve lines (thanks to the flexibility of the Yahoo! BOSS Search Mashup Framework).
Ending Thoughts
My current thoughts on Cappuccino are still pretty warm, but as you leave the realm of trivial functionality you'll almost certainly run into buggy code. 280 North is a three man team, and a library of this size has a number of cracks for bugs to slip through. Now that the library is open sourced, you just have to be willing to read the source. If you want to use Cappuccino, make sure you're willing to read the source!
This seems to be a personal trend of mine to write personal verions of software. My blogging software, my twitter client, my Japanese dictionary, a teacher's syllabus and resource organizer, a note taking app, and now a search engine. There is probably something wrong with me.↩