diff --git a/.gitignore b/.gitignore index db4561e..1532d61 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ var/ *.egg-info/ .installed.cfg *.egg +.idea # PyInstaller # Usually these files are written by a python script from a template diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8e56430 --- /dev/null +++ b/LICENSE @@ -0,0 +1,32 @@ +Copyright (c) 2012 by Freedom Dumlao. + +Some rights reserved. + +Redistribution and use in source and binary forms of the software as well +as documentation, with or without modification, are permitted provided +that the following conditions are met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + +* The names of the contributors may not be used to endorse or + promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT +NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e47d75a --- /dev/null +++ b/__init__.py @@ -0,0 +1,5 @@ +import pkg_resources + + +def get_javascript_string(): + return pkg_resources.resource_string('sajax', 'static/js/sajax.js') \ No newline at end of file diff --git a/response.py b/response.py new file mode 100644 index 0000000..69ed38e --- /dev/null +++ b/response.py @@ -0,0 +1,76 @@ +from flask import jsonify, session, url_for +import json, os, pprint +from config import * + + +class SResponse: + """An object which eases the work between ajax and python""" + data = [] + + def __init__(self, cls=None): + self.data = [] + + # If we're loading a view and have not yet loaded its static dependencies, asynchronously load them + if cls.__class__.__name__.find('View') != -1: + folder = cls.__class__.__name__.replace('View', '').lower() + + # if its not in loaded, it hasn't been loaded + if folder not in session['sajax']['loaded_dep']: + # css first, /static/css/obt_folder.css + if os.path.isfile(ROOT+'/static/css/obt_'+folder+'.css'): + self.data.append({'action': 'load', 'data': url_for('static', filename='css/obt_%s.css' % folder)}) + + # first the js file at /static/js/folder/folder.js if it exists + if os.path.isfile(ROOT+'/static/js/%s/%s.js' % (folder, folder)): + self.data.append({'action': 'load', 'data': url_for('static', filename='js/%s/%s.js' % (folder, folder))}) + + # then all the rest of the js files in /static/js/folder/ + if os.path.isdir(ROOT+'/static/js/'+folder): + for file in os.listdir(ROOT+'/static/js/'+folder): + if (file != folder+'.js'): + self.data.append({'action': 'load', 'data': url_for('static', filename='js/%s/%s' % (folder, file))}) + + # this folder's js files might have defined an object + self.data.append({'action': 'js', 'data': "if (typeof %s != 'undefined') window.%s = new %s()" % (folder, folder, folder)}) + # The files will be loaded, mark them as such + session['sajax']['loaded_dep'].append(folder) + + def __add__(self, other): + if isinstance(other.data, basestring): + decoder = json.JSONDecoder() + self.data += decoder.decode(other.data)['r'] + elif isinstance(other.data, list): + self.data += other.data + + return self + + def get_json(self): + return jsonify(r=self.data) + + def assign(self, selector, html): + self.data.append({ 'action': 'as', 'selector': selector, 'data': html }) + + def append(self, selector, html): + self.data.append({ 'action': 'ap', 'selector': selector, 'data': html }) + + def append_template(self, selector, html, template_id): + self.data.append({ 'action': 'ap_t', 'selector': selector, 'data': html, 'id': template_id }) + + def script(self, js): + self.data.append({ 'action': 'js', 'data': js }) + + def position(self, selector, keyword): + self.data.append({ 'action': 'js', 'data': "$('%s').data('position', '%s').addClass('positioned'); sajax.position('%s');" % (selector, keyword, selector) }) + + def remove(self, selector): + self.data.append({ 'action': 'js', 'data': "$('%s').remove()" % selector }) + + def alert(self, text): + self.data.append({ 'action': 'alert', 'data': text}) + + def call(self, path): + self.data.append({ 'action': 'js', 'data': "sajax.call('%s')" % path}) + + def error(self, baseSelector, error): + self.data.append({ 'action': 'assign', 'selector': baseSelector+' .error', 'data': error }) + self.data.append({ 'action': 'js', 'data': "$('"+baseSelector+" .error').show();" }) \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..23c36e7 --- /dev/null +++ b/setup.py @@ -0,0 +1,39 @@ +""" +sajax +------------- + +Python / jQuery AJAX library, structured similarly to old xajax library +""" +from setuptools import setup + +setup( + name='sajax', + version='0.1', + url='https://github.com/harkenn/sajax', + license='BSD', + author='Robert Burnham', + author_email='burnhamrobertp@gmail.com', + description="Python / jQuery AJAX library", + long_description=__doc__, + py_modules=['sajax'], + zip_safe=False, + include_package_data=True, + platforms='any', + install_requires=[ + 'Flask>=0.9' + ], + classifiers=[ + 'Environment :: Web Environment', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', + 'Topic :: Software Development :: Libraries :: Python Modules' + ] +) \ No newline at end of file diff --git a/static/js/sajax.js b/static/js/sajax.js new file mode 100644 index 0000000..b0cbc08 --- /dev/null +++ b/static/js/sajax.js @@ -0,0 +1,177 @@ +function sajax() { + this.loads_pending = 0; +} + +/** + * Position element(s) by keyword + * + * @param selector + */ +sajax.prototype.position = function(selector) { + selector = selector === undefined ? '' : selector; + + var positioned = $('.positioned'+selector), i, elem, data; + for (i = 0; i < positioned.length; i++) { + elem = $(positioned[i]), data = elem.data(); + + switch(data['position']) { + case 'top-left': + elem.css({ 'left': '0px', 'top': '0px' }); + break; + case 'top-center': + elem.css({ 'left': ($(window).width()-elem.outerWidth())/2+'px', 'top': '0px' }); + break; + case 'top-right': + elem.css({ 'left': $(window).width()-elem.outerWidth()+'px', 'top': '0px' }); + break; + case 'center': + elem.css({ 'left': ($(window).width()-elem.outerWidth())/2+'px', 'top': ($(window).height()-elem.outerHeight())/2+'px' }); + break; + case 'bottom-left': + elem.css({ 'left': '0px', 'top': $(window).height()-elem.outerHeight()+'px' }); + break; + case 'bottom-right': + elem.css({ 'top': $(window).height()-elem.outerHeight()+'px', 'left': $(window).width()-elem.outerWidth()+'px' }); + break; + } + } +} + +/** + * Process sajax response (data) + * + * sajax.call binds this as the success processing method. data is a json array of + * commands (denoted sometimes by an abbreviation) to take action on and any corresponding + * data (such as JS that needs evaluating, or some HTML to be assigned). + * + * @param data + */ +sajax.prototype.parseResponse = function(data) { + // Limit to 20 iterations (10 seconds) + var i = data.i === undefined ? 0 : data.i; + // Store delayed data to be called via timeout + var delayed_data = [] + + if (i > 20) + throw 'Exceeded parseResponse processing limit'; + + $.each(data.r, function() { + // If we have a load queued and we haven't already waited 10 seconds, continue to wait + if (delayed_data.length > 0 || (sajax.loads_pending > 0 && this.action != 'load')) { + delayed_data.push(this); + } else { + // Process the action for this iteration of data.r + switch(this.action) { + case 'as': + $(this.selector).html(this.data); + break; + case 'ap': + $(this.selector).append(this.data); + break; + case 'ap_t': + // append to selector only if the id of the template is not on the page + if ($(this.id).length == 0) + $(this.selector).append(this.data); + break; + case 'js': + eval(this.data); + break; + case 'alert': + alert(this.data); + break; + case 'load': + sajax.load(this.data); + break; + default: + break; + } + } + }); + + // If there was one or more delayed_data, set a timeout to re-process + if (delayed_data.length > 0) + setTimeout(function() { sajax.parseResponse({'r': delayed_data, 'i':++i}) }, 500); +}; + +/** + * load a static file onto the DOM via ajax + * + * @param file + */ +sajax.prototype.load = function(file) { + if (file != undefined) + this.loads_pending++; + + try { + switch(file.split('.').pop()) { + case 'css': + link = $(''); + $('head').append(link); + // css files effectively load automatically + this.loads_pending--; + break; + case 'js': + $.getScript(file, function() { sajax.loads_pending--; }); + break; + } + } catch(e) { + this.loads_pending--; + throw e; + } +} + +/** + * Shortcut to initialize a jquery AJAX request via post + * + * @param path + * @param parameters + * @returns {boolean} + */ +sajax.prototype.call = function(path, parameters) { + if (parameters === undefined) parameters = {}; + if (path.slice(0, 1) !== '/') path = '/' + path; + + $.ajax({ + type: 'POST', + contentType: 'application/json', + url: path, + data: JSON.stringify(parameters), + datatype: 'json', + success: this.parseResponse + }); + + return false; +}; + +/** + * Build array from arguments, retrun + * + * @returns {{}} + */ +sajax.prototype.buildData = function() { + // extend string holds the js that will be eval'd into the returned object + var e_s = ''; + var data = {}; + + for (var i = 0; i < arguments.length; i++) { + if (typeof(arguments[i]) == 'object') + e_s += 'arguments['+i+'],'; + else if (typeof(arguments[i]) == 'string') + e_s += "$('"+arguments[i].replace("'", "\\'")+"').serializeForm(),"; + } + + if (e_s.length > 1) + eval('$.extend(true, data, '+e_s.substring(0, e_s.length-1)+');'); + + return data; +} + +/** + * Return serialized data for easy form consumption + * + * @param selector + * @returns {{}} + */ +sajax.prototype.getFormValues = function(selector) { + return $(selector).serializeForm(); +} \ No newline at end of file