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

Posted February 5, 2010 at 10:26 AM by Ben Nadel

Tags: ColdFusion, Javascript / DHTML

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

Feb 5, 2010 at 10:44 AM // reply »
15 Comments

Good work Ben. I have a project coming up and this might be fun to work into it.


Feb 5, 2010 at 11:33 AM // reply »
11,246 Comments

@Robert,

Awesome - when you do, I'd love to hear what you did with it.


Feb 11, 2010 at 12:45 PM // reply »
1 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


Feb 11, 2010 at 10:29 PM // reply »
11,246 Comments

@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.


Apr 21, 2010 at 10:07 AM // reply »
1 Comments

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.


Apr 21, 2010 at 10:50 AM // reply »
11,246 Comments

@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.


Jun 25, 2010 at 7:33 AM // reply »
4 Comments

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


Jun 25, 2010 at 6:37 PM // reply »
4 Comments

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


Jun 25, 2010 at 8:02 PM // reply »
4 Comments

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.


Jun 29, 2010 at 10:12 AM // reply »
11,246 Comments

@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.


Sep 30, 2010 at 11:32 AM // reply »
2 Comments

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.


Oct 30, 2011 at 11:30 AM // reply »
1 Comments

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


Oct 12, 2012 at 2:15 PM // reply »
2 Comments

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!


Oct 22, 2012 at 3:47 PM // reply »
1 Comments

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.


Dec 2, 2012 at 7:52 PM // reply »
2 Comments

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



Post A Comment

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.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 25, 2013 at 10:08 AM
Using "//" And ".//" Expressions In XPath XML Search Directives In ColdFusion
@Ben, my question is that i want the current node with its tag and its parent node. i just want only that data. So, give me the solution for that. and remember solution is working on " xpath 1.0 ... read »
May 25, 2013 at 10:01 AM
Using "//" And ".//" Expressions In XPath XML Search Directives In ColdFusion
hey ben, i want get my current node tag and also want the root node tag withing. So, how can i fix it.. ! ... read »
May 24, 2013 at 5:39 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
@Adam Oops! My mistake! I hadn't gotten that far in my testing - I'm still baby stepping my way through the process. ... read »
May 24, 2013 at 5:13 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
Hi Jason, Thanks for checking up on that, but I still stand firm on my position. :) There are actually two listLast()'s in use, and you're right that the one using a space as a delimiter is fine. ... read »
May 24, 2013 at 4:45 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
@Ben I have been lurking your site for quite some time, and haven't stepped up to comment until today. Thanks for all the great info - keep it up! @Adam I believe you are mistaken... as the commen ... read »
May 24, 2013 at 11:21 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@WebManWalking, Ha ha, let's us never speak of justifying "##" notation again :P ... read »
May 24, 2013 at 11:18 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben, Ah, so it was indeed how I vaguely remembered it to be: A direct assignment value = users.id[ i ] causes value to retain the sticky datatype of the query column. Although unnecessary in ... read »
May 24, 2013 at 9:11 AM
Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
@Brandon, Hi, No, I haven't been able to do that. I have just kept it as it is. ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools