source: main/trunk/model-cols-dev/pei-jones/collect/photos/script/image-annotator/js/jquery.annotate.js@ 27948

Last change on this file since 27948 was 27948, checked in by davidb, 11 years ago

First cut at a collection specificially designed to annotate the photos of the Pei Jones collection

File size: 16.5 KB
Line 
1/// <reference path="jquery-1.2.6-vsdoc.js" />
2(function($) {
3
4 $.fn.annotateImage = function(options) {
5 /// <summary>
6 /// Creates annotations on the given image.
7 /// Images are loaded from the "getUrl" propety passed into the options.
8 /// </summary>
9 var opts = $.extend({}, $.fn.annotateImage.defaults, options);
10 var image = this;
11
12 this.image = this;
13 this.mode = 'view';
14
15 // Assign defaults
16 this.getUrl = opts.getUrl;
17 this.saveUrl = opts.saveUrl;
18 this.deleteUrl = opts.deleteUrl;
19 this.editable = opts.editable;
20 this.useAjax = opts.useAjax;
21 this.notes = opts.notes;
22
23 console.log("getUrl = " + this.getUrl);
24 console.log("saveUrl = " + this.saveUrl);
25 console.log("deleteUrl = " + this.deleteUrl);
26 console.log("notes = " + this.notes);
27
28 // Add the canvas
29 this.canvas = $('<div class="image-annotate-canvas"><div class="image-annotate-view"></div><div class="image-annotate-edit"><div class="image-annotate-edit-area"></div></div></div>');
30 this.canvas.children('.image-annotate-edit').hide();
31 this.canvas.children('.image-annotate-view').hide();
32 this.image.after(this.canvas);
33
34 // Give the canvas and the container their size and background
35 this.canvas.height(this.height());
36 this.canvas.width(this.width());
37 this.canvas.css('background-image', 'url("' + this.attr('src') + '")');
38 this.canvas.children('.image-annotate-view, .image-annotate-edit').height(this.height());
39 this.canvas.children('.image-annotate-view, .image-annotate-edit').width(this.width());
40
41 // Add the behavior: hide/show the notes when hovering the picture
42 this.canvas.hover(function() {
43 if ($(this).children('.image-annotate-edit').css('display') == 'none') {
44 $(this).children('.image-annotate-view').show();
45 }
46 }, function() {
47 $(this).children('.image-annotate-view').hide();
48 });
49
50 this.canvas.children('.image-annotate-view').hover(function() {
51 $(this).show();
52 }, function() {
53 $(this).hide();
54 });
55
56 // load the notes
57 if (this.useAjax) {
58 console.log("this.useAjax is true");
59 $.fn.annotateImage.ajaxLoad(this);
60 } else {
61 $.fn.annotateImage.load(this);
62 }
63
64 // Add the "Add a note" button
65 if (this.editable) {
66 /*this.button = $('<a class="image-annotate-add" id="image-annotate-add" href="#">Add Note</a>');*/
67 /*this.button = $('<a class="image-annotate=add" id="image-annotate-add">Add Note</a>');*/
68
69 this.button = $('<button type="button" id="image-annotate-add">Add Note</button>');
70
71 this.button.click(function() {
72 console.log("** Button clicked");
73 $.fn.annotateImage.add(image);
74 });
75 this.canvas.after(this.button);
76 }
77
78 // Hide the original
79 this.hide();
80
81 return this;
82 };
83
84 /**
85 * Plugin Defaults
86 **/
87 $.fn.annotateImage.defaults = {
88 getUrl: 'your-get.rails',
89 saveUrl: 'your-save.rails',
90 deleteUrl: 'your-delete.rails',
91 editable: true,
92 useAjax: true,
93 notes: new Array()
94 };
95
96 $.fn.annotateImage.clear = function(image) {
97 /// <summary>
98 /// Clears all existing annotations from the image.
99 /// </summary>
100 for (var i = 0; i < image.notes.length; i++) {
101 image.notes[image.notes[i]].destroy();
102 }
103 image.notes = new Array();
104 };
105
106 $.fn.annotateImage.ajaxLoad = function(image) {
107 /// <summary>
108 /// Loads the annotations from the "getUrl" property passed in on the
109 /// options object.
110 /// </summary>
111 $.getJSON(image.getUrl + '?ticks=' + $.fn.annotateImage.getTicks(), function(data) {
112 image.notes = data;
113 $.fn.annotateImage.load(image);
114 });
115 };
116
117 $.fn.annotateImage.load = function(image) {
118 /// <summary>
119 /// Loads the annotations from the notes property passed in on the
120 /// options object.
121 /// </summary>
122 for (var i = 0; i < image.notes.length; i++) {
123 image.notes[image.notes[i]] = new $.fn.annotateView(image, image.notes[i]);
124 }
125 };
126
127 $.fn.annotateImage.getTicks = function() {
128 /// <summary>
129 /// Gets a count of the ticks for the current date.
130 /// This is used to ensure that URLs are always unique and not cached by the browser.
131 /// </summary>
132 var now = new Date();
133 return now.getTime();
134 };
135
136 $.fn.annotateImage.add = function(image) {
137 /// <summary>
138 /// Adds a note to the image.
139 /// </summary>
140
141 console.log("image mode = " + image.mode);
142 if (image.mode == 'view') {
143 image.mode = 'edit';
144
145 // Create/prepare the editable note elements
146 var editable = new $.fn.annotateEdit(image);
147
148 $.fn.annotateImage.createSaveButton(editable, image);
149 $.fn.annotateImage.createCancelButton(editable, image);
150 }
151 };
152
153 /// <summary>
154 /// Creates an OK button on the editable note.
155 /// </summary>
156 $.fn.annotateImage.createSaveButton = function(editable, image, note) {
157
158 var ok = $('<a class="image-annotate-edit-ok">OK</a>');
159
160 ok.click(function() {
161 var form = $('#image-annotate-edit-form form');
162 var text = $('#image-annotate-text').val();
163 $.fn.annotateImage.appendPosition(form, editable)
164 image.mode = 'view';
165
166 // Save via AJAX
167 if (image.useAjax) {
168 $.ajax({
169 url: image.saveUrl,
170 data: form.serialize(),
171 error: function(e) { alert("An error occured saving that note.") },
172 success: function(data) {
173 if (data.annotation_id != undefined) {
174 editable.note.id = data.annotation_id;
175 }
176 },
177 dataType: "json"
178 });
179 }
180
181 // Add to canvas
182 if (note) {
183 note.resetPosition(editable, text);
184 } else {
185 editable.note.editable = true;
186 note = new $.fn.annotateView(image, editable.note);
187
188 note.resetPosition(editable, text);
189 image.notes.push(editable.note);
190 }
191
192 editable.destroy();
193 });
194 editable.form.append(ok);
195 };
196
197 $.fn.annotateImage.createCancelButton = function(editable, image) {
198 /// <summary>
199 /// Creates a Cancel button on the editable note.
200 /// </summary>
201 var cancel = $('<a class="image-annotate-edit-close">Cancel</a>');
202 cancel.click(function() {
203 editable.destroy();
204 image.mode = 'view';
205 });
206 editable.form.append(cancel);
207 };
208
209 $.fn.annotateImage.saveAsHtml = function(image, target) {
210 var element = $(target);
211 var html = "";
212 for (var i = 0; i < image.notes.length; i++) {
213 html += $.fn.annotateImage.createHiddenField("text_" + i, image.notes[i].text);
214 html += $.fn.annotateImage.createHiddenField("top_" + i, image.notes[i].top);
215 html += $.fn.annotateImage.createHiddenField("left_" + i, image.notes[i].left);
216 html += $.fn.annotateImage.createHiddenField("height_" + i, image.notes[i].height);
217 html += $.fn.annotateImage.createHiddenField("width_" + i, image.notes[i].width);
218 }
219 element.html(html);
220 };
221
222 $.fn.annotateImage.createHiddenField = function(name, value) {
223 return '&lt;input type="hidden" name="' + name + '" value="' + value + '" /&gt;<br />';
224 };
225
226 $.fn.annotateEdit = function(image, note) {
227 /// <summary>
228 /// Defines an editable annotation area.
229 /// </summary>
230 this.image = image;
231
232 if (note) {
233 this.note = note;
234 } else {
235 var newNote = new Object();
236 newNote.id = "new";
237 newNote.top = 30;
238 newNote.left = 30;
239 newNote.width = 30;
240 newNote.height = 30;
241 newNote.text = "";
242 this.note = newNote;
243 }
244
245 // Set area
246 var area = image.canvas.children('.image-annotate-edit').children('.image-annotate-edit-area');
247 this.area = area;
248 this.area.css('height', this.note.height + 'px');
249 this.area.css('width', this.note.width + 'px');
250 this.area.css('left', this.note.left + 'px');
251 this.area.css('top', this.note.top + 'px');
252
253 // Show the edition canvas and hide the view canvas
254 image.canvas.children('.image-annotate-view').hide();
255 image.canvas.children('.image-annotate-edit').show();
256
257 // Add the note (which we'll load with the form afterwards)
258 var form = $('<div id="image-annotate-edit-form"><form><textarea id="image-annotate-text" name="text" rows="3" cols="30">' + this.note.text + '</textarea></form></div>');
259 this.form = form;
260
261 $('body').append(this.form);
262 this.form.css('left', this.area.offset().left + 'px');
263 this.form.css('top', (parseInt(this.area.offset().top) + parseInt(this.area.height()) + 7) + 'px');
264
265 // Set the area as a draggable/resizable element contained in the image canvas.
266 // Would be better to use the containment option for resizable but buggy
267 area.resizable({
268 handles: 'all',
269
270 stop: function(e, ui) {
271 form.css('left', area.offset().left + 'px');
272 form.css('top', (parseInt(area.offset().top) + parseInt(area.height()) + 2) + 'px');
273 }
274 })
275 .draggable({
276 containment: image.canvas,
277 drag: function(e, ui) {
278 form.css('left', area.offset().left + 'px');
279 form.css('top', (parseInt(area.offset().top) + parseInt(area.height()) + 2) + 'px');
280 },
281 stop: function(e, ui) {
282 form.css('left', area.offset().left + 'px');
283 form.css('top', (parseInt(area.offset().top) + parseInt(area.height()) + 2) + 'px');
284 }
285 });
286 return this;
287 };
288
289 $.fn.annotateEdit.prototype.destroy = function() {
290 /// <summary>
291 /// Destroys an editable annotation area.
292 /// </summary>
293 this.image.canvas.children('.image-annotate-edit').hide();
294 this.area.resizable('destroy');
295 this.area.draggable('destroy');
296 this.area.css('height', '');
297 this.area.css('width', '');
298 this.area.css('left', '');
299 this.area.css('top', '');
300 this.form.remove();
301 }
302
303 $.fn.annotateView = function(image, note) {
304 /// <summary>
305 /// Defines an annotation area.
306 /// </summary>
307 this.image = image;
308
309 this.note = note;
310
311 this.editable = (note.editable && image.editable);
312
313 // Add the area
314 this.area = $('<div class="image-annotate-area' + (this.editable ? ' image-annotate-area-editable' : '') + '"><div></div></div>');
315 image.canvas.children('.image-annotate-view').prepend(this.area);
316
317 // Add the note
318 this.form = $('<div class="image-annotate-note">' + note.text + '</div>');
319 this.form.hide();
320 image.canvas.children('.image-annotate-view').append(this.form);
321 this.form.children('span.actions').hide();
322
323 // Set the position and size of the note
324 this.setPosition();
325
326 // Add the behavior: hide/display the note when hovering the area
327 var annotation = this;
328 this.area.hover(function() {
329 annotation.show();
330 }, function() {
331 annotation.hide();
332 });
333
334 // Edit a note feature
335 if (this.editable) {
336 var form = this;
337 this.area.click(function() {
338 form.edit();
339 });
340 }
341 };
342
343 $.fn.annotateView.prototype.setPosition = function() {
344 /// <summary>
345 /// Sets the position of an annotation.
346 /// </summary>
347 this.area.children('div').height((parseInt(this.note.height) - 2) + 'px');
348 this.area.children('div').width((parseInt(this.note.width) - 2) + 'px');
349 this.area.css('left', (this.note.left) + 'px');
350 this.area.css('top', (this.note.top) + 'px');
351 this.form.css('left', (this.note.left) + 'px');
352 this.form.css('top', (parseInt(this.note.top) + parseInt(this.note.height) + 7) + 'px');
353 };
354
355 $.fn.annotateView.prototype.show = function() {
356 /// <summary>
357 /// Highlights the annotation
358 /// </summary>
359 this.form.fadeIn(250);
360 if (!this.editable) {
361 this.area.addClass('image-annotate-area-hover');
362 } else {
363 this.area.addClass('image-annotate-area-editable-hover');
364 }
365 };
366
367 $.fn.annotateView.prototype.hide = function() {
368 /// <summary>
369 /// Removes the highlight from the annotation.
370 /// </summary>
371 this.form.fadeOut(250);
372 this.area.removeClass('image-annotate-area-hover');
373 this.area.removeClass('image-annotate-area-editable-hover');
374 };
375
376 $.fn.annotateView.prototype.destroy = function() {
377 /// <summary>
378 /// Destroys the annotation.
379 /// </summary>
380 this.area.remove();
381 this.form.remove();
382 }
383
384 $.fn.annotateView.prototype.edit = function() {
385 /// <summary>
386 /// Edits the annotation.
387 /// </summary>
388 if (this.image.mode == 'view') {
389 this.image.mode = 'edit';
390 var annotation = this;
391
392 // Create/prepare the editable note elements
393 var editable = new $.fn.annotateEdit(this.image, this.note);
394
395 $.fn.annotateImage.createSaveButton(editable, this.image, annotation);
396
397 // Add the delete button
398 var del = $('<a class="image-annotate-edit-delete">Delete</a>');
399 del.click(function() {
400 var form = $('#image-annotate-edit-form form');
401
402 $.fn.annotateImage.appendPosition(form, editable)
403
404 if (annotation.image.useAjax) {
405 $.ajax({
406 url: annotation.image.deleteUrl,
407 data: form.serialize(),
408 error: function(e) { alert("An error occured deleting that note.") }
409 });
410 }
411
412 annotation.image.mode = 'view';
413 editable.destroy();
414 annotation.destroy();
415 });
416 editable.form.append(del);
417
418 $.fn.annotateImage.createCancelButton(editable, this.image);
419 }
420 };
421
422 $.fn.annotateImage.appendPosition = function(form, editable) {
423 /// <summary>
424 /// Appends the annotations coordinates to the given form that is posted to the server.
425 /// </summary>
426 var areaFields = $('<input type="hidden" value="' + editable.area.height() + '" name="height"/>' +
427 '<input type="hidden" value="' + editable.area.width() + '" name="width"/>' +
428 '<input type="hidden" value="' + editable.area.position().top + '" name="top"/>' +
429 '<input type="hidden" value="' + editable.area.position().left + '" name="left"/>' +
430 '<input type="hidden" value="' + editable.note.id + '" name="id"/>');
431 form.append(areaFields);
432 }
433
434 $.fn.annotateView.prototype.resetPosition = function(editable, text) {
435 /// <summary>
436 /// Sets the position of an annotation.
437 /// </summary>
438 this.form.html(text);
439 this.form.hide();
440
441 // Resize
442 this.area.children('div').height(editable.area.height() + 'px');
443 this.area.children('div').width((editable.area.width() - 2) + 'px');
444 this.area.css('left', (editable.area.position().left) + 'px');
445 this.area.css('top', (editable.area.position().top) + 'px');
446 this.form.css('left', (editable.area.position().left) + 'px');
447 this.form.css('top', (parseInt(editable.area.position().top) + parseInt(editable.area.height()) + 7) + 'px');
448
449 // Save new position to note
450 this.note.top = editable.area.position().top;
451 this.note.left = editable.area.position().left;
452 this.note.height = editable.area.height();
453 this.note.width = editable.area.width();
454 this.note.text = text;
455 this.note.id = editable.note.id;
456 this.editable = true;
457 };
458
459})(jQuery);
Note: See TracBrowser for help on using the repository browser.