Adds typeahead (similar to Flickr) and an admin page for deleting, renaming and merging tags to projects that already use sfPropelActAsTaggableBehaviorPlugin. Also adds support for featured tags and high-priority tags, and methods to retrieve tags ordered by popularity and priority.

Developers

License

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Changelog for release 0.3.3 - 18/08/2008

Other releases

Release 0.3.3 - 18/08/2008

Release 0.3.2 - 05/08/2008

sfTagtoolsPlugin

REQUIREMENTS: READ THIS FIRST

You MUST have the sfPropelActAsTaggablePlugin as well. This plugin
provides additional services to applications that utilize that plugin. There is
no point in using this plugin without it.

To use the admin page and typeahead features, you must also have Prototype (prototype.js)
loading in your pages.

Introduction

This plugin provides many additional services above and beyond those
standard in the excellent sfPropelActAsTaggablePlugin. This plugin
is less mature than sfPropelActAsTaggablePlugin but provides quite a bit
of user interface "glue" to flesh it out.

Contact

sfTagtoolsPlugin was created by the team at [http://www.punkave.com/ P'unk Avenue].
Please address questions, concerns and patches to [mailto:tom@punkave.com Tom Boutell].

Features

Typeahead for tags, with autocomplete on tab or mouseclick. Similar to Flickr's solution, which is familiar to many users.
See: lib/helper/tagaheadHelper.php

The concept of featured tags. These are just a handy way to mark certain
tags as "featured" for your own UI's purposes. If a tag has a
corresponding !FeatureTag object, you know it's a featured tag.
We provide this so that you can mark tags as "featured" in...

A nicely AJAXified admin page for renaming, merging, deleting,
prioritizing and featuring tags. Note that you don't have to
use !FeaturedTag to get this admin page, that's an optional feature.

The concept of prioritized tags, implemented by the
!PriorityTag class. A tag with a higher priority appears first in results
returned by sfTagtoolsToolkit::getBeginningWith(). Again, this is
an optional feature, you can use the typeahead and the admin page
without !PriorityTag. "But what is sfTagtoolsToolkit::getBeginningWith()?" you ask. This leads
us to:

A way to retrieve tags ranked in order by popularity, optionally
requiring that they begin with a particular string. See
sfTagtoolsToolkit::getBeginningWith(). This method began life as
part of the typeahead mechanism but with a blank initial string
it's quite useful for creating tag browsers, too.

A way to fetch objects of a particular taggable model class
which are related to a particular set of tags, ranking them in
descending order by the number of related tags, with high priority
tags receiving an additional boost.

Philosophy

sfPropelActAsTaggablePlugin works its magic by storing the name of the
tagged object class as a string in the database (taggable_model). This
is great in a lot of ways but it causes problems when we want to
rank things meaningfully so that stale taggings pointing to dead objects
are not considered. This can be worked around to an extent using
SQL's provision for an "IN" clause (exposed by the retrieveByPKs
method of peer classes in Propel), and Xavier Lacot's code makes
heroic use of this feature. However this tends to fall apart if you want
to, let's say, quickly count the tagged objects of a particular model
that still exist without the overhead of hydrating them all (for
instance, to build a tag admin page). It also falls apart if you have
hundreds of thousands of records, creating IN clauses that are too large for
a MySQL packet (and inefficient in any case). It's true that an id is a whole lot smaller than an actual object, but when you have enough of them it's still bad.

So if you want to count the tagged objects of a particular model that still
exist and meet a particular criterion of "live-ness" or "published-ness"
without hydrating them all... welcome to custom SQL query land!
Happily I have coded those queries for you.

So although it can be used without, sfTagtoolsPlugin works best when you
describe your taggable classes to it via app.yml. This allows
sfTagtoolsPlugin to work around this design limitation of
sfPropelActAsTaggablePlugin by joining with the tables that contain
the taggable classes when making custom queries to efficiently
fetch just the information needed.

"Isn't that still slow?" There is a small performance hit relative to
selecting against the tags and taggings alone, but it's
not significant in "big-O notation" terms (at least, not if MySQL does
its job and optimizes things properly). Still, if you have a
zillion taggable classes, you might want to go with a single
taggable class instead and reference it from tables for each of the
"real" objects. Sigh... wouldn't it be nice if Propel truly
understood multiple inheritance?

Installation

Just install the plugin. Okay, it's not quite that simple.

You'll need to enable the tagtools module in your application. In my
apps/frontend/config/settings.yml, I have:

Your site may use other JavaScript libraries which can be loaded in a comma-separated list.

Finally, to make the typeahead ("tagahead") support work properly, you must have at least a few bare-bones CSS settings to correctly display the suggested tags. See sfTagtoolsPlugin/web/css/suggested.css for details.

That's it for essential configuration. But to take full advantage, you should also describe your taggable classes
in app.yml. And you may wish to
enable the optional !FeaturedTag and !PriorityTag support although this plugin works quite well without those.
Here's an example:

"What are criteria_live and criteria_public?" These criteria are
applied when deciding if the object a tagging refers to is
good enough to qualify as a "live" object. If your application, like
many of ours, involves objects that users abandon halfway through the
creation process, then you know exactly what I'm talking about.
criteria_public is similar, but is applied only if the user
is not logged in (isAuthenticated fails on the current sfUser).
The word TAGGABLE is automatically replaced with the name of the
table for the appropriate object class when the query is run.

"What are label and cssclass?" These are used in the admin page, so
they are optional if you don't use the admin page. You do need
the name field, which must be the actual PHP class name
(not table name) for that type of object.

"Can I use typeahead and the admin page without any of these
settings?" Yes, but sfTagtools won't be able to distinguish
stale taggings pointing to long-deleted objects from taggings
that point to live objects. So I strongly recommend that you
at least define your taggables:

Otherwise you'll see typeahead suggestions for tags that are
no longer actually in use anywhere, perhaps for a good reason.
And you'll also see an inflated count of total occurrences for
each tag in the tagadmin module, because taggings are never
deleted and sfTagtools doesn't have enough information to join
with the taggables and count only the taggings that still matter.

Don't forget to "symfony cc"!

Implementing Typeahead

To implement the typeahead feature, use code like the following in your templates:

The tagahead_input_tag helper outputs an input tag with the specified name (which will also be its ID). The tagahead_observer helper outputs JavaScript code to dynamically update the tag suggestions based on what the user has typed so far.

Implementing Tag Admin

The tag admin page was activated when you enabled the tags module. It was also secured with a rule in sfTagtoolsPlugin/modules/tagtools/config/security.yml. However this rule simply prevents users from accessing the admin page if they are not authenticated. If your site has multiple levels of user privileges, you will want to adjust these settings so that only admins can delete, rename and merge tags!

To access the tag admin page, just visit:

!http://yoursite/tagtools/admin

Again, you must be authenticated first if you are using our provided security.yml rule.

version 0.33 - 2008-08-05

getBeginningWith: when joining with multiple taggables we do separate queries
consecutively and merge the results in order to avoid serious performance
issues when the database fails to spot potential optimizations and actually
examines a * b * c * d * e rows, where a * b * c * d * e tends to be a
rather large number.

An empty taggables array is now treated the same as no taggables setting
at all.

Further markdown corrections for the wiki.

version 0.32 - 2008-08-05

Another try at markdown format for documentation compatibility
with the wiki.

version 0.31 - 2008-08-05

README code examples now in markdown format for compatibility
with the new Symfony plugins wiki.

version 0.3 - 2008-07-31

version 0.2 - 2008-07-30

The model classes were missing! To make matters worse, config/schema.yml
didn't have a package attribute, so they were rebuilt in the main app
folder. And in any case my custom methods were missing. Fixed. Thanks to
Xavier Lacot for pointing out the problem.

version 0.1 - 2008-07-28

Initial public release. Thanks to Xavier Lacot for
wiki:sfPropelActAsTaggableBehaviorPlugin, which makes this plugin possible.