├── __init__.py ├── .gitignore ├── main ├── __init__.py ├── models.py ├── models.pyc ├── tests.pyc ├── views.pyc ├── __init__.pyc ├── tests.py └── views.py ├── urls.pyc ├── __init__.pyc ├── settings.pyc ├── media ├── logo.png ├── logo_black.png ├── log.txt ├── style.css └── jquery-1.3.2.js ├── .project ├── .pydevproject ├── manage.py ├── urls.py ├── README ├── settings.py └── templates └── smstest.html /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | python -------------------------------------------------------------------------------- /main/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /main/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /urls.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwanjau/SMSsync-Python-Django-webservice/HEAD/urls.pyc -------------------------------------------------------------------------------- /__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwanjau/SMSsync-Python-Django-webservice/HEAD/__init__.pyc -------------------------------------------------------------------------------- /settings.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwanjau/SMSsync-Python-Django-webservice/HEAD/settings.pyc -------------------------------------------------------------------------------- /main/models.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwanjau/SMSsync-Python-Django-webservice/HEAD/main/models.pyc -------------------------------------------------------------------------------- /main/tests.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwanjau/SMSsync-Python-Django-webservice/HEAD/main/tests.pyc -------------------------------------------------------------------------------- /main/views.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwanjau/SMSsync-Python-Django-webservice/HEAD/main/views.pyc -------------------------------------------------------------------------------- /media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwanjau/SMSsync-Python-Django-webservice/HEAD/media/logo.png -------------------------------------------------------------------------------- /main/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwanjau/SMSsync-Python-Django-webservice/HEAD/main/__init__.pyc -------------------------------------------------------------------------------- /media/logo_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwanjau/SMSsync-Python-Django-webservice/HEAD/media/logo_black.png -------------------------------------------------------------------------------- /main/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates writing tests using the unittest module. These will pass 3 | when you run "manage.py test". 4 | 5 | Replace this with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | 11 | class SimpleTest(TestCase): 12 | def test_basic_addition(self): 13 | """ 14 | Tests that 1 + 1 always equals 2. 15 | """ 16 | self.assertEqual(1 + 1, 2) 17 | -------------------------------------------------------------------------------- /media/log.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | From : +254717043938 5 | Message: Hello 6 | Timestamp: 3rd February 2012 06:53 7 | Message Id: 12345 8 | Sent to: +254722123456 9 | 10 | 11 | From : +254717043938 12 | Message: school uon 13 | Timestamp: 3rd February 2012 06:59 14 | Message Id: 01234 15 | Sent to: +254722123456 16 | 17 | 18 | From : +254717043938 19 | Message: Hello 20 | Timestamp: 6th February 2012 18:15 21 | Message Id: 12345 22 | Sent to: +254722123456 23 | 24 | 25 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | SMSsync-Python-Django-webservice 4 | 5 | 6 | 7 | 8 | 9 | org.python.pydev.PyDevBuilder 10 | 11 | 12 | 13 | 14 | 15 | org.python.pydev.django.djangoNature 16 | org.python.pydev.pythonNature 17 | 18 | 19 | -------------------------------------------------------------------------------- /.pydevproject: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Default 6 | python 2.7 7 | 8 | DJANGO_MANAGE_LOCATION 9 | /manage.py 10 | 11 | 12 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from django.core.management import execute_manager 3 | import imp 4 | try: 5 | imp.find_module('settings') # Assumed to be in the same directory. 6 | except ImportError: 7 | import sys 8 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__) 9 | sys.exit(1) 10 | 11 | import settings 12 | 13 | if __name__ == "__main__": 14 | execute_manager(settings) 15 | -------------------------------------------------------------------------------- /urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import patterns 2 | from django.views.generic.simple import direct_to_template 3 | from django.conf import settings 4 | 5 | from main import views 6 | 7 | urlpatterns = patterns('', 8 | 9 | #Url configuration 10 | (r'^$', direct_to_template, {'template': 'smstest.html'}), 11 | (r'^sms', views.smssync), 12 | ) 13 | 14 | if settings.DEBUG: 15 | urlpatterns += patterns('', 16 | (r'^media/(?P.*)$', 'django.views.static.serve',{'document_root': settings.MEDIA_ROOT}), 17 | ) 18 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | *** About *** 2 | name: Python/Django SMSSync webservice 3 | website: smssync.ushahidi.com 4 | description:A sample Python/Django application to demonstrate how to write a webservice to successfully communicate with SMSSync. 5 | version: 1.11 6 | requires: Python 2.7.2 and Django 1.3.1 7 | author : Caine Wanjau caine@faidika.co.ke 8 | 9 | This webservice has been tested on a computer running Python 2.7.2 and Django 1.3.1 10 | 11 | Before using this software, please familarise yourself with the SMSsync documentation at smssync.ushahidi.com/doc 12 | 13 | Installation Instructions: 14 | 15 | 1: Copy this folder into your computer: 16 | 17 | 2: Open Command Prompt/Terminal and Change to the top level directory of the application and run the following command: 18 | $ pyhton manage.py runserver 19 | 20 | 3: Open your browser and run http://localhost:8000 [Your port number may be different depending on your settings] 21 | 22 | 4: On the home page you will see a form. This form simulates the data that would be sent from a phone running SMSsync. 23 | 24 | 5: Fill in the data as appropriate and sumbit your form. If the data has been submitted correctly you will get the following response: 25 | {"payload": {"success": "true"}} 26 | 27 | 6: When using the webservice from the SMSsync Gateway, set your callback URL to http:// yourdomain.com/sms 28 | 29 | All messages that are sent are logged in a text file. To view the messages, point your browser to yourdomain.com/media/log.txt -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for smssync project. 2 | import os 3 | 4 | ROOT_PATH = os.path.dirname(__file__) 5 | 6 | DEBUG = True 7 | TEMPLATE_DEBUG = DEBUG 8 | 9 | ADMINS = ( 10 | # ('Your Name', 'your_email@example.com'), 11 | ) 12 | 13 | MANAGERS = ADMINS 14 | 15 | 16 | TIME_ZONE = 'Africa/Nairobi' 17 | 18 | 19 | LANGUAGE_CODE = 'en-us' 20 | 21 | 22 | SITE_ID = 1 23 | 24 | 25 | USE_I18N = True 26 | 27 | 28 | USE_L10N = True 29 | 30 | 31 | MEDIA_ROOT = os.path.join(ROOT_PATH, 'media') 32 | 33 | 34 | MEDIA_URL = '/media/' 35 | 36 | STATIC_ROOT = '' 37 | 38 | STATIC_URL = '/static/' 39 | 40 | ADMIN_MEDIA_PREFIX = '/static/admin/' 41 | 42 | 43 | STATICFILES_FINDERS = ( 44 | 'django.contrib.staticfiles.finders.FileSystemFinder', 45 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 46 | # 'django.contrib.staticfiles.finders.DefaultStorageFinder', 47 | ) 48 | 49 | SECRET_KEY = '' 50 | 51 | 52 | TEMPLATE_LOADERS = ( 53 | 'django.template.loaders.filesystem.Loader', 54 | 'django.template.loaders.app_directories.Loader', 55 | # 'django.template.loaders.eggs.Loader', 56 | ) 57 | 58 | MIDDLEWARE_CLASSES = ( 59 | 'django.middleware.common.CommonMiddleware', 60 | 'django.contrib.sessions.middleware.SessionMiddleware', 61 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 62 | 'django.contrib.messages.middleware.MessageMiddleware', 63 | ) 64 | 65 | ROOT_URLCONF = 'urls' 66 | 67 | TEMPLATE_DIRS = ( 68 | os.path.join(os.path.dirname(__file__), 'templates'), 69 | ) 70 | 71 | INSTALLED_APPS = ( 72 | 'main', 73 | 'django.contrib.auth', 74 | 'django.contrib.contenttypes', 75 | 'django.contrib.sessions', 76 | 'django.contrib.sites', 77 | 'django.contrib.messages', 78 | 'django.contrib.staticfiles', 79 | # Uncomment the next line to enable the admin: 80 | # 'django.contrib.admin', 81 | # Uncomment the next line to enable admin documentation: 82 | # 'django.contrib.admindocs', 83 | ) -------------------------------------------------------------------------------- /media/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * css styling adapted from 3 | * http://woork.blogspot.com/2008/06/clean-and-pure-css-form-design.html 4 | */ 5 | 6 | body{ 7 | font-family:"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif; 8 | font-size:12px; 9 | } 10 | p, h1, form, button{border:0; margin:0; padding:0;} 11 | 12 | footer{text-align: center;} 13 | 14 | 15 | .spacer{clear:both; height:1px;} 16 | /* ----------- My Form ----------- */ 17 | .myform{ 18 | margin:0 auto; 19 | width:460px; 20 | padding:14px; 21 | } 22 | 23 | 24 | /* ----------- stylized ----------- */ 25 | #stylized{ 26 | border:solid 2px #1DD300; 27 | background:#52E93A; 28 | } 29 | #stylized h1 { 30 | font-size:18px; 31 | font-weight:bold; 32 | margin-bottom:8px; 33 | } 34 | 35 | #stylized h2 { 36 | font-size:14px; 37 | font-weight:bold; 38 | margin-bottom:8px; 39 | } 40 | 41 | #stylized p{ 42 | font-size:11px; 43 | color:#000; 44 | margin-bottom:20px; 45 | /*border-bottom:solid 1px #b7ddf2;*/ 46 | padding-bottom:10px; 47 | } 48 | #stylized label{ 49 | display:block; 50 | font-weight:bold; 51 | text-align:right; 52 | width:200px; 53 | float:left; 54 | } 55 | #stylized .small{ 56 | color:#000; 57 | display:block; 58 | font-size:11px; 59 | font-weight:normal; 60 | text-align:right; 61 | width:200px; 62 | } 63 | #stylized input{ 64 | float:left; 65 | font-size:12px; 66 | padding:4px 2px; 67 | border:solid 1px #138900; 68 | width:200px; 69 | margin:2px 0 20px 10px; 70 | } 71 | 72 | 73 | #stylized .disp{ 74 | float:left; 75 | font-size:12px; 76 | padding-bottom: 2px; 77 | width:200px; 78 | margin:2px 0 10px 10px; 79 | } 80 | 81 | #stylized button{ 82 | clear:both; 83 | margin-left:150px; 84 | width:125px; 85 | height:31px; 86 | background:#666666; 87 | text-align:center; 88 | line-height:31px; 89 | color:#FFFFFF; 90 | font-size:11px; 91 | font-weight:bold; 92 | } 93 | 94 | #stylized text{ 95 | display:block; 96 | font-weight:bold; 97 | text-align:right; 98 | width:200px; 99 | float:left; 100 | } 101 | 102 | #stylized #error { 103 | color:red; 104 | font-size:12px; 105 | display:none; 106 | text-align: center; 107 | margin-top: 5px; 108 | } 109 | 110 | .needsfilled { 111 | background:red; 112 | color:white; 113 | } -------------------------------------------------------------------------------- /templates/smstest.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | SMSsync test 4 | 5 | 6 | 7 | 8 | 47 | 48 | 49 | 50 | 51 |
52 |
53 |

SMSsync test

54 |

Input the details below to test your response from the server

55 | 56 |

There were errors on the form, please make sure all fields are fill out correctly.

57 | 58 | 61 | 62 | 63 | 66 | 67 | 68 | 71 | 72 | 73 | 76 |

+254722123456

77 | 78 | 81 | 82 | 83 | 86 |

{% now "jS F Y H:i" %}

87 | 88 | 89 | 90 | 91 | 92 |
93 | 94 |
95 |
96 | 97 | 100 | 101 | -------------------------------------------------------------------------------- /main/views.py: -------------------------------------------------------------------------------- 1 | # Sample Python webservice for SMSsync 2 | # Developed by Caine Wanjau 3 | # For inquiries contact caine@faidika.co.ke 4 | # 5 | # Comment snippets adapted from sample php webservice at http://smssync.ushahidi.com/doc 6 | 7 | from django.http import HttpResponse 8 | from django.utils import simplejson 9 | from django.conf import settings 10 | 11 | import os 12 | 13 | def smssync(request): 14 | 15 | if request.method == 'POST': 16 | 17 | #get the phone number that sent the SMS. 18 | 19 | if "from" in request.POST and request.POST["from"]: 20 | 21 | num_from = request.POST["from"] 22 | 23 | 24 | # get the SMS aka message sent 25 | 26 | if "message" in request.POST and request.POST["message"]: 27 | 28 | message = request.POST["message"] 29 | 30 | 31 | # set success to true 32 | 33 | success = "true" 34 | 35 | # in case a secret has been set at SMSSync side, 36 | # get it for match making 37 | 38 | if "secret" in request.POST and request.POST["secret"]: 39 | 40 | secret = request.POST["secret"] 41 | 42 | 43 | # get the timestamp of the SMS 44 | 45 | if "sent_timestamp" in request.POST and request.POST["sent_timestamp"]: 46 | 47 | sent_timestamp = request.POST["sent_timestamp"] 48 | 49 | 50 | if "sent_to" in request.POST and request.POST["sent_to"]: 51 | 52 | sent_to = request.POST["sent_to"] 53 | 54 | 55 | if "message_id" in request.POST and request.POST["message_id"]: 56 | 57 | message_id = request.POST["message_id"] 58 | else: 59 | message_id = 0 60 | 61 | 62 | # We have have retrieved the data sent over by SMSSync 63 | # via HTTP. next thing to do is to do something with 64 | # the data. Either echo it or write to a file or even 65 | # store it in a database. This is entirely up to you. 66 | # After, return a JSON string back to SMSSync to know 67 | # if the web service received the message successfully. 68 | 69 | # In this demo, we are just going to send a success or false reply to SMSsync. 70 | 71 | 72 | if len(num_from) > 0 and len(message) > 0 and len(sent_timestamp) > 0 and len(sent_to) > 0: 73 | if secret != "123456": 74 | 75 | success = "false" 76 | 77 | string = "From : %s\n" %(num_from) 78 | string += "Message: %s\n" %(message) 79 | string += "Timestamp: %s\n" %(sent_timestamp) 80 | string += "Message Id: %s\n" %(message_id) 81 | string += "Sent to: %s\n\n\n" %(sent_to) 82 | 83 | 84 | writefile = "log.txt" 85 | 86 | fh = open(os.path.join(settings.MEDIA_ROOT, writefile), 'a') 87 | 88 | fh.write(string) 89 | 90 | fh.close() 91 | 92 | else: 93 | success = "false" 94 | 95 | reply = {"payload":{"success": success}} 96 | 97 | return HttpResponse(simplejson.dumps(reply),mimetype="application/json") 98 | 99 | -------------------------------------------------------------------------------- /media/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