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