├── .gitignore ├── README.markdown ├── css └── annotation.css ├── example_usage.html ├── images ├── accept.png ├── asterisk_yellow.png ├── cross.png ├── delete.png └── trafalgar-square-annotated.jpg ├── js ├── jquery-1.3.2.js ├── jquery-ui-1.7.1.js └── jquery.annotate.js └── php-api ├── delete.php ├── get.php ├── jQueryAnnotate.class.php └── save.php /.gitignore: -------------------------------------------------------------------------------- 1 | php-api/*.csv 2 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | ## This is a fork of jQuery Image Annotation plugin with PHP support 2 | 3 | Original author is Chris from [http://blog.flipbit.co.uk](http://blog.flipbit.co.uk) 4 | 5 | Project webpage can be found at [http://code.google.com/p/jquery-image-annotate/](http://code.google.com/p/jquery-image-annotate/) 6 | 7 | Code uses the same licence the plugin was released: GPL 2. 8 | 9 | 10 | ### How it works 11 | 12 | I wrote a class which stores annotations into a csv file (php-api/data.csv) 13 | with full support to delete, save and get the information from it to server 14 | json. 15 | 16 | 17 | ### Who uses it? 18 | 19 | * [Romanian Free Software Group](http://softwareliber.ro) for it's [FLOSSCamp Project](http://camp.softwareliber.ro/2009/membri) 20 | 21 | 22 | ### Future plans 23 | 24 | I hope this fork to be merged into upstream release, otherway I'm not sure I'll have enough time to support it. 25 | 26 | 27 | ### Requirements 28 | 29 | * PHP 5 30 | -------------------------------------------------------------------------------- /css/annotation.css: -------------------------------------------------------------------------------- 1 | .image-annotate-add { 2 | background: #fff url(../images/asterisk_yellow.png) no-repeat 3px 3px; 3 | border: solid 1px #ccc !important; 4 | color: #000 !important; 5 | cursor: pointer; 6 | display: block; 7 | float: left; 8 | font-family: Verdana, Sans-Serif; 9 | font-size: 12px; 10 | height: 18px; 11 | line-height: 18px; 12 | padding: 2px 0 2px 24px; 13 | margin: 5px 0; 14 | width: 64px; 15 | text-decoration: none; 16 | } 17 | .image-annotate-add:hover { 18 | background-color: #eee; 19 | } 20 | .image-annotate-canvas { 21 | border: solid 1px #ccc; 22 | background-position: left top; 23 | background-repeat: no-repeat; 24 | display: block; 25 | margin: 0; 26 | position: relative; 27 | } 28 | .image-annotate-view { 29 | display: none; 30 | position: relative; 31 | } 32 | .image-annotate-area { 33 | border: 1px solid #000000; 34 | position: absolute; 35 | } 36 | .image-annotate-area div { 37 | border: 1px solid #FFFFFF; 38 | display: block; 39 | } 40 | .image-annotate-area-hover div { 41 | border-color: yellow !important; 42 | } 43 | .image-annotate-area-editable { 44 | cursor: pointer; 45 | } 46 | .image-annotate-area-editable-hover div { 47 | border-color: #00AD00 !important; 48 | } 49 | .image-annotate-note { 50 | background: #E7FFE7 none repeat scroll 0 0; 51 | border: solid 1px #397F39; 52 | color: #000; 53 | display: none; 54 | font-family: Verdana, Sans-Serif; 55 | font-size: 12px; 56 | max-width: 200px; 57 | padding: 3px 7px; 58 | position: absolute; 59 | } 60 | .image-annotate-note .actions { 61 | display: block; 62 | font-size: 80%; 63 | } 64 | .image-annotate-edit { 65 | display: none; 66 | } 67 | #image-annotate-edit-form { 68 | background: #FFFEE3 none repeat scroll 0 0; 69 | border: 1px solid #000000; 70 | height: 78px; 71 | padding: 7px; 72 | position: absolute; 73 | width: 250px; 74 | } 75 | #image-annotate-edit-form form { 76 | clear: right; 77 | margin: 0 !important; 78 | padding: 0; 79 | z-index: 999; 80 | } 81 | #image-annotate-edit-form .box { 82 | margin: 0; 83 | } 84 | #image-annotate-edit-form input.form-text, #image-annotate-edit-form #edit-comment-wrapper textarea { 85 | width: 90%; 86 | } 87 | #image-annotate-edit-form textarea { 88 | height: 50px; 89 | font-family: Verdana, Sans-Serif; 90 | font-size: 12px; 91 | width: 248px; 92 | } 93 | #image-annotate-edit-form fieldset { 94 | background: transparent none repeat scroll 0 0; 95 | } 96 | #image-annotate-edit-form .form-item { 97 | margin: 0 0 5px; 98 | } 99 | #image-annotate-edit-form .form-button, #image-annotate-edit-form .form-submit { 100 | margin: 0; 101 | } 102 | #image-annotate-edit-form a { 103 | background-color: #fff; 104 | background-repeat: no-repeat; 105 | background-position: 3px 3px; 106 | border: solid 1px #ccc; 107 | color: #333; 108 | cursor: pointer; 109 | display: block; 110 | float: left; 111 | font-family: Verdana, Sans-Serif; 112 | font-size: 12px; 113 | height: 18px; 114 | line-height: 18px; 115 | padding: 2px 0 2px 24px; 116 | margin: 3px 6px 3px 0; 117 | width: 48px; 118 | } 119 | #image-annotate-edit-form a:hover { 120 | background-color: #eee; 121 | } 122 | .image-annotate-edit-area { 123 | border: 1px solid black; 124 | cursor: move; 125 | display: block; 126 | height: 60px; 127 | left: 10px; 128 | margin: 0; 129 | padding: 0; 130 | position: absolute; 131 | top: 10px; 132 | width: 60px; 133 | } 134 | .image-annotate-edit-area .ui-resizable-handle { 135 | opacity: 0.8; 136 | } 137 | .image-annotate-edit-ok { 138 | background-image: url(../images/accept.png); 139 | } 140 | .image-annotate-edit-delete { 141 | background-image: url(../images/delete.png); 142 | } 143 | .image-annotate-edit-close { 144 | background-image: url(../images/cross.png); 145 | } 146 | .ui-resizable { 147 | position: relative; 148 | } 149 | .ui-resizable-handle { 150 | position: absolute; 151 | font-size: 0.1px; 152 | z-index: 99999; 153 | display: block; 154 | } 155 | .ui-resizable-disabled .ui-resizable-handle, .ui-resizable- autohide .ui-resizable-handle { 156 | display: block; 157 | } 158 | .ui-resizable-n { 159 | cursor: n-resize; 160 | height: 7px; 161 | width: 100%; 162 | top: -5px; 163 | left: 0px; 164 | } 165 | .ui-resizable-s { 166 | cursor: s-resize; 167 | height: 7px; 168 | width: 100%; 169 | bottom: -5px; 170 | left: 0px; 171 | } 172 | .ui-resizable-e { 173 | cursor: e-resize; 174 | width: 7px; 175 | right: -5px; 176 | top: 0px; 177 | height: 100%; 178 | } 179 | .ui-resizable-w { 180 | cursor: w-resize; 181 | width: 7px; 182 | left: -5px; 183 | top: 0px; 184 | height: 100%; 185 | } 186 | .ui-resizable-se { 187 | cursor: se-resize; 188 | width: 12px; 189 | height: 12px; 190 | right: 1px; 191 | bottom: 1px; 192 | } 193 | .ui-resizable-sw { 194 | cursor: sw-resize; 195 | width: 9px; 196 | height: 9px; 197 | left: -5px; 198 | bottom: -5px; 199 | } 200 | .ui-resizable-nw { 201 | cursor: nw-resize; 202 | width: 9px; 203 | height: 9px; 204 | left: -5px; 205 | top: -5px; 206 | } 207 | .ui-resizable-ne { 208 | cursor: ne-resize; 209 | width: 9px; 210 | height: 9px; 211 | right: -5px; 212 | top: -5px; 213 | } -------------------------------------------------------------------------------- /example_usage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Image Annotations 4 | 5 | 6 | 7 | 8 | 9 | 19 | 20 | 21 |
22 | Trafalgar Square 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /images/accept.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stas/jquery-image-annotate-php-fork/985947e44d439381f6ddf8c3428c9775731bc372/images/accept.png -------------------------------------------------------------------------------- /images/asterisk_yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stas/jquery-image-annotate-php-fork/985947e44d439381f6ddf8c3428c9775731bc372/images/asterisk_yellow.png -------------------------------------------------------------------------------- /images/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stas/jquery-image-annotate-php-fork/985947e44d439381f6ddf8c3428c9775731bc372/images/cross.png -------------------------------------------------------------------------------- /images/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stas/jquery-image-annotate-php-fork/985947e44d439381f6ddf8c3428c9775731bc372/images/delete.png -------------------------------------------------------------------------------- /images/trafalgar-square-annotated.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stas/jquery-image-annotate-php-fork/985947e44d439381f6ddf8c3428c9775731bc372/images/trafalgar-square-annotated.jpg -------------------------------------------------------------------------------- /js/jquery-1.3.2.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery JavaScript Library v1.3.2 3 | * http://jquery.com/ 4 | * 5 | * Copyright (c) 2009 John Resig 6 | * Dual licensed under the MIT and GPL licenses. 7 | * http://docs.jquery.com/License 8 | * 9 | * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) 10 | * Revision: 6246 11 | */ 12 | (function(){ 13 | 14 | var 15 | // Will speed up references to window, and allows munging its name. 16 | window = this, 17 | // Will speed up references to undefined, and allows munging its name. 18 | undefined, 19 | // Map over jQuery in case of overwrite 20 | _jQuery = window.jQuery, 21 | // Map over the $ in case of overwrite 22 | _$ = window.$, 23 | 24 | jQuery = window.jQuery = window.$ = function( selector, context ) { 25 | // The jQuery object is actually just the init constructor 'enhanced' 26 | return new jQuery.fn.init( selector, context ); 27 | }, 28 | 29 | // A simple way to check for HTML strings or ID strings 30 | // (both of which we optimize for) 31 | quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/, 32 | // Is it a simple selector 33 | isSimple = /^.[^:#\[\.,]*$/; 34 | 35 | jQuery.fn = jQuery.prototype = { 36 | init: function( selector, context ) { 37 | // Make sure that a selection was provided 38 | selector = selector || document; 39 | 40 | // Handle $(DOMElement) 41 | if ( selector.nodeType ) { 42 | this[0] = selector; 43 | this.length = 1; 44 | this.context = selector; 45 | return this; 46 | } 47 | // Handle HTML strings 48 | if ( typeof selector === "string" ) { 49 | // Are we dealing with HTML string or an ID? 50 | var match = quickExpr.exec( selector ); 51 | 52 | // Verify a match, and that no context was specified for #id 53 | if ( match && (match[1] || !context) ) { 54 | 55 | // HANDLE: $(html) -> $(array) 56 | if ( match[1] ) 57 | selector = jQuery.clean( [ match[1] ], context ); 58 | 59 | // HANDLE: $("#id") 60 | else { 61 | var elem = document.getElementById( match[3] ); 62 | 63 | // Handle the case where IE and Opera return items 64 | // by name instead of ID 65 | if ( elem && elem.id != match[3] ) 66 | return jQuery().find( selector ); 67 | 68 | // Otherwise, we inject the element directly into the jQuery object 69 | var ret = jQuery( elem || [] ); 70 | ret.context = document; 71 | ret.selector = selector; 72 | return ret; 73 | } 74 | 75 | // HANDLE: $(expr, [context]) 76 | // (which is just equivalent to: $(content).find(expr) 77 | } else 78 | return jQuery( context ).find( selector ); 79 | 80 | // HANDLE: $(function) 81 | // Shortcut for document ready 82 | } else if ( jQuery.isFunction( selector ) ) 83 | return jQuery( document ).ready( selector ); 84 | 85 | // Make sure that old selector state is passed along 86 | if ( selector.selector && selector.context ) { 87 | this.selector = selector.selector; 88 | this.context = selector.context; 89 | } 90 | 91 | return this.setArray(jQuery.isArray( selector ) ? 92 | selector : 93 | jQuery.makeArray(selector)); 94 | }, 95 | 96 | // Start with an empty selector 97 | selector: "", 98 | 99 | // The current version of jQuery being used 100 | jquery: "1.3.2", 101 | 102 | // The number of elements contained in the matched element set 103 | size: function() { 104 | return this.length; 105 | }, 106 | 107 | // Get the Nth element in the matched element set OR 108 | // Get the whole matched element set as a clean array 109 | get: function( num ) { 110 | return num === undefined ? 111 | 112 | // Return a 'clean' array 113 | Array.prototype.slice.call( this ) : 114 | 115 | // Return just the object 116 | this[ num ]; 117 | }, 118 | 119 | // Take an array of elements and push it onto the stack 120 | // (returning the new matched element set) 121 | pushStack: function( elems, name, selector ) { 122 | // Build a new jQuery matched element set 123 | var ret = jQuery( elems ); 124 | 125 | // Add the old object onto the stack (as a reference) 126 | ret.prevObject = this; 127 | 128 | ret.context = this.context; 129 | 130 | if ( name === "find" ) 131 | ret.selector = this.selector + (this.selector ? " " : "") + selector; 132 | else if ( name ) 133 | ret.selector = this.selector + "." + name + "(" + selector + ")"; 134 | 135 | // Return the newly-formed element set 136 | return ret; 137 | }, 138 | 139 | // Force the current matched set of elements to become 140 | // the specified array of elements (destroying the stack in the process) 141 | // You should use pushStack() in order to do this, but maintain the stack 142 | setArray: function( elems ) { 143 | // Resetting the length to 0, then using the native Array push 144 | // is a super-fast way to populate an object with array-like properties 145 | this.length = 0; 146 | Array.prototype.push.apply( this, elems ); 147 | 148 | return this; 149 | }, 150 | 151 | // Execute a callback for every element in the matched set. 152 | // (You can seed the arguments with an array of args, but this is 153 | // only used internally.) 154 | each: function( callback, args ) { 155 | return jQuery.each( this, callback, args ); 156 | }, 157 | 158 | // Determine the position of an element within 159 | // the matched set of elements 160 | index: function( elem ) { 161 | // Locate the position of the desired element 162 | return jQuery.inArray( 163 | // If it receives a jQuery object, the first element is used 164 | elem && elem.jquery ? elem[0] : elem 165 | , this ); 166 | }, 167 | 168 | attr: function( name, value, type ) { 169 | var options = name; 170 | 171 | // Look for the case where we're accessing a style value 172 | if ( typeof name === "string" ) 173 | if ( value === undefined ) 174 | return this[0] && jQuery[ type || "attr" ]( this[0], name ); 175 | 176 | else { 177 | options = {}; 178 | options[ name ] = value; 179 | } 180 | 181 | // Check to see if we're setting style values 182 | return this.each(function(i){ 183 | // Set all the styles 184 | for ( name in options ) 185 | jQuery.attr( 186 | type ? 187 | this.style : 188 | this, 189 | name, jQuery.prop( this, options[ name ], type, i, name ) 190 | ); 191 | }); 192 | }, 193 | 194 | css: function( key, value ) { 195 | // ignore negative width and height values 196 | if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 ) 197 | value = undefined; 198 | return this.attr( key, value, "curCSS" ); 199 | }, 200 | 201 | text: function( text ) { 202 | if ( typeof text !== "object" && text != null ) 203 | return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); 204 | 205 | var ret = ""; 206 | 207 | jQuery.each( text || this, function(){ 208 | jQuery.each( this.childNodes, function(){ 209 | if ( this.nodeType != 8 ) 210 | ret += this.nodeType != 1 ? 211 | this.nodeValue : 212 | jQuery.fn.text( [ this ] ); 213 | }); 214 | }); 215 | 216 | return ret; 217 | }, 218 | 219 | wrapAll: function( html ) { 220 | if ( this[0] ) { 221 | // The elements to wrap the target around 222 | var wrap = jQuery( html, this[0].ownerDocument ).clone(); 223 | 224 | if ( this[0].parentNode ) 225 | wrap.insertBefore( this[0] ); 226 | 227 | wrap.map(function(){ 228 | var elem = this; 229 | 230 | while ( elem.firstChild ) 231 | elem = elem.firstChild; 232 | 233 | return elem; 234 | }).append(this); 235 | } 236 | 237 | return this; 238 | }, 239 | 240 | wrapInner: function( html ) { 241 | return this.each(function(){ 242 | jQuery( this ).contents().wrapAll( html ); 243 | }); 244 | }, 245 | 246 | wrap: function( html ) { 247 | return this.each(function(){ 248 | jQuery( this ).wrapAll( html ); 249 | }); 250 | }, 251 | 252 | append: function() { 253 | return this.domManip(arguments, true, function(elem){ 254 | if (this.nodeType == 1) 255 | this.appendChild( elem ); 256 | }); 257 | }, 258 | 259 | prepend: function() { 260 | return this.domManip(arguments, true, function(elem){ 261 | if (this.nodeType == 1) 262 | this.insertBefore( elem, this.firstChild ); 263 | }); 264 | }, 265 | 266 | before: function() { 267 | return this.domManip(arguments, false, function(elem){ 268 | this.parentNode.insertBefore( elem, this ); 269 | }); 270 | }, 271 | 272 | after: function() { 273 | return this.domManip(arguments, false, function(elem){ 274 | this.parentNode.insertBefore( elem, this.nextSibling ); 275 | }); 276 | }, 277 | 278 | end: function() { 279 | return this.prevObject || jQuery( [] ); 280 | }, 281 | 282 | // For internal use only. 283 | // Behaves like an Array's method, not like a jQuery method. 284 | push: [].push, 285 | sort: [].sort, 286 | splice: [].splice, 287 | 288 | find: function( selector ) { 289 | if ( this.length === 1 ) { 290 | var ret = this.pushStack( [], "find", selector ); 291 | ret.length = 0; 292 | jQuery.find( selector, this[0], ret ); 293 | return ret; 294 | } else { 295 | return this.pushStack( jQuery.unique(jQuery.map(this, function(elem){ 296 | return jQuery.find( selector, elem ); 297 | })), "find", selector ); 298 | } 299 | }, 300 | 301 | clone: function( events ) { 302 | // Do the clone 303 | var ret = this.map(function(){ 304 | if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) { 305 | // IE copies events bound via attachEvent when 306 | // using cloneNode. Calling detachEvent on the 307 | // clone will also remove the events from the orignal 308 | // In order to get around this, we use innerHTML. 309 | // Unfortunately, this means some modifications to 310 | // attributes in IE that are actually only stored 311 | // as properties will not be copied (such as the 312 | // the name attribute on an input). 313 | var html = this.outerHTML; 314 | if ( !html ) { 315 | var div = this.ownerDocument.createElement("div"); 316 | div.appendChild( this.cloneNode(true) ); 317 | html = div.innerHTML; 318 | } 319 | 320 | return jQuery.clean([html.replace(/ jQuery\d+="(?:\d+|null)"/g, "").replace(/^\s*/, "")])[0]; 321 | } else 322 | return this.cloneNode(true); 323 | }); 324 | 325 | // Copy the events from the original to the clone 326 | if ( events === true ) { 327 | var orig = this.find("*").andSelf(), i = 0; 328 | 329 | ret.find("*").andSelf().each(function(){ 330 | if ( this.nodeName !== orig[i].nodeName ) 331 | return; 332 | 333 | var events = jQuery.data( orig[i], "events" ); 334 | 335 | for ( var type in events ) { 336 | for ( var handler in events[ type ] ) { 337 | jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data ); 338 | } 339 | } 340 | 341 | i++; 342 | }); 343 | } 344 | 345 | // Return the cloned set 346 | return ret; 347 | }, 348 | 349 | filter: function( selector ) { 350 | return this.pushStack( 351 | jQuery.isFunction( selector ) && 352 | jQuery.grep(this, function(elem, i){ 353 | return selector.call( elem, i ); 354 | }) || 355 | 356 | jQuery.multiFilter( selector, jQuery.grep(this, function(elem){ 357 | return elem.nodeType === 1; 358 | }) ), "filter", selector ); 359 | }, 360 | 361 | closest: function( selector ) { 362 | var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null, 363 | closer = 0; 364 | 365 | return this.map(function(){ 366 | var cur = this; 367 | while ( cur && cur.ownerDocument ) { 368 | if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) { 369 | jQuery.data(cur, "closest", closer); 370 | return cur; 371 | } 372 | cur = cur.parentNode; 373 | closer++; 374 | } 375 | }); 376 | }, 377 | 378 | not: function( selector ) { 379 | if ( typeof selector === "string" ) 380 | // test special case where just one selector is passed in 381 | if ( isSimple.test( selector ) ) 382 | return this.pushStack( jQuery.multiFilter( selector, this, true ), "not", selector ); 383 | else 384 | selector = jQuery.multiFilter( selector, this ); 385 | 386 | var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType; 387 | return this.filter(function() { 388 | return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector; 389 | }); 390 | }, 391 | 392 | add: function( selector ) { 393 | return this.pushStack( jQuery.unique( jQuery.merge( 394 | this.get(), 395 | typeof selector === "string" ? 396 | jQuery( selector ) : 397 | jQuery.makeArray( selector ) 398 | ))); 399 | }, 400 | 401 | is: function( selector ) { 402 | return !!selector && jQuery.multiFilter( selector, this ).length > 0; 403 | }, 404 | 405 | hasClass: function( selector ) { 406 | return !!selector && this.is( "." + selector ); 407 | }, 408 | 409 | val: function( value ) { 410 | if ( value === undefined ) { 411 | var elem = this[0]; 412 | 413 | if ( elem ) { 414 | if( jQuery.nodeName( elem, 'option' ) ) 415 | return (elem.attributes.value || {}).specified ? elem.value : elem.text; 416 | 417 | // We need to handle select boxes special 418 | if ( jQuery.nodeName( elem, "select" ) ) { 419 | var index = elem.selectedIndex, 420 | values = [], 421 | options = elem.options, 422 | one = elem.type == "select-one"; 423 | 424 | // Nothing was selected 425 | if ( index < 0 ) 426 | return null; 427 | 428 | // Loop through all the selected options 429 | for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { 430 | var option = options[ i ]; 431 | 432 | if ( option.selected ) { 433 | // Get the specifc value for the option 434 | value = jQuery(option).val(); 435 | 436 | // We don't need an array for one selects 437 | if ( one ) 438 | return value; 439 | 440 | // Multi-Selects return an array 441 | values.push( value ); 442 | } 443 | } 444 | 445 | return values; 446 | } 447 | 448 | // Everything else, we just grab the value 449 | return (elem.value || "").replace(/\r/g, ""); 450 | 451 | } 452 | 453 | return undefined; 454 | } 455 | 456 | if ( typeof value === "number" ) 457 | value += ''; 458 | 459 | return this.each(function(){ 460 | if ( this.nodeType != 1 ) 461 | return; 462 | 463 | if ( jQuery.isArray(value) && /radio|checkbox/.test( this.type ) ) 464 | this.checked = (jQuery.inArray(this.value, value) >= 0 || 465 | jQuery.inArray(this.name, value) >= 0); 466 | 467 | else if ( jQuery.nodeName( this, "select" ) ) { 468 | var values = jQuery.makeArray(value); 469 | 470 | jQuery( "option", this ).each(function(){ 471 | this.selected = (jQuery.inArray( this.value, values ) >= 0 || 472 | jQuery.inArray( this.text, values ) >= 0); 473 | }); 474 | 475 | if ( !values.length ) 476 | this.selectedIndex = -1; 477 | 478 | } else 479 | this.value = value; 480 | }); 481 | }, 482 | 483 | html: function( value ) { 484 | return value === undefined ? 485 | (this[0] ? 486 | this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") : 487 | null) : 488 | this.empty().append( value ); 489 | }, 490 | 491 | replaceWith: function( value ) { 492 | return this.after( value ).remove(); 493 | }, 494 | 495 | eq: function( i ) { 496 | return this.slice( i, +i + 1 ); 497 | }, 498 | 499 | slice: function() { 500 | return this.pushStack( Array.prototype.slice.apply( this, arguments ), 501 | "slice", Array.prototype.slice.call(arguments).join(",") ); 502 | }, 503 | 504 | map: function( callback ) { 505 | return this.pushStack( jQuery.map(this, function(elem, i){ 506 | return callback.call( elem, i, elem ); 507 | })); 508 | }, 509 | 510 | andSelf: function() { 511 | return this.add( this.prevObject ); 512 | }, 513 | 514 | domManip: function( args, table, callback ) { 515 | if ( this[0] ) { 516 | var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(), 517 | scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ), 518 | first = fragment.firstChild; 519 | 520 | if ( first ) 521 | for ( var i = 0, l = this.length; i < l; i++ ) 522 | callback.call( root(this[i], first), this.length > 1 || i > 0 ? 523 | fragment.cloneNode(true) : fragment ); 524 | 525 | if ( scripts ) 526 | jQuery.each( scripts, evalScript ); 527 | } 528 | 529 | return this; 530 | 531 | function root( elem, cur ) { 532 | return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ? 533 | (elem.getElementsByTagName("tbody")[0] || 534 | elem.appendChild(elem.ownerDocument.createElement("tbody"))) : 535 | elem; 536 | } 537 | } 538 | }; 539 | 540 | // Give the init function the jQuery prototype for later instantiation 541 | jQuery.fn.init.prototype = jQuery.fn; 542 | 543 | function evalScript( i, elem ) { 544 | if ( elem.src ) 545 | jQuery.ajax({ 546 | url: elem.src, 547 | async: false, 548 | dataType: "script" 549 | }); 550 | 551 | else 552 | jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); 553 | 554 | if ( elem.parentNode ) 555 | elem.parentNode.removeChild( elem ); 556 | } 557 | 558 | function now(){ 559 | return +new Date; 560 | } 561 | 562 | jQuery.extend = jQuery.fn.extend = function() { 563 | // copy reference to target object 564 | var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; 565 | 566 | // Handle a deep copy situation 567 | if ( typeof target === "boolean" ) { 568 | deep = target; 569 | target = arguments[1] || {}; 570 | // skip the boolean and the target 571 | i = 2; 572 | } 573 | 574 | // Handle case when target is a string or something (possible in deep copy) 575 | if ( typeof target !== "object" && !jQuery.isFunction(target) ) 576 | target = {}; 577 | 578 | // extend jQuery itself if only one argument is passed 579 | if ( length == i ) { 580 | target = this; 581 | --i; 582 | } 583 | 584 | for ( ; i < length; i++ ) 585 | // Only deal with non-null/undefined values 586 | if ( (options = arguments[ i ]) != null ) 587 | // Extend the base object 588 | for ( var name in options ) { 589 | var src = target[ name ], copy = options[ name ]; 590 | 591 | // Prevent never-ending loop 592 | if ( target === copy ) 593 | continue; 594 | 595 | // Recurse if we're merging object values 596 | if ( deep && copy && typeof copy === "object" && !copy.nodeType ) 597 | target[ name ] = jQuery.extend( deep, 598 | // Never move original objects, clone them 599 | src || ( copy.length != null ? [ ] : { } ) 600 | , copy ); 601 | 602 | // Don't bring in undefined values 603 | else if ( copy !== undefined ) 604 | target[ name ] = copy; 605 | 606 | } 607 | 608 | // Return the modified object 609 | return target; 610 | }; 611 | 612 | // exclude the following css properties to add px 613 | var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i, 614 | // cache defaultView 615 | defaultView = document.defaultView || {}, 616 | toString = Object.prototype.toString; 617 | 618 | jQuery.extend({ 619 | noConflict: function( deep ) { 620 | window.$ = _$; 621 | 622 | if ( deep ) 623 | window.jQuery = _jQuery; 624 | 625 | return jQuery; 626 | }, 627 | 628 | // See test/unit/core.js for details concerning isFunction. 629 | // Since version 1.3, DOM methods and functions like alert 630 | // aren't supported. They return false on IE (#2968). 631 | isFunction: function( obj ) { 632 | return toString.call(obj) === "[object Function]"; 633 | }, 634 | 635 | isArray: function( obj ) { 636 | return toString.call(obj) === "[object Array]"; 637 | }, 638 | 639 | // check if an element is in a (or is an) XML document 640 | isXMLDoc: function( elem ) { 641 | return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || 642 | !!elem.ownerDocument && jQuery.isXMLDoc( elem.ownerDocument ); 643 | }, 644 | 645 | // Evalulates a script in a global context 646 | globalEval: function( data ) { 647 | if ( data && /\S/.test(data) ) { 648 | // Inspired by code by Andrea Giammarchi 649 | // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html 650 | var head = document.getElementsByTagName("head")[0] || document.documentElement, 651 | script = document.createElement("script"); 652 | 653 | script.type = "text/javascript"; 654 | if ( jQuery.support.scriptEval ) 655 | script.appendChild( document.createTextNode( data ) ); 656 | else 657 | script.text = data; 658 | 659 | // Use insertBefore instead of appendChild to circumvent an IE6 bug. 660 | // This arises when a base node is used (#2709). 661 | head.insertBefore( script, head.firstChild ); 662 | head.removeChild( script ); 663 | } 664 | }, 665 | 666 | nodeName: function( elem, name ) { 667 | return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); 668 | }, 669 | 670 | // args is for internal usage only 671 | each: function( object, callback, args ) { 672 | var name, i = 0, length = object.length; 673 | 674 | if ( args ) { 675 | if ( length === undefined ) { 676 | for ( name in object ) 677 | if ( callback.apply( object[ name ], args ) === false ) 678 | break; 679 | } else 680 | for ( ; i < length; ) 681 | if ( callback.apply( object[ i++ ], args ) === false ) 682 | break; 683 | 684 | // A special, fast, case for the most common use of each 685 | } else { 686 | if ( length === undefined ) { 687 | for ( name in object ) 688 | if ( callback.call( object[ name ], name, object[ name ] ) === false ) 689 | break; 690 | } else 691 | for ( var value = object[0]; 692 | i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} 693 | } 694 | 695 | return object; 696 | }, 697 | 698 | prop: function( elem, value, type, i, name ) { 699 | // Handle executable functions 700 | if ( jQuery.isFunction( value ) ) 701 | value = value.call( elem, i ); 702 | 703 | // Handle passing in a number to a CSS property 704 | return typeof value === "number" && type == "curCSS" && !exclude.test( name ) ? 705 | value + "px" : 706 | value; 707 | }, 708 | 709 | className: { 710 | // internal only, use addClass("class") 711 | add: function( elem, classNames ) { 712 | jQuery.each((classNames || "").split(/\s+/), function(i, className){ 713 | if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) ) 714 | elem.className += (elem.className ? " " : "") + className; 715 | }); 716 | }, 717 | 718 | // internal only, use removeClass("class") 719 | remove: function( elem, classNames ) { 720 | if (elem.nodeType == 1) 721 | elem.className = classNames !== undefined ? 722 | jQuery.grep(elem.className.split(/\s+/), function(className){ 723 | return !jQuery.className.has( classNames, className ); 724 | }).join(" ") : 725 | ""; 726 | }, 727 | 728 | // internal only, use hasClass("class") 729 | has: function( elem, className ) { 730 | return elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1; 731 | } 732 | }, 733 | 734 | // A method for quickly swapping in/out CSS properties to get correct calculations 735 | swap: function( elem, options, callback ) { 736 | var old = {}; 737 | // Remember the old values, and insert the new ones 738 | for ( var name in options ) { 739 | old[ name ] = elem.style[ name ]; 740 | elem.style[ name ] = options[ name ]; 741 | } 742 | 743 | callback.call( elem ); 744 | 745 | // Revert the old values 746 | for ( var name in options ) 747 | elem.style[ name ] = old[ name ]; 748 | }, 749 | 750 | css: function( elem, name, force, extra ) { 751 | if ( name == "width" || name == "height" ) { 752 | var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ]; 753 | 754 | function getWH() { 755 | val = name == "width" ? elem.offsetWidth : elem.offsetHeight; 756 | 757 | if ( extra === "border" ) 758 | return; 759 | 760 | jQuery.each( which, function() { 761 | if ( !extra ) 762 | val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0; 763 | if ( extra === "margin" ) 764 | val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0; 765 | else 766 | val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0; 767 | }); 768 | } 769 | 770 | if ( elem.offsetWidth !== 0 ) 771 | getWH(); 772 | else 773 | jQuery.swap( elem, props, getWH ); 774 | 775 | return Math.max(0, Math.round(val)); 776 | } 777 | 778 | return jQuery.curCSS( elem, name, force ); 779 | }, 780 | 781 | curCSS: function( elem, name, force ) { 782 | var ret, style = elem.style; 783 | 784 | // We need to handle opacity special in IE 785 | if ( name == "opacity" && !jQuery.support.opacity ) { 786 | ret = jQuery.attr( style, "opacity" ); 787 | 788 | return ret == "" ? 789 | "1" : 790 | ret; 791 | } 792 | 793 | // Make sure we're using the right name for getting the float value 794 | if ( name.match( /float/i ) ) 795 | name = styleFloat; 796 | 797 | if ( !force && style && style[ name ] ) 798 | ret = style[ name ]; 799 | 800 | else if ( defaultView.getComputedStyle ) { 801 | 802 | // Only "float" is needed here 803 | if ( name.match( /float/i ) ) 804 | name = "float"; 805 | 806 | name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase(); 807 | 808 | var computedStyle = defaultView.getComputedStyle( elem, null ); 809 | 810 | if ( computedStyle ) 811 | ret = computedStyle.getPropertyValue( name ); 812 | 813 | // We should always get a number back from opacity 814 | if ( name == "opacity" && ret == "" ) 815 | ret = "1"; 816 | 817 | } else if ( elem.currentStyle ) { 818 | var camelCase = name.replace(/\-(\w)/g, function(all, letter){ 819 | return letter.toUpperCase(); 820 | }); 821 | 822 | ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; 823 | 824 | // From the awesome hack by Dean Edwards 825 | // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 826 | 827 | // If we're not dealing with a regular pixel number 828 | // but a number that has a weird ending, we need to convert it to pixels 829 | if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { 830 | // Remember the original values 831 | var left = style.left, rsLeft = elem.runtimeStyle.left; 832 | 833 | // Put in the new values to get a computed value out 834 | elem.runtimeStyle.left = elem.currentStyle.left; 835 | style.left = ret || 0; 836 | ret = style.pixelLeft + "px"; 837 | 838 | // Revert the changed values 839 | style.left = left; 840 | elem.runtimeStyle.left = rsLeft; 841 | } 842 | } 843 | 844 | return ret; 845 | }, 846 | 847 | clean: function( elems, context, fragment ) { 848 | context = context || document; 849 | 850 | // !context.createElement fails in IE with an error but returns typeof 'object' 851 | if ( typeof context.createElement === "undefined" ) 852 | context = context.ownerDocument || context[0] && context[0].ownerDocument || document; 853 | 854 | // If a single string is passed in and it's a single tag 855 | // just do a createElement and skip the rest 856 | if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) { 857 | var match = /^<(\w+)\s*\/?>$/.exec(elems[0]); 858 | if ( match ) 859 | return [ context.createElement( match[1] ) ]; 860 | } 861 | 862 | var ret = [], scripts = [], div = context.createElement("div"); 863 | 864 | jQuery.each(elems, function(i, elem){ 865 | if ( typeof elem === "number" ) 866 | elem += ''; 867 | 868 | if ( !elem ) 869 | return; 870 | 871 | // Convert html string into DOM nodes 872 | if ( typeof elem === "string" ) { 873 | // Fix "XHTML"-style tags in all browsers 874 | elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ 875 | return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? 876 | all : 877 | front + ">"; 878 | }); 879 | 880 | // Trim whitespace, otherwise indexOf won't work as expected 881 | var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase(); 882 | 883 | var wrap = 884 | // option or optgroup 885 | !tags.indexOf("", "" ] || 887 | 888 | !tags.indexOf("", "" ] || 890 | 891 | tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && 892 | [ 1, "", "
" ] || 893 | 894 | !tags.indexOf("", "" ] || 896 | 897 | // matched above 898 | (!tags.indexOf("", "" ] || 900 | 901 | !tags.indexOf("", "" ] || 903 | 904 | // IE can't serialize and @si', // Strip out javascript 156 | '@<[\/\!]*?[^<>]*?>@si', // Strip out HTML tags 157 | '@([\r\n])[\s]+@', // Strip out white space 158 | '@&(quot|#34);@i', // Replace HTML entities 159 | '@&(lt|#60);@i', 160 | '@&(gt|#62);@i', 161 | '@&(nbsp|#160);@i', 162 | '@&#(\d+);@e'); // evaluate as php 163 | 164 | $replace = array ('', 165 | '', 166 | '\1', 167 | '"', 168 | '<', 169 | '>', 170 | ' ', 171 | 'chr(\1)'); 172 | 173 | return trim(preg_replace($search, $replace, $text)); 174 | } 175 | 176 | /** 177 | * Prints a
 formated error message
178 | 	 *
179 | 	 * @param string $m message to be printed as an error
180 | 	 * @access public
181 | 	 * @return string
182 | 	 */
183 | 	function print_error($m) {
184 | 		die("
$m
\n"); 185 | } 186 | 187 | /** 188 | * Destructor 189 | * Closes the file the constructor opened. 190 | * 191 | * @access public 192 | * @return void 193 | */ 194 | function __destruct() { 195 | fclose($this->file); 196 | } 197 | } 198 | ?> 199 | -------------------------------------------------------------------------------- /php-api/save.php: -------------------------------------------------------------------------------- 1 | save(); 5 | ?> 6 | --------------------------------------------------------------------------------