Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at cf.Objective() 2009 (Minneapolis, MN) with:

Update: jQuery Photo Tagger Plugin For Flickr-Style Photo Tagging

By Ben Nadel on

This morning, I worked on updating my jQuery Flickr-Style Photo Tagger plugin to get rid of some of the limitations with the initial release. I'm still kind of feeling my way through the plugin "best practices", so bear with me.

 
 
 
 
 
 
 
 
 
 

Today, I made the following changes (the latest code can be downloaded from the project page):

  • Hover now works in Internet Explorer. I had to add a transparent GIF background image to the anchor tags to get this to work; hopefully, I'll find a non-linked-file way to deal with this in the future.
  • I removed the use of the CTRL key. Now, to create new tags, you simple click and drag; the caveat here is that "tag creation" has to be turned on (another new feature) in order for this to work.
  • When you create a new tag, you cannot start the tag over an existing tag. This was more about technical issues than anything else - it made the double-click-to-delete and the click-to-drawing easier to program along side one another.
  • Tag creation can be turned on/off by default as well as set manually.
  • Tag deletion can be turned on/off by default as well as set manually.

To see some of these new features, I have updated the demo code:

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Flickr-Style Photo Tagging With jQuery And ColdFusion</title>
  • <style type="text/css">
  •  
  • div.photo-column {
  • float: left ;
  • margin-right: 10px ;
  • }
  •  
  • div.photo-container {
  • border: 1px solid #333333 ;
  • margin-bottom: 13px ;
  • }
  •  
  • </style>
  • <script type="text/javascript" src="./jquery-1.4.1.js"></script>
  • <script type="text/javascript" src="./coldfusion.json.js"></script>
  • <script type="text/javascript" src="./phototagger.jquery.js"></script>
  • <script type="text/javascript">
  •  
  • // When the DOM is ready, initialize the scripts.
  • jQuery(function( $ ){
  •  
  • // Set up the photo tagger.
  • $( "div.photo-container" ).photoTagger({
  •  
  • // The API urls.
  • loadURL: "./load_tags.cfm",
  • saveURL: "./save_Tag.cfm",
  • deleteURL: "./delete_tag.cfm",
  •  
  • // Default to turned on.
  • // isTagCreationEnabled: false,
  •  
  • // This will allow us to clean the response from
  • // a ColdFusion server (it will convert the
  • // uppercase keys to lowercase keys expected by
  • // the photoTagger plugin.
  • cleanAJAXResponse: cleanColdFusionJSONResponse
  • });
  •  
  •  
  • // Hook up the enable create links.
  • $( "a.enable-create" ).click(
  • function( event ){
  • // Prevent relocation.
  • event.preventDefault();
  •  
  • // Get the container and enable the tag
  • // creation on it.
  • $( this ).prevAll( "div.photo-container" )
  • .photoTagger( "enableTagCreation" )
  • ;
  • }
  • );
  •  
  •  
  • // Hook up the disabled create links.
  • $( "a.disable-create" ).click(
  • function( event ){
  • // Prevent relocation.
  • event.preventDefault();
  •  
  • // Get the container and enable the tag
  • // creation on it.
  • $( this ).prevAll( "div.photo-container" )
  • .photoTagger( "disableTagCreation" )
  • ;
  • }
  • );
  •  
  •  
  • // Hook up the enable delete links.
  • $( "a.enable-delete" ).click(
  • function( event ){
  • // Prevent relocation.
  • event.preventDefault();
  •  
  • // Get the container and enable the tag
  • // deletion on it.
  • $( this ).prevAll( "div.photo-container" )
  • .photoTagger( "enableTagDeletion" )
  • ;
  • }
  • );
  •  
  •  
  • // Hook up the disabled delete links.
  • $( "a.disable-delete" ).click(
  • function( event ){
  • // Prevent relocation.
  • event.preventDefault();
  •  
  • // Get the container and disabled the tag
  • // deletion on it.
  • $( this ).prevAll( "div.photo-container" )
  • .photoTagger( "disableTagDeletion" )
  • ;
  • }
  • );
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Flickr-Style Photo Tagging With jQuery And ColdFusion
  • </h1>
  •  
  •  
  • <div class="photo-column">
  •  
  • <div class="photo-container">
  • <img
  • id="photo3"
  • src="./sexy3.jpg"
  • width="520"
  • height="347"
  • alt="Sexy woman used for demo."
  • />
  • </div>
  •  
  • <!-- These will toggle the tag ceation. -->
  • <a href="#" class="enable-create">Enable Create</a>
  • &nbsp;|&nbsp;
  • <a href="#" class="disable-create">Disable Create</a>
  •  
  • <br />
  • <br />
  •  
  • <!-- These will toggle the tag deletiong. -->
  • <a href="#" class="enable-delete">Enable Delete</a>
  • &nbsp;|&nbsp;
  • <a href="#" class="disable-delete">Disable Delete</a>
  •  
  • </div>
  •  
  •  
  • <div class="photo-column">
  •  
  • <div class="photo-container">
  • <img
  • id="photo2"
  • src="./sexy2.jpg"
  • width="520"
  • height="347"
  • alt="Sexy woman used for demo."
  • />
  • </div>
  •  
  • <!-- These will toggle the tag ceation. -->
  • <a href="#" class="enable-create">Enable Create</a>
  • &nbsp;|&nbsp;
  • <a href="#" class="disable-create">Disable Create</a>
  •  
  • <br />
  • <br />
  •  
  • <!-- These will toggle the tag deletiong. -->
  • <a href="#" class="enable-delete">Enable Delete</a>
  • &nbsp;|&nbsp;
  • <a href="#" class="disable-delete">Disable Delete</a>
  •  
  • </div>
  •  
  • </body>
  • </html>

As you can see, I tried to follow a sort of jQuery-UI convention where you can use the plugin method to both apply a new plugin as well as invoke methods on an existing plugin. So, for example, if you call the plugin method with an options hash:

.photoTagger( {} );

... it applies the plugin. But, if you call it with a method name:

.photoTagger( "enableTagCreation" );

... it invokes the given method - enableTagCreation - on the photoTagger instances associated with the elements in the given collection.

If you want to try this out for yourself, look at the online demo.




Reader Comments

hi,
thats a real nice feature

im interested for testing it but
im more designer than developer and i only know php ...

Is there a way to make the same functionnality with php, and mysql ?

i need to change The API urls with the path to my php file, right ?

and what i dont know how to replace cleanAJAXResponse: cleanColdFusionJSONResponse
for an apache server...

Thanks for sharing

Reply to this Comment

@Keusta,

You should be able to set this up in PHP, although you'll have to write the PHP scripts. As far as the function, cleanAJAXResponse(), you won't need it. It is only required for ColdFusion because it is not case sensitive.

Reply to this Comment

Hi Ben,

Its a really cool photo tagging plugin. Since i was implementing this is php I was wondering how to store the data and retrieve it back.

Thanks for sharing this wonderful work.

Reply to this Comment

@Ashish,

I don't have it in front of me, but for this demo, I was probably caching the tagging info in the Application scope (a persistent scope in ColdFusion). Ultimately, though, this is built to be server agnostic. As long as your PHP code can return the JSON, it should be good.

Reply to this Comment

Hi, Ben,

I tried to port your design into PHP implementation. It works in my free hosting server.

However, I found my modification has issues in IE and Opera, while yours are fully compatible with most browsers. My Dragonfly is disabled due to some security issues. While Opera error console through out an CSS warning for jQuery zoom:1.

Opera will alert me for "problem in API", just as it can not decode the JSON correctly.

The error console from IE8 told me following:

Message: 'length' is null or not an object
Line: 29
Char: 312
Code: 0
URI: http://ajax.googleapis.com/ajax/libs/jquery/1.4.1/jquery.min.js

I have no ideas for all of these. I have tried the jquery-1.4.1.js from your site. The error is same.

So I return to check what is happening in JSON communications between jQuery and backend. I have tried to change the MIME to "application/x-json". It doesn't help. It is interesting that your demo's JSON XHR content-body is hidden from the Firebug tools. I don't know why.

If you happen to see the comment. Would you give me some hints to debug?

My testing implementation is written in PHP functions. The load_tags will read a CSV and encode into JSON. The save_tag will write parameters into CSV file. I wonder if there is anything I should take care of.

My demo locates at:

http://allankliu.66ghz.com/prototype/labs/jquery-photo-tagger/

My source code at:

http://allankliu.66ghz.com/prototype/labs/jquery-photo-tagger/jquery-photo-tagger.zip

Any help is appreciated.

Yours sincerely

Allan K Liu

Reply to this Comment

After upgrading my Safari to v5(7533.16), the alert window can not be entered for message. Maybe it is browser bug?

Reply to this Comment

Hello, I am coming back. I found that my problem is terrible format in the JSON data with unnecessary comma and missing quote. Now, IE/FF/Opera works, although Safari 5 still works abnormally.

Sorry to bother you. Again, great work for this plug-in.

Reply to this Comment

@Allankliu,

Sounds like you got it mostly working with the JSON formatting. I am not sure why it would be working abnormally in Safari. Sorry.

Reply to this Comment

Hello Ben,
Thank you for sharing such a nice feature.
I have a small problem and i need ure help.
I have a servlet (java) which populates an image and i wish to tag that image, and when i tag the image i wish to store it in the database. i need ure help in understanding the JASON part. Is there a way to solve my problem.

Reply to this Comment

Hello Ben,
first, thanks for your nice work.
Before I port your code in PHP, I wanna ask for it because someone already does this work.
Does someone have the PHP-Code from allankliu? Both of his links are dead.

Greetings from germany
Elodrin

Reply to this Comment

hiya, as a user i'm looking for this functionality on the web, eg in flickr or some other image database, have you or someone done a plug-in which can be used? If so where can i find it? Maybe data could be stored/retrieved from google spreadsheets (columns: image url, x,y,w,h, tag, hyperlink).

Thanks a lot!

Reply to this Comment

I'm working on trying to adapt this plugin for some grading software.
<P>
The idea that student essays would be converted from PDF to JPG and then the faculty would leave comments on the image of the document.
<P>
The section of the plugin where the text of the tag is entered is <p>
<pre>
// Now that the user has drawn the tag, let's
// prompt them for the message to be associated.

var message = prompt( "Message:", "" );

// Check to see if the message was returned (if
// the user cancelled out, then we are going to
// cancel the tag creation).

if (message){

// Create a tag based on our pending tag. We
// know everything BUT the ID at this point.
var tag = this.addTag(
"",
this.pendingTag.position().left,
this.pendingTag.position().top,
this.pendingTag.width(),
this.pendingTag.height(),
message
);

// Save this tag (to the server).
this.saveTag( tag );

}
</pre>

The "prompt" function stops script processing and waits for the tag text to be entered. While there's nothing that prevents someone from entering paragraph-long text in a prompt box, I wanted a larger box. So, I'm trying to use the "Impromptu" plugin as a replacement. <P>
<P>
<pre>
// Now that the user has drawn the tag, let's
// prompt them for the message to be associated.

var txt = 'Please enter your comment:<br /><textarea name="newComment" id="newComment" wrap="virtual" cols="45" rows="5">Your Comments</textarea>';

var message = "";
var buttonclicked = "";

$.prompt(txt,{
callback: function (e,v,m,f){
if (v != undefined){
if (v == 'Submitted')
{buttonclicked = "s";
message = f.newComment;}
else {
buttonclicked = "c";
}}
},
buttons: { Submit: 'Submitted', Cancel: 'Cancelled' }
});

function waiting() {
if (buttonclicked == "s" ) {
clearInterval(dialogdelay);

var tag = this.addTag(
"",
this.pendingTag.position().left,
this.pendingTag.position().top,
this.pendingTag.width(),
this.pendingTag.height(),
message
);
// Save this tag (to the server).
this.saveTag( tag );
}}

dialogdelay = setInterval(function(){waiting()},1000);

</pre>

<P>
The problem I'm running into is in an effort to get the script to "pause" to allow the text to be entered, I think I'm getting lost with respect to the scope of the "addTag" function. <P> I keep getting "addTag is not a function" in Firebug.
<P>I've tried various alternatives to "this.addTag", but just can't seem to be able to figure out the correct syntax.
<P>
Any help would be appreciated.

Reply to this Comment

Just seen flikr now have an add note option allowing this to be done. early stages but it does the job, and URLs work.

Reply to this Comment

Post A Comment

?
You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.