From: Mohamed Larabi Date: Tue, 12 Mar 2013 08:59:52 +0000 (+0100) Subject: Merge branch 'master' of ssh://git.onelab.eu/git/myslice-django X-Git-Tag: myslice-django-0.1-1~29^2~1 X-Git-Url: http://git.onelab.eu/?a=commitdiff_plain;h=7ef107329feb20b88d3782885b01068d6d0b5650;hp=1b014c4b719598ec82ca0fd2b49443571fabcbb4;p=unfold.git Merge branch 'master' of ssh://git.onelab.eu/git/myslice-django --- diff --git a/engine/manifoldapi.py b/engine/manifoldapi.py index c8c3e666..111241c5 100644 --- a/engine/manifoldapi.py +++ b/engine/manifoldapi.py @@ -25,7 +25,14 @@ class ManifoldAPI: def __getattr__(self, methodName): def func(*args, **kwds): result=getattr(self.proxy, methodName)(self.auth, *args, **kwds) - if debug: print methodName, self.auth, self.url,'->',result + ### debug + if debug: + print '===> backend call',methodName, self.auth, self.url,'->', + if not result: print "no/empty result" + elif isinstance (result,str): print "result is '%s'"%result + elif isinstance (result,list): print "result is a %d-elts list"%len(result) + else: print "dont know how to display result" + ### return result return func diff --git a/engine/manifoldproxy.py b/engine/manifoldproxy.py index a66f187a..d5a4ab08 100644 --- a/engine/manifoldproxy.py +++ b/engine/manifoldproxy.py @@ -1,9 +1,3 @@ -# this view is what the javascript talks to when it sends a query -# see also -# myslice/urls.py -# as well as -# static/js/manifold-async.js - import json # this is for django objects only #from django.core import serializers @@ -12,31 +6,48 @@ from django.http import HttpResponse, HttpResponseForbidden from engine.manifoldquery import ManifoldQuery from engine.manifoldapi import ManifoldAPI -# xxx should probably cater for -# format_in : how is the query encoded in POST -# format_out: how to serve the results +# add artificial delay in s +debug_spin=0 +#debug_spin=1 + +# this view is what the javascript talks to when it sends a query +# see also +# myslice/urls.py +# as well as +# static/js/manifold-async.js def api (request,format): + """the view associated with /manifold/api/ +with the query passed using POST""" + # expecting a POST if request.method != 'POST': print "manifoldproxy.api: unexpected method %s -- exiting"%request.method return # we only support json for now + # if needed in the future we should probably cater for + # format_in : how is the query encoded in POST + # format_out: how to serve the results if format != 'json': print "manifoldproxy.api: unexpected format %s -- exiting"%format return - # xxx actually ask the backend here + # translate incoming POST request into a query object manifold_query = ManifoldQuery() manifold_query.fill_from_dict(request.POST) + # retrieve session for request manifold_api_session_auth = request.session['manifold']['auth'] + # actually forward manifold_api= ManifoldAPI(auth=manifold_api_session_auth) - # forward answer=manifold_api.send_manifold_query (manifold_query) + if debug_spin: + import time + time.sleep(debug_spin) + # return json-encoded answer return HttpResponse (json.dumps(answer), mimetype="application/json") #################### -# to enable : see CSRF_FAILURE_VIEW in settings.py -# probably we want to elaborate this one a little in real life -# at least we can display the reason in the django output (although this turns out disappointing) +# see CSRF_FAILURE_VIEW in settings.py +# the purpose of redefining this was to display the failure reason somehow +# this however turns out disappointing/not very informative failure_answer=[ "csrf_failure" ] def csrf_failure(request, reason=""): print "CSRF failure with reason '%s'"%reason diff --git a/engine/manifoldquery.py b/engine/manifoldquery.py index 8e25ed9e..332309b2 100644 --- a/engine/manifoldquery.py +++ b/engine/manifoldquery.py @@ -51,7 +51,7 @@ class ManifoldQuery: # xxx # this should build an object from a dict as received from javascript # to see an example just look at the server's output - # incoming POST + # incoming POST def fill_from_dict (self, d): for key in d.keys(): for arg in ['action', 'method', 'filters', 'fields', 'timestamp', 'params']: diff --git a/engine/plugin.py b/engine/plugin.py index 00b37b7c..99a3ff8a 100644 --- a/engine/plugin.py +++ b/engine/plugin.py @@ -16,7 +16,7 @@ from engine.prelude import Prelude # . True : to debug all plugin DEBUG= False -#DEBUG= [ 'SimpleList' ] +#DEBUG= [ 'SliceList' ] # decorator to deflect calls on Plugin to its PluginSet def to_prelude (method): @@ -61,7 +61,8 @@ class Plugin: self.title=title if not domid: domid=Plugin.newdomid() self.domid=domid - self.classname=self._classname() + self.classname=self._py_classname() + self.plugin_classname=self._js_classname() self.visible=visible self.togglable=togglable self.toggled=toggled @@ -77,10 +78,14 @@ class Plugin: # do this only once the structure is fine self.pluginset.record_plugin(self) - def _classname (self): + def _py_classname (self): try: return self.__class__.__name__ except: return 'Plugin' + def _js_classname (self): + try: return self.plugin_classname () + except: return self._py_classname() + ########## def need_debug (self): if not DEBUG: return False @@ -111,6 +116,11 @@ class Plugin: result += "}" return result + # as a first approximation, only plugins that are associated with a query + # need to be prepared for js - others just get displayed and that's it + def is_asynchroneous (self): + return 'query' in self.__dict__ + # returns the html code for that plugin # in essence, wraps the results of self.render_content () def render (self, request): @@ -122,9 +132,8 @@ class Plugin: env.update(self.__dict__) result = render_to_string ('plugin.html',env) - # as a first approximation we're only concerned with plugins that are associated with a query - # other simpler plugins that only deal with layout do not need this - if 'query' in self.__dict__: + # export this only for relevant plugins + if self.is_asynchroneous(): env ['settings_json' ] = self.settings_json() # compute plugin-specific initialization js_init = render_to_string ( 'plugin-setenv.js', env ) @@ -221,3 +230,5 @@ class Plugin: # also 'query_uuid' gets replaced with query.uuid def json_settings_list (self): return ['json_settings_list-must-be-redefined'] + # might also define this one; see e.g. slicelist.py that piggybacks simplelist js code + # def plugin_classname (self): diff --git a/engine/pluginset.py b/engine/pluginset.py index 0a0e291f..be8c9ced 100644 --- a/engine/pluginset.py +++ b/engine/pluginset.py @@ -45,12 +45,12 @@ class PluginSet: # return the javascript that triggers all the queries def exec_queue_asynchroneously (self): js = "" - js += "var manifold_query_array = new Array();\n" + js += "var async_queries = new Array();\n" for (query,domid) in self._queue: qjson=query.to_json() id="'%s'"%domid if domid else 'undefined' - js += "manifold_query_array.push({'query':%(qjson)s, 'id':%(id)s});\n"%locals() - js += "onFunctionAvailable('manifold_async_exec', function() {manifold_async_exec(manifold_query_array);}, this, true);" + js += "async_queries.push({'query':%(qjson)s, 'id':%(id)s});\n"%locals() + js += "onFunctionAvailable('manifold_async_exec', function() {manifold_async_exec(async_queries);}, this, true);" self.reset_queue() # run only once the document is ready js = "$(document).ready(function(){%(js)s})"%locals() diff --git a/engine/prelude.py b/engine/prelude.py index fe628f20..20beb7db 100644 --- a/engine/prelude.py +++ b/engine/prelude.py @@ -61,6 +61,9 @@ class Prelude: env['css_files']= self.css_files env['js_chunks']= self.js_chunks env['css_chunks']=self.css_chunks + if debug: + print "prelude has %d js_files, %d css files, %d js chunks and %d css_chunks"%\ + (len(self.js_files),len(self.css_files),len(self.js_chunks),len(self.css_chunks),) # not sure how this should be done more cleanly from myslice.settings import STATIC_URL env ['STATIC_URL'] = STATIC_URL diff --git a/engine/static/js/manifold-async.js b/engine/static/js/manifold-async.js index d6e46f69..98433775 100644 --- a/engine/static/js/manifold-async.js +++ b/engine/static/js/manifold-async.js @@ -4,15 +4,12 @@ manifold_async_debug=false; var api_url = '/manifold/api/json/' -// Executes all async. queries contained in manifold_async_query_array, which is -// an array of hash (action, method, ts, filter, fields) -// -function manifold_async_exec(arr) -{ - if (manifold_async_debug) console.log('manifold_async_exec length='+ arr.length); +// Executes all async. queries +// input queries are specified as a list of {'query': new Query(..), 'id': } +function manifold_async_exec(queries) { + if (manifold_async_debug) console.log('manifold_async_exec length='+ queries.length); // start spinners - // xxx todo - I don't have the spinner jquery plugin yet -// jQuery('.loading').spin(); + jQuery('.need-spin').spin(); // We use js function closure to be able to pass the query (array) to the // callback function used when data is received @@ -20,20 +17,22 @@ function manifold_async_exec(arr) return function(data, textStatus) {manifold_async_success(data, query, id);} }; - // Loop through query array and issue XML/RPC queries - jQuery.each(arr, function(index, elt) { - hash=elt.query.to_hash(); - if (manifold_async_debug) console.log ('sending POST on ' + api_url + " iterating on " + hash); - jQuery.post(api_url, {'query': hash}, manifold_async_success_closure(elt.query, elt.id)); + // Loop through query array and use ajax to send back queries (to frontend) with json + jQuery.each(queries, function(index, tuple) { + hash=tuple.query.to_hash(); + if (manifold_async_debug) console.log ("sending POST on " + api_url + " iterating on " + tuple + " -> " + hash); + jQuery.post(api_url, {'query': hash}, manifold_async_success_closure(tuple.query, tuple.id)); }) } +/* not used function manifold_async_error(str) { var out = '

Error

Notice
  • ' + jQuery('
    ').text(str).html() + '
'; jQuery('#manifold_message').html(out); //onObjectAvailable('Spinners', function(){ Spinners.get('.loading').remove(); }, this, true); - jQuery('.loading').spin(); + jQuery('.need-spin').spin(false); } +*/ /* what the hell is this doing here ? function apply_format(key, value, type, method) { diff --git a/engine/templates/plugin-setenv.js b/engine/templates/plugin-setenv.js index f6c27faa..0d44075b 100644 --- a/engine/templates/plugin-setenv.js +++ b/engine/templates/plugin-setenv.js @@ -1 +1 @@ -$(document).ready(function() { jQuery('#{{ domid }}').{{ classname }}({{ settings_json|safe }}); }); +$(document).ready(function() { jQuery('#{{ domid }}').{{ plugin_classname }}({{ settings_json|safe }}); }); diff --git a/engine/templates/plugin.html b/engine/templates/plugin.html index d4b5cf1d..19e0b8b0 100644 --- a/engine/templates/plugin.html +++ b/engine/templates/plugin.html @@ -1,6 +1,6 @@ {##} {% if visible %} -
+
{% if togglable %} {% if not toggled %}

Show {{ title }} ({{ classname }})

diff --git a/plugins/simplelist.py b/plugins/simplelist.py index 6eb0f44a..d3ec8761 100644 --- a/plugins/simplelist.py +++ b/plugins/simplelist.py @@ -3,17 +3,26 @@ from engine.plugin import Plugin class SimpleList (Plugin) : # only deal with our own stuff here and let Plugin handle the rest - def __init__ (self, list=[], with_datatables=False, **settings): + def __init__ (self, key, value, with_datatables=False, **settings): Plugin.__init__ (self, **settings) - self.list=list + self.key=key + self.value=value self.with_datatables = with_datatables # SimpleList is useless per se anyways - def template_file (self): return "simplelist.html" + def template_file (self): + return "simplelist.html" + + def template_env (self, request): + env={} + header=getattr(self,'header',None) + if header: env['header']=header + return env def requirements (self): reqs = { 'js_files' : [ "js/simplelist.js", "js/plugin.js", "js/query.js", "js/onavail.js", - "js/manifold-pubsub.js", "js/manifold-async.js", ] , + "js/manifold-pubsub.js", "js/manifold-async.js", "spin/spin.all.js", +] , 'css_files': [ "css/simplelist.css" ], } if self.with_datatables: diff --git a/plugins/slicelist.py b/plugins/slicelist.py index c9804234..92673f26 100644 --- a/plugins/slicelist.py +++ b/plugins/slicelist.py @@ -1,17 +1,12 @@ from plugins.simplelist import SimpleList +# the SimpleList plugin requires 'key' and 'value' that are used +# on the results of the query for rendering class SliceList (SimpleList): - def __init__ (self, list=[], **settings): - SimpleList.__init__(self, **settings) - self.list = [ "%s"%(x,x) for x in list ] + def __init__ (self, **settings): + SimpleList.__init__(self, key='slice_hrn', value='slice_hrn', **settings) -# def requirements (self): -# reqs=SimpleList.requirements(self) -# reqs['js_files'].append('slice.js') -# reqs['js_files'].append('slice2.js') -# reqs['css_files'].append('slice.css') -# reqs['css_files'].append('slice2.css') -# reqs['js_chunks']=['js chunk1','js chunk2'] -# reqs['css_chunks']=['css chunk1','css chunk2'] -# return reqs + # writing a js plugin for that would be overkill, just use SimpleList + def plugin_classname (self): + return 'SimpleList' diff --git a/plugins/static/css/simplelist.css b/plugins/static/css/simplelist.css index 42937724..8bfe2167 100644 --- a/plugins/static/css/simplelist.css +++ b/plugins/static/css/simplelist.css @@ -1,3 +1,16 @@ +/* ---------- */ +td.simplelist { + padding: 0; + list-style: none; +} +td.simplelist>a { + padding-left: 8px; +} +th.simplelist { + font-size: 150%; +} +/* ---------- */ +/* xxx this probably should be separated in something related to datatables */ select { width: auto; } diff --git a/plugins/static/css/staticlist.css b/plugins/static/css/staticlist.css new file mode 100644 index 00000000..226e1892 --- /dev/null +++ b/plugins/static/css/staticlist.css @@ -0,0 +1,3 @@ +th.staticlist { + font-size: 150%; +} diff --git a/plugins/static/js/simplelist.js b/plugins/static/js/simplelist.js index 9c3752ba..d7f1d928 100644 --- a/plugins/static/js/simplelist.js +++ b/plugins/static/js/simplelist.js @@ -17,18 +17,16 @@ simplelist_debug=false; return this.each(function(){ var $this = $(this); var data = $this.data('SimpleList'); -// console.log("data" + data); -// looks like $this.attr('title') is undefined.. -// console.log('iterating in simplelist.init with data='+data+' and title='+$this.attr('title')); /* create an empty DOM object */ var SimpleList = $('
', { text : $this.attr('title') }); // If the plugin hasn't been initialized yet if ( ! data ) { /* Subscribe to query updates */ - var url='/results/' + options.query_uuid + '/changed'; - $.subscribe(url, {instance: $this}, update_list); - if (simplelist_debug) window.console.log('subscribing to ' + url); - $this.data('SimpleList', {options: options, target : this, SimpleList : SimpleList}); + var channel='/results/' + options.query_uuid + '/changed'; + /* passing $this as 2nd arg: callbacks will retrieve $this as e.data */ + $.subscribe(channel, $this, update_list); + if (simplelist_debug) window.console.log('subscribing to ' + channel); + $this.data('SimpleList', {options: options, SimpleList : SimpleList}); } }); }, @@ -56,41 +54,52 @@ simplelist_debug=false; /* Private methods */ function update_list(e, rows) { + // e.data is what we passed in second argument to subscribe + var $this=e.data; + // locate the , expected layout being + //
+ // -- or, if we don't have a header -- + //
+ var $tbody=$this.find("tbody.simplelist").first(); + if (simplelist_debug) console.log("$tbody goes with "+$tbody.get(0)); + if (rows.length == 0) { - e.data.instance.html('No result !'); + $tbody.html("No result !"); return; } if (typeof rows[0].error != 'undefined') { - e.data.instance.html('ERROR: ' + rows[0].error); + e.data.html("ERROR: " + rows[0].error + ""); return; } - options = e.data.instance.data().SimpleList.options; - is_cached = options.query.ts != 'now' ? true : false; - html_code=myslice_html_ul(rows, options.key, options.value, is_cached)+"
"; - e.data.instance.html(html_code); - + var options = e.data.data().SimpleList.options; + var is_cached = options.query.timestamp != 'now' ? true : false; + // here is where we use 'key' and 'value' from the SimpleList (python) constructor + html_code=myslice_html_tbody(rows, options.key, options.value, is_cached); + // locate the tbody from the template, set its text + $tbody.html(html_code); + // clear the spinning wheel + var $elt = e.data; + if (simplelist_debug) console.log("about to unspin with elt #" + $elt.attr('id') + " class " + $elt.attr('class')); + $elt.closest('.need-spin').spin(false); } - function myslice_html_ul(data, key, value, is_cached) { - var out = "
    "; + function myslice_html_tbody(data, key, value, is_cached) { +// return $.map (...) + var out = ""; for (var i = 0; i < data.length; i++) { - out += myslice_html_li(key, data[i][value], is_cached); - //out += myslice_html_li(key, myslice_html_a(data[i][key], data[i][value], key), is_cached); + out += myslice_html_tr(key, data[i][value], is_cached); } - out += "
"; return out; } - function myslice_html_li(type, value, is_cached) { - var cached = ''; - //if (is_cached) - // cached='
Cached information from the database
Timestamp: XX/XX/XX XX:XX:XX

Refresh in progress...
'; - if (type == 'slice_hrn') { - return "
  • " + value + cached + "
  • "; - } else if (type == 'network_hrn') { - return "
  • " + value + cached + "
  • "; + function myslice_html_tr(key, value,is_cached) { + var cached = is_cached ? "(cached)" : ""; + if (key == 'slice_hrn') { + return "" + value + cached + ""; + } else if (key == 'network_hrn') { + return "" + value + cached + ""; } else { - return "
  • " + value + "
  • "; + return "" + value + ""; } } diff --git a/plugins/staticlist.py b/plugins/staticlist.py new file mode 100644 index 00000000..b9c644b6 --- /dev/null +++ b/plugins/staticlist.py @@ -0,0 +1,29 @@ +from engine.plugin import Plugin + +class StaticList (Plugin) : + + # only deal with our own stuff here and let Plugin handle the rest + def __init__ (self, list=[], with_datatables=False, **settings): + Plugin.__init__ (self, **settings) + self.list=list + self.with_datatables = with_datatables + + # SimpleList is useless per se anyways + def template_file (self): + return "staticlist.html" + + def template_env (self, request): + env={} + header=getattr(self,'header',None) + if header: env['header']=header + env['list']=self.list + return env + + def requirements (self): + reqs = { 'js_files' : [ ] , + 'css_files': [ "css/staticlist.css" ], + } + if self.with_datatables: + reqs['js_files'].append ("datatables/js/dataTables.js") + reqs['js_files'].append ("js/with-datatables.js") + return reqs diff --git a/plugins/templates/simplelist.html b/plugins/templates/simplelist.html index d2143323..ca30c0cb 100644 --- a/plugins/templates/simplelist.html +++ b/plugins/templates/simplelist.html @@ -1,10 +1,7 @@ {% if header %} - + {% endif %} - -{% for item in list %} - -{% endfor %} +
    {{ header }}
    {{ header }}
    {{ item|safe }}
    diff --git a/plugins/templates/staticlist.html b/plugins/templates/staticlist.html new file mode 100644 index 00000000..2e30eeec --- /dev/null +++ b/plugins/templates/staticlist.html @@ -0,0 +1,10 @@ + +{% if header %} + +{% endif %} + +{% for item in list %} + +{% endfor %} + +
    {{ header }}
    {{ item|safe }}
    diff --git a/static/spin-1.2.8/Makefile b/static/spin-1.2.8/Makefile new file mode 100644 index 00000000..667a694a --- /dev/null +++ b/static/spin-1.2.8/Makefile @@ -0,0 +1,6 @@ +# we need 3 pieces +# spin.min.js - core - see URL +# jquery.min.js - the jqeury plugin - see URL +# spin.presets.js - our own settings defined as spin_presets +all: + cat spin.min.js jquery.spin.js spin.presets.js > spin.all.js diff --git a/static/spin-1.2.8/jquery.spin.js b/static/spin-1.2.8/jquery.spin.js new file mode 100644 index 00000000..d363e366 --- /dev/null +++ b/static/spin-1.2.8/jquery.spin.js @@ -0,0 +1,48 @@ +// https://gist.github.com/its-florida/1290439/raw/ce7face0309bdb265244e8483ce91dcf86e8cb14/jquery.spin.js +/* + +You can now create a spinner using any of the variants below: + +$("#el").spin(); // Produces default Spinner using the text color of #el. +$("#el").spin("small"); // Produces a 'small' Spinner using the text color of #el. +$("#el").spin("large", "white"); // Produces a 'large' Spinner in white (or any valid CSS color). +$("#el").spin({ ... }); // Produces a Spinner using your custom settings. + +$("#el").spin(false); // Kills the spinner. + +*/ +(function($) { + $.fn.spin = function(opts, color) { + var presets = { + "tiny": { lines: 8, length: 2, width: 2, radius: 3 }, + "small": { lines: 8, length: 4, width: 3, radius: 5 }, + "large": { lines: 10, length: 8, width: 4, radius: 8 } + }; + if (Spinner) { + return this.each(function() { + var $this = $(this), + data = $this.data(); + + if (data.spinner) { + data.spinner.stop(); + delete data.spinner; + } + if (opts !== false) { + if (typeof opts === "string") { + if (opts in presets) { + opts = presets[opts]; + } else { + opts = {}; + } + if (color) { + opts.color = color; + } + } + data.spinner = new Spinner($.extend({color: $this.css('color')}, opts)).spin(this); + } + }); + } else { + throw "Spinner class not available."; + } + }; +})(jQuery); diff --git a/static/spin-1.2.8/jquery.spin.js.url b/static/spin-1.2.8/jquery.spin.js.url new file mode 100644 index 00000000..3d0edf37 --- /dev/null +++ b/static/spin-1.2.8/jquery.spin.js.url @@ -0,0 +1 @@ +https://gist.github.com/its-florida/1290439/raw/ce7face0309bdb265244e8483ce91dcf86e8cb14/jquery.spin.js diff --git a/static/spin-1.2.8/spin.all.js b/static/spin-1.2.8/spin.all.js new file mode 100644 index 00000000..452bb862 --- /dev/null +++ b/static/spin-1.2.8/spin.all.js @@ -0,0 +1,65 @@ +!function(t,e,i){var o=["webkit","Moz","ms","O"],r={},n;function a(t,i){var o=e.createElement(t||"div"),r;for(r in i)o[r]=i[r];return o}function s(t){for(var e=1,i=arguments.length;e>1):parseInt(i.left,10)+r)+"px",top:(i.top=="auto"?f.y-s.y+(t.offsetHeight>>1):parseInt(i.top,10)+r)+"px"})}o.setAttribute("aria-role","progressbar");e.lines(o,e.opts);if(!n){var l=0,p=i.fps,c=p/i.speed,h=(1-i.opacity)/(c*i.trail/100),m=c/i.lines;(function y(){l++;for(var t=i.lines;t;t--){var r=Math.max(1-(l+t*m)%c*h,i.opacity);e.opacity(o,i.lines-t,r,i)}e.timeout=e.el&&setTimeout(y,~~(1e3/p))})()}return e},stop:function(){var t=this.el;if(t){clearTimeout(this.timeout);if(t.parentNode)t.parentNode.removeChild(t);this.el=i}return this},lines:function(t,e){var i=0,o;function r(t,o){return u(a(),{position:"absolute",width:e.length+e.width+"px",height:e.width+"px",background:t,boxShadow:o,transformOrigin:"left",transform:"rotate("+~~(360/e.lines*i+e.rotate)+"deg) translate("+e.radius+"px"+",0)",borderRadius:(e.corners*e.width>>1)+"px"})}for(;i',e)}var e=u(a("group"),{behavior:"url(#default#VML)"});if(!p(e,"transform")&&e.adj){f.addRule(".spin-vml","behavior:url(#default#VML)");m.prototype.lines=function(e,i){var o=i.length+i.width,r=2*o;function n(){return u(t("group",{coordsize:r+" "+r,coordorigin:-o+" "+-o}),{width:r,height:r})}var a=-(i.width+i.length)*2+"px",f=u(n(),{position:"absolute",top:a,left:a}),l;function p(e,r,a){s(f,s(u(n(),{rotation:360/i.lines*e+"deg",left:~~r}),s(u(t("roundrect",{arcsize:i.corners}),{width:o,height:i.width,left:i.radius,top:-i.width>>1,filter:a}),t("fill",{color:i.color,opacity:i.opacity}),t("stroke",{opacity:0}))))}if(i.shadow)for(l=1;l<=i.lines;l++)p(l,-2,"progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)");for(l=1;l<=i.lines;l++)p(l);return s(e,f)};m.prototype.opacity=function(t,e,i,o){var r=t.firstChild;o=o.shadow&&o.lines||0;if(r&&e+o> 1) : parseInt(o.left, 10) + mid) + 'px', + top: (o.top == 'auto' ? tp.y-ep.y + (target.offsetHeight >> 1) : parseInt(o.top, 10) + mid) + 'px' + }) + } + + el.setAttribute('aria-role', 'progressbar') + self.lines(el, self.opts) + + if (!useCssAnimations) { + // No CSS animation support, use setTimeout() instead + var i = 0 + , fps = o.fps + , f = fps/o.speed + , ostep = (1-o.opacity) / (f*o.trail / 100) + , astep = f/o.lines + + ;(function anim() { + i++; + for (var s=o.lines; s; s--) { + var alpha = Math.max(1-(i+s*astep)%f * ostep, o.opacity) + self.opacity(el, o.lines-s, alpha, o) + } + self.timeout = self.el && setTimeout(anim, ~~(1000/fps)) + })() + } + return self + }, + + stop: function() { + var el = this.el + if (el) { + clearTimeout(this.timeout) + if (el.parentNode) el.parentNode.removeChild(el) + this.el = undefined + } + return this + }, + + lines: function(el, o) { + var i = 0 + , seg + + function fill(color, shadow) { + return css(createEl(), { + position: 'absolute', + width: (o.length+o.width) + 'px', + height: o.width + 'px', + background: color, + boxShadow: shadow, + transformOrigin: 'left', + transform: 'rotate(' + ~~(360/o.lines*i+o.rotate) + 'deg) translate(' + o.radius+'px' +',0)', + borderRadius: (o.corners * o.width>>1) + 'px' + }) + } + + for (; i < o.lines; i++) { + seg = css(createEl(), { + position: 'absolute', + top: 1+~(o.width/2) + 'px', + transform: o.hwaccel ? 'translate3d(0,0,0)' : '', + opacity: o.opacity, + animation: useCssAnimations && addAnimation(o.opacity, o.trail, i, o.lines) + ' ' + 1/o.speed + 's linear infinite' + }) + + if (o.shadow) ins(seg, css(fill('#000', '0 0 4px ' + '#000'), {top: 2+'px'})) + + ins(el, ins(seg, fill(o.color, '0 0 1px rgba(0,0,0,.1)'))) + } + return el + }, + + opacity: function(el, i, val) { + if (i < el.childNodes.length) el.childNodes[i].style.opacity = val + } + + }) + + ///////////////////////////////////////////////////////////////////////// + // VML rendering for IE + ///////////////////////////////////////////////////////////////////////// + + /** + * Check and init VML support + */ + ;(function() { + + function vml(tag, attr) { + return createEl('<' + tag + ' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">', attr) + } + + var s = css(createEl('group'), {behavior: 'url(#default#VML)'}) + + if (!vendor(s, 'transform') && s.adj) { + + // VML support detected. Insert CSS rule ... + sheet.addRule('.spin-vml', 'behavior:url(#default#VML)') + + Spinner.prototype.lines = function(el, o) { + var r = o.length+o.width + , s = 2*r + + function grp() { + return css( + vml('group', { + coordsize: s + ' ' + s, + coordorigin: -r + ' ' + -r + }), + { width: s, height: s } + ) + } + + var margin = -(o.width+o.length)*2 + 'px' + , g = css(grp(), {position: 'absolute', top: margin, left: margin}) + , i + + function seg(i, dx, filter) { + ins(g, + ins(css(grp(), {rotation: 360 / o.lines * i + 'deg', left: ~~dx}), + ins(css(vml('roundrect', {arcsize: o.corners}), { + width: r, + height: o.width, + left: o.radius, + top: -o.width>>1, + filter: filter + }), + vml('fill', {color: o.color, opacity: o.opacity}), + vml('stroke', {opacity: 0}) // transparent stroke to fix color bleeding upon opacity change + ) + ) + ) + } + + if (o.shadow) + for (i = 1; i <= o.lines; i++) + seg(i, -2, 'progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)') + + for (i = 1; i <= o.lines; i++) seg(i) + return ins(el, g) + } + + Spinner.prototype.opacity = function(el, i, val, o) { + var c = el.firstChild + o = o.shadow && o.lines || 0 + if (c && i+o < c.childNodes.length) { + c = c.childNodes[i+o]; c = c && c.firstChild; c = c && c.firstChild + if (c) c.opacity = val + } + } + } + else + useCssAnimations = vendor(s, 'animation') + })() + + if (typeof define == 'function' && define.amd) + define(function() { return Spinner }) + else + window.Spinner = Spinner + +}(window, document); diff --git a/static/spin-1.2.8/spin.js.url b/static/spin-1.2.8/spin.js.url new file mode 100644 index 00000000..bf2c5bdf --- /dev/null +++ b/static/spin-1.2.8/spin.js.url @@ -0,0 +1 @@ +http://fgnass.github.com/spin.js/ diff --git a/static/spin-1.2.8/spin.min.js b/static/spin-1.2.8/spin.min.js new file mode 100644 index 00000000..efb4355d --- /dev/null +++ b/static/spin-1.2.8/spin.min.js @@ -0,0 +1 @@ +!function(t,e,i){var o=["webkit","Moz","ms","O"],r={},n;function a(t,i){var o=e.createElement(t||"div"),r;for(r in i)o[r]=i[r];return o}function s(t){for(var e=1,i=arguments.length;e>1):parseInt(i.left,10)+r)+"px",top:(i.top=="auto"?f.y-s.y+(t.offsetHeight>>1):parseInt(i.top,10)+r)+"px"})}o.setAttribute("aria-role","progressbar");e.lines(o,e.opts);if(!n){var l=0,p=i.fps,c=p/i.speed,h=(1-i.opacity)/(c*i.trail/100),m=c/i.lines;(function y(){l++;for(var t=i.lines;t;t--){var r=Math.max(1-(l+t*m)%c*h,i.opacity);e.opacity(o,i.lines-t,r,i)}e.timeout=e.el&&setTimeout(y,~~(1e3/p))})()}return e},stop:function(){var t=this.el;if(t){clearTimeout(this.timeout);if(t.parentNode)t.parentNode.removeChild(t);this.el=i}return this},lines:function(t,e){var i=0,o;function r(t,o){return u(a(),{position:"absolute",width:e.length+e.width+"px",height:e.width+"px",background:t,boxShadow:o,transformOrigin:"left",transform:"rotate("+~~(360/e.lines*i+e.rotate)+"deg) translate("+e.radius+"px"+",0)",borderRadius:(e.corners*e.width>>1)+"px"})}for(;i',e)}var e=u(a("group"),{behavior:"url(#default#VML)"});if(!p(e,"transform")&&e.adj){f.addRule(".spin-vml","behavior:url(#default#VML)");m.prototype.lines=function(e,i){var o=i.length+i.width,r=2*o;function n(){return u(t("group",{coordsize:r+" "+r,coordorigin:-o+" "+-o}),{width:r,height:r})}var a=-(i.width+i.length)*2+"px",f=u(n(),{position:"absolute",top:a,left:a}),l;function p(e,r,a){s(f,s(u(n(),{rotation:360/i.lines*e+"deg",left:~~r}),s(u(t("roundrect",{arcsize:i.corners}),{width:o,height:i.width,left:i.radius,top:-i.width>>1,filter:a}),t("fill",{color:i.color,opacity:i.opacity}),t("stroke",{opacity:0}))))}if(i.shadow)for(l=1;l<=i.lines;l++)p(l,-2,"progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)");for(l=1;l<=i.lines;l++)p(l);return s(e,f)};m.prototype.opacity=function(t,e,i,o){var r=t.firstChild;o=o.shadow&&o.lines||0;if(r&&e+o