Commit ea2791e6 authored by Thomas Lechauve's avatar Thomas Lechauve

Main feature added: List instances

other features:
error handler with showing over bootstrap interface.
loading animation during an ajax request
parent 7c485ef0
...@@ -4,6 +4,44 @@ ...@@ -4,6 +4,44 @@
<title></title> <title></title>
<link href="static/css/bootstrap.min.css" rel="stylesheet"/> <link href="static/css/bootstrap.min.css" rel="stylesheet"/>
<script id="server.list" type="text/html">
<article>
<table class="table table-condensed">
<caption><h2>Computers list</h2></caption>
<tr><th>Name</th><th>Reference</th><th>State</th></tr>
<tr><td>Couscous</td><td>Comp-1</td><td><span class="label label-success">Started</span></td></tr>
<tr><td>Merguez</td><td>Comp-3</td><td><span class="label label-success">Started</span></td></tr>
<tr><td>Plop</td><td>Comp-5</td><td><span class="label label-important">Stopped</span></td></tr>
</table>
</article>
</script>
<script id="service.list" type="text/html">
<article>
<table class="table table-condensed" id="service.table">
<caption><h2>Instances list</h2></caption>
<tr><th>Title</th><th>Status</th></tr>
</table>
</article>
</script>
<script id="service.list.elem" type="text/html">
<tr><td><a href="{{ url }}">{{ title }}</a></td><td>{{ status }}</td></tr>
</script>
<script id="invoice.list" type="text/html">
<article>
<table class="table table-condensed">
<caption><h2>Invoices</h2></caption>
<tr><th>Number</th><th>Total price</th><th>Currency</th><th>Payment</th></tr>
<tr><td>43</td><td>1.0</td><td>Euro</td><td><span class="label label-warning">Waiting</span></td></th>
<tr><td>44</td><td>0.0</td><td>Euro</td><td><span class="label label-success">Payed</span></td></th>
<tr><td>45</td><td>1.0</td><td>Euro</td><td><span class="label label-success">Payed</span></td></th>
<tr><td>01</td><td></td><td>Euro</td><td><span class="label label-important">Ongoing</span></td></th>
</table>
</article>
</script>
<script id="form.new.instance" type="text/html"> <script id="form.new.instance" type="text/html">
<article> <article>
<form class="form-horizontal"> <form class="form-horizontal">
...@@ -32,17 +70,17 @@ ...@@ -32,17 +70,17 @@
<script id="auth" type="text/html"> <script id="auth" type="text/html">
<article> <article>
<p>Authentification needed. Are you agree to be redirect to login ?</p> <p>Authentification needed. Are you agree to be redirect to login ?</p>
<a href="http://{{ host }}?response_type=token&client_id={{ client_id }}&redirect_uri={{ redirect }}">Redirect</a> <a href="{{ host }}?response_type=token&client_id={{ client_id }}&redirect_uri={{ redirect }}">Redirect</a>
</article> </article>
</script> </script>
<script id="service" type="text/html"> <script id="instance" type="text/html">
{{! Service page template }} {{! Service page template }}
<article> <article>
<form class="form-horizontal"> <form class="form-horizontal">
<fieldset> <fieldset>
<legend> <legend>
{{ instance_id }} {{ title }}
<div class="pull-right"> <div class="pull-right">
<div class="btn">Bang</div> <div class="btn">Bang</div>
<div class="btn">Destroy</div> <div class="btn">Destroy</div>
...@@ -52,7 +90,7 @@ ...@@ -52,7 +90,7 @@
<div class="control-group"> <div class="control-group">
<label class="control-label">Reference</label> <label class="control-label">Reference</label>
<div class="controls"> <div class="controls">
<span class="uneditable-input">{{ instance_id }}</span> <span class="uneditable-input">{{ }}</span>
</div> </div>
</div> </div>
<div class="control-group"> <div class="control-group">
...@@ -118,14 +156,17 @@ ...@@ -118,14 +156,17 @@
</form> </form>
</script> </script>
<script id="service-error" type="text/html"> <script id="error" type="text/html">
<div class="alert alert-error" style="text-align: center;"> <div class="alert alert-{{ state }}">
<h4>Service : {{ id }} not found !</h4> <a class="close" data-dismiss="alert" href="#">×</a>
<h4 class="alert-heading">{{ state }}</h4>
{{ message }}
</div> </div>
</script> </script>
</head> </head>
<body> <body>
<div id="loading" style="position: absolute; right: 20px; top: 20px;"></div>
<div class="navbar"> <div class="navbar">
<div class="navbar-inner"> <div class="navbar-inner">
<div class="container-fluid"> <div class="container-fluid">
...@@ -152,14 +193,11 @@ ...@@ -152,14 +193,11 @@
<div class="well"> <div class="well">
<ul class="nav nav-list"> <ul class="nav nav-list">
<li class="nav-header">Servers</li> <li class="nav-header">Servers</li>
<li autofocus="autofocus"><a href="#computers"><i class="icon-list"></i>List all servers</a></li> <li autofocus="autofocus"><a href="#/computers"><i class="icon-list"></i>List all servers</a></li>
<li><a href="#/computer"><i class="icon-plus-sign"></i>Add new server</a></li> <li><a href="#/computer"><i class="icon-plus-sign"></i>Add new server</a></li>
<li><a href="#/computer/comp-1"><i class="icon-"></i>Comp-1</a></li>
<li class="nav-header">Services</li> <li class="nav-header">Services</li>
<li><a href="#/services"><i class="icon-list"></i>List all services</a></li> <li><a href="#/instances"><i class="icon-list"></i>List all services</a></li>
<li><a href="#/service"><i class="icon-plus-sign"></i>Add new service</a></li> <li><a href="#/instance"><i class="icon-plus-sign"></i>Add new service</a></li>
<li><a href="#/service/200"><i class="icon-"></i>inst-0</a></li>
<li><a href="#/service/401"><i class="icon-"></i>inst-1</a></li>
<li class="nav-header">Account</li> <li class="nav-header">Account</li>
<li><a href="#/invoices"><i class="icon-inbox"></i>Invoices</a></li> <li><a href="#/invoices"><i class="icon-inbox"></i>Invoices</a></li>
<li><a href="#/settings"><i class="icon-cog"></i>Settings</a></li> <li><a href="#/settings"><i class="icon-cog"></i>Settings</a></li>
...@@ -171,13 +209,16 @@ ...@@ -171,13 +209,16 @@
</ul> </ul>
</div> </div>
</div> </div>
<section class="span9" id="main"> <section class="span9" id="main" style="min-height: 200px">
<!--Body content--> <!--Body content-->
</section> </section>
</div> </div>
</div> </div>
<script type="text/javascript" src="static/js/jquery-1.7.2.js"></script> <script type="text/javascript" src="static/js/jquery-1.7.2.js"></script>
<script type="text/javascript" src="static/js/bootstrap-alert.js"></script>
<script type="text/javascript" src="static/js/spin.js"></script>
<script type="text/javascript" src="static/js/jquery-spin.js"></script>
<script type="text/javascript" src="static/js/ICanHaz.min.js"></script> <script type="text/javascript" src="static/js/ICanHaz.min.js"></script>
<script type="text/javascript" src="static/js/modernizr-2.5.3.js"></script> <script type="text/javascript" src="static/js/modernizr-2.5.3.js"></script>
<script type="text/javascript" src="static/js/sinon-1.3.2.js"></script> <script type="text/javascript" src="static/js/sinon-1.3.2.js"></script>
......
/* ==========================================================
* bootstrap-alert.js v2.0.3
* http://twitter.github.com/bootstrap/javascript.html#alerts
* ==========================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================== */
!function ($) {
"use strict"; // jshint ;_;
/* ALERT CLASS DEFINITION
* ====================== */
var dismiss = '[data-dismiss="alert"]'
, Alert = function (el) {
$(el).on('click', dismiss, this.close)
}
Alert.prototype.close = function (e) {
var $this = $(this)
, selector = $this.attr('data-target')
, $parent
if (!selector) {
selector = $this.attr('href')
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
}
$parent = $(selector)
e && e.preventDefault()
$parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent())
$parent.trigger(e = $.Event('close'))
if (e.isDefaultPrevented()) return
$parent.removeClass('in')
function removeElement() {
$parent
.trigger('closed')
.remove()
}
$.support.transition && $parent.hasClass('fade') ?
$parent.on($.support.transition.end, removeElement) :
removeElement()
}
/* ALERT PLUGIN DEFINITION
* ======================= */
$.fn.alert = function (option) {
return this.each(function () {
var $this = $(this)
, data = $this.data('alert')
if (!data) $this.data('alert', (data = new Alert(this)))
if (typeof option == 'string') data[option].call($this)
})
}
$.fn.alert.Constructor = Alert
/* ALERT DATA-API
* ============== */
$(function () {
$('body').on('click.alert.data-api', dismiss, Alert.prototype.close)
})
}(window.jQuery);
\ No newline at end of file
...@@ -5,8 +5,11 @@ ...@@ -5,8 +5,11 @@
*/ */
(function($) { (function($) {
var routes = { var routes = {
"/service" : "displayForm", "/instance" : "requestService",
"/service/:id" : "displayData" "/instance/:url" : "showInstance",
"/computers" : "listComputers",
"/instances" : "listInstances",
"/invoices" : "listInvoices"
} }
var router = function(e, d){ var router = function(e, d){
...@@ -22,22 +25,57 @@ ...@@ -22,22 +25,57 @@
}); });
} }
var redirect = function(){
$(this).vifib('render', 'auth', {
'host':'http://192.168.242.64:12002/erp5/web_site_module/hosting/request-access-token',
'client_id': 'client',
'redirect':escape(window.location.href)
})
};
var payment = function(jqxhr){
var message = $.parseJSON(jqXHR.responseText).error
$(this).vifib('popup', message, "information");
};
var notFound = function(jqxhr){
var message = $.parseJSON(jqXHR.responseText).error
$(this).vifib('popup', message);
};
var serverError = function(jqxhr){
var message = $.parseJSON(jqxhr.responseText).error
$(this).vifib('popup', message);
};
var spinOptions = {color: "#FFF", lines:9, length:3, width:2, radius:6, rotate:0, trail:36, speed:1.0};
var methods = { var methods = {
init: function() { init: function() {
// Initialize slapos in this context // Initialize slapos in this context
$(this).slapos({host: '10.0.1.139:12002/erp5/portal_vifib_rest_api_v1'}); $(this).slapos({host: 'http://192.168.242.64:12002/erp5/portal_vifib_rest_api_v1'});
var $this = $(this); var $this = $(this);
// Bind Loading content
$("#loading").ajaxStart(function(){
$(this).spin(spinOptions);
}).ajaxStop(function(){
$(this).spin(false);
});
// Bind to urlChange event // Bind to urlChange event
return this.each(function(){ return this.each(function(){
$.subscribe("urlChange", function(e, d){ $.subscribe("urlChange", function(e, d){
router.call($this, e, d); router.call($this, e, d);
}); });
$.subscribe("auth", function(e, d){ $.subscribe("auth", function(e, d){
$(this).form("authenticate", d); $(this).vifib("authenticate", d);
}); });
}); });
}, },
genInstanceUrl: function(uri){
return location.href.split('#')[0] + "#/instance/" + encodeURIComponent(uri)
},
authenticate: function(data) { authenticate: function(data) {
for (var d in data) { for (var d in data) {
if (data.hasOwnProperty(d)) { if (data.hasOwnProperty(d)) {
...@@ -46,36 +84,65 @@ ...@@ -46,36 +84,65 @@
} }
}, },
displayData: function(id){ showInstance: function (uri) {
var redirect = function(){
$(this).form('render', 'auth', {
'host':'t139:12002/erp5/web_site_module/hosting/request-access-token',
'client_id': 'client',
'redirect':escape(window.location.href)
})
};
var statusCode = { var statusCode = {
401: redirect 401: redirect,
402: payment,
404: notFound,
500: serverError
}; };
$(this).html("<p>Ajax loading...</p>") var $this = $(this);
.slapos('getInstance', id, function(data){ $(this).slapos('instanceInfo', uri, function(infos){
$(this).form('render', 'service', data); $this.vifib('render', 'instance', infos);
}, statusCode); }, statusCode);
}, },
displayForm: function() { listComputers: function(){
$(this).form('render', 'form.new.instance'); $(this).vifib('render', 'server.list');
},
listInstances: function(){
var statusCode = {
401: redirect,
402: payment,
404: notFound,
500: serverError
};
var $this = $(this);
$(this).slapos('instanceList', function(data){
$(this).vifib('render', 'service.list'),
$.each(data['list'], function(){
var url = this+"";
$this.vifib('instanceInfo', url, function(instance){
$.extend(instance, {'url': methods.genInstanceUrl(url)})
$this.vifib('renderAppend', 'service.list.elem', 'service.table', instance);
})
})},
statusCode
);
},
listInvoices: function(){
$(this).vifib('render', 'invoice.list');
},
instanceInfo: function (url, callback) {
$(this).slapos('instanceInfo', url, callback);
},
requestService: function() {
$(this).vifib('render', 'form.new.instance');
var data = {}; var data = {};
$(this).find("form").submit(function(){ $(this).find("form").submit(function(){
$(this).find('input').serializeArray().map(function(elem){ $(this).find('input').serializeArray().map(function(elem){
data[elem["name"]] = elem["value"]; data[elem["name"]] = elem["value"];
}); });
$(this).form('displayAsking', data); $(this).vifib('requestAsking', data);
return false; return false;
}); });
}, },
displayAsking: function(data){ requestAsking: function(data){
var request = { var request = {
software_type: "type_provided_by_the_software", software_type: "type_provided_by_the_software",
slave: false, slave: false,
...@@ -89,38 +156,45 @@ ...@@ -89,38 +156,45 @@
computer_id: "COMP-0", computer_id: "COMP-0",
} }
}; };
var redirect = function(){
$(this).form('render', 'auth', {
'host':'t139:12002/erp5/web_site_module/hosting/request-access-token',
'client_id': 'client',
'redirect':escape(window.location.href)
})
};
var statusCode = { var statusCode = {
401: redirect 401: redirect,
402: payment,
404: notFound,
500: serverError
}; };
$.extend(request, data); $.extend(request, data);
$(this).html("<p>Requesting a new instance "+request["title"]+" ...</p>") $(this).slapos('newInstance', request, function(data){
.slapos('newInstance', request, function(data){
$(this).html(data);}, $(this).html(data);},
statusCode statusCode
); );
}, },
popup: function(message, state) {
state = state || 'error';
return this.each(function(){
$(this).prepend(ich['error']({'message': message, 'state': state}, true))
})
},
render: function(template, data){ render: function(template, data){
$(this).html(ich[template](data, true)); $(this).html(ich[template](data, true));
},
renderAppend: function(template, rootId, data){
rootId = rootId.replace('.', '\\.');
$(this).find('#'+rootId).append(ich[template](data, true));
} }
}; };
$.fn.form = function(method){ $.fn.vifib = function(method){
if ( methods[method] ) { if ( methods[method] ) {
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 )); return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) { } else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments ); return methods.init.apply( this, arguments );
} else { } else {
$.error( 'Method ' + method + ' does not exist on jQuery.form' ); $.error( 'Method ' + method + ' does not exist on jQuery.vifib' );
} }
}; };
})(jQuery); })(jQuery);
$("#main").form(); $("#main").vifib();
/*
$("#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);
...@@ -3,9 +3,6 @@ ...@@ -3,9 +3,6 @@
var methods = { var methods = {
init: function (options) { init: function (options) {
var settings = $.extend({ var settings = $.extend({
'host': '',
'access_token': '',
'clientID': ''
}, options); }, options);
return this.each(function () { return this.each(function () {
...@@ -58,23 +55,22 @@ ...@@ -58,23 +55,22 @@
statusDefault: function () { statusDefault: function () {
return { return {
0: function () { console.error("status error code: 0"); }, 0: function () { console.error("status error code: 0"); },
404: function () { console.error("status error code: Not Found !"); } 404: function () { console.error("status error code: Not Found !"); },
500: function () { console.error("Server error !"); }
}; };
}, },
request: function (type, url, callback, statusCode, data) { request: function (type, url, authentication, callback, statusCode, data) {
data = data || '';
statusCode = statusCode || this.statusDefault;
return this.each(function () { return this.each(function () {
$.ajax({ $.ajax({
url: "http://" + $(this).slapos('host') + url, url: url,
type: type, type: type,
contentType: 'application/octet-stream', contentType: 'application/json',
data: JSON.stringify(data), data: JSON.stringify(data),
dataType: 'json', dataType: 'json',
context: $(this), context: $(this),
beforeSend: function (xhr) { beforeSend: function (xhr) {
if ($(this).slapos("access_token")) { if ($(this).slapos("access_token") && authentication) {
xhr.setRequestHeader("Authorization", $(this).slapos("store", "token_type") + " " + $(this).slapos("access_token")); xhr.setRequestHeader("Authorization", $(this).slapos("store", "token_type") + " " + $(this).slapos("access_token"));
xhr.setRequestHeader("Accept", "application/json"); xhr.setRequestHeader("Accept", "application/json");
} }
...@@ -85,53 +81,48 @@ ...@@ -85,53 +81,48 @@
}); });
}, },
newInstance: function (data, callback, statusEvent) { prepareRequest: function (methodName, callback, statusCode, url, data) {
return $(this).slapos('request', 'POST', '/request', callback, statusEvent, data); data = data || undefined;
}, statusCode = statusCode || methods.statusDefault();
var $this = $(this);
deleteInstance: function (id, callback, statusEvent) { return this.each(function(){
return $(this).slapos('request', 'DELETE', '/instance/' + id, callback, statusEvent); $(this).slapos('discovery', function(access){
}, if (access.hasOwnProperty(methodName)) {
url = url || access[methodName].url;
getInstance: function (id, callback, statusEvent) { $this.slapos('request',
return $(this).slapos('request', 'GET', '/instance/' + id, callback, statusEvent); access[methodName].method,
}, url,
access[methodName].authentication,
getInstanceCert: function (id, callback, statusEvent) { callback,
return $(this).slapos('request', 'GET', '/instance/' + id + '/certificate', callback, statusEvent); statusCode,
}, data);
}
bangInstance: function (id, log, callback, statusEvent) { });
return $(this).slapos('request', 'POST', '/instance/' + id + '/bang', callback, statusEvent, log); })
},
editInstance: function (id, data, callback, statusEvent) {
return $(this).slapos('request', 'PUT', '/instance/' + id, callback, statusEvent, data);
},
newComputer: function (data, callback, statusEvent) {
return $(this).slapos('request', 'POST', '/computer', callback, statusEvent, data);
},
getComputer: function (id, callback, statusEvent) {
return $(this).slapos('request', 'GET', '/computer/' + id, callback, statusEvent);
}, },
editComputer: function (id, data, callback, statusEvent) { discovery: function (callback) {
return $(this).slapos('request', 'PUT', '/computer/' + id, callback, statusEvent, data); return this.each(function(){
$.ajax({
url: "http://192.168.242.64:12002/erp5/portal_vifib_rest_api_v1",
dataType: "json",
beforeSend: function (xhr) {
xhr.setRequestHeader("Accept", "application/json");
}, },
success: callback
newSoftware: function (computerId, data, callback, statusEvent) { });
return $(this).slapos('request', 'POST', '/computer/' + computerId + '/supply', callback, statusEvent, data); });
}, },
bangComputer: function (id, log, callback, statusEvent) { instanceList: function (callback, statusCode) {
return $(this).slapos('request', 'POST', '/computer/' + id + '/bang', callback, statusEvent, log); return $(this).slapos('prepareRequest', 'instance_list', callback, statusCode);
}, },
computerReport: function (id, data, callback, statusEvent) { instanceInfo: function (url, callback, statusCode) {
return $(this).slapos('request', 'POST', '/computer/' + id + '/report', callback, statusEvent, data); url = decodeURIComponent(url);
return $(this).slapos('prepareRequest', 'instance_info', callback, statusCode, url);
} }
}; };
$.fn.slapos = function (method) { $.fn.slapos = function (method) {
......
//fgnass.github.com/spin.js#v1.2.5
(function(window, document, undefined) {
/**
* Copyright (c) 2011 Felix Gnass [fgnass at neteye dot de]
* Licensed under the MIT license
*/
var prefixes = ['webkit', 'Moz', 'ms', 'O']; /* Vendor prefixes */
var animations = {}; /* Animation rules keyed by their name */
var useCssAnimations;
/**
* Utility function to create elements. If no tag name is given,
* a DIV is created. Optionally properties can be passed.
*/
function createEl(tag, prop) {
var el = document.createElement(tag || 'div');
var n;
for(n in prop) {
el[n] = prop[n];
}
return el;
}
/**
* Appends children and returns the parent.
*/
function ins(parent /* child1, child2, ...*/) {
for (var i=1, n=arguments.length; i<n; i++) {
parent.appendChild(arguments[i]);
}
return parent;
}
/**
* Insert a new stylesheet to hold the @keyframe or VML rules.
*/
var sheet = function() {
var el = createEl('style');
ins(document.getElementsByTagName('head')[0], el);
return el.sheet || el.styleSheet;
}();
/**
* Creates an opacity keyframe animation rule and returns its name.
* Since most mobile Webkits have timing issues with animation-delay,
* we create separate rules for each line/segment.
*/
function addAnimation(alpha, trail, i, lines) {
var name = ['opacity', trail, ~~(alpha*100), i, lines].join('-');
var start = 0.01 + i/lines*100;
var z = Math.max(1-(1-alpha)/trail*(100-start) , alpha);
var prefix = useCssAnimations.substring(0, useCssAnimations.indexOf('Animation')).toLowerCase();
var pre = prefix && '-'+prefix+'-' || '';
if (!animations[name]) {
sheet.insertRule(
'@' + pre + 'keyframes ' + name + '{' +
'0%{opacity:'+z+'}' +
start + '%{opacity:'+ alpha + '}' +
(start+0.01) + '%{opacity:1}' +
(start+trail)%100 + '%{opacity:'+ alpha + '}' +
'100%{opacity:'+ z + '}' +
'}', 0);
animations[name] = 1;
}
return name;
}
/**
* Tries various vendor prefixes and returns the first supported property.
**/
function vendor(el, prop) {
var s = el.style;
var pp;
var i;
if(s[prop] !== undefined) return prop;
prop = prop.charAt(0).toUpperCase() + prop.slice(1);
for(i=0; i<prefixes.length; i++) {
pp = prefixes[i]+prop;
if(s[pp] !== undefined) return pp;
}
}
/**
* Sets multiple style properties at once.
*/
function css(el, prop) {
for (var n in prop) {
el.style[vendor(el, n)||n] = prop[n];
}
return el;
}
/**
* Fills in default values.
*/
function merge(obj) {
for (var i=1; i < arguments.length; i++) {
var def = arguments[i];
for (var n in def) {
if (obj[n] === undefined) obj[n] = def[n];
}
}
return obj;
}
/**
* Returns the absolute page-offset of the given element.
*/
function pos(el) {
var o = {x:el.offsetLeft, y:el.offsetTop};
while((el = el.offsetParent)) {
o.x+=el.offsetLeft;
o.y+=el.offsetTop;
}
return o;
}
var defaults = {
lines: 12, // The number of lines to draw
length: 7, // The length of each line
width: 5, // The line thickness
radius: 10, // The radius of the inner circle
rotate: 0, // rotation offset
color: '#000', // #rgb or #rrggbb
speed: 1, // Rounds per second
trail: 100, // Afterglow percentage
opacity: 1/4, // Opacity of the lines
fps: 20, // Frames per second when using setTimeout()
zIndex: 2e9, // Use a high z-index by default
className: 'spinner', // CSS class to assign to the element
top: 'auto', // center vertically
left: 'auto' // center horizontally
};
/** The constructor */
var Spinner = function Spinner(o) {
if (!this.spin) return new Spinner(o);
this.opts = merge(o || {}, Spinner.defaults, defaults);
};
Spinner.defaults = {};
merge(Spinner.prototype, {
spin: function(target) {
this.stop();
var self = this;
var o = self.opts;
var el = self.el = css(createEl(0, {className: o.className}), {position: 'relative', zIndex: o.zIndex});
var mid = o.radius+o.length+o.width;
var ep; // element position
var tp; // target position
if (target) {
target.insertBefore(el, target.firstChild||null);
tp = pos(target);
ep = pos(el);
css(el, {
left: (o.left == 'auto' ? tp.x-ep.x + (target.offsetWidth >> 1) : o.left+mid) + 'px',
top: (o.top == 'auto' ? tp.y-ep.y + (target.offsetHeight >> 1) : o.top+mid) + 'px'
});
}
el.setAttribute('aria-role', 'progressbar');
self.lines(el, self.opts);
if (!useCssAnimations) {
// No CSS animation support, use setTimeout() instead
var i = 0;
var fps = o.fps;
var f = fps/o.speed;
var ostep = (1-o.opacity)/(f*o.trail / 100);
var 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;
var 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.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;
var 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';
var g = css(grp(), {position: 'absolute', top: margin, left: margin});
var i;
function seg(i, dx, filter) {
ins(g,
ins(css(grp(), {rotation: 360 / o.lines * i + 'deg', left: ~~dx}),
ins(css(vml('roundrect', {arcsize: 1}), {
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');
}
}();
window.Spinner = Spinner;
})(window, document);
...@@ -10,7 +10,8 @@ $.parseHash = function(hashTag) { ...@@ -10,7 +10,8 @@ $.parseHash = function(hashTag) {
var tokenized = $.extractAuth(hashTag); var tokenized = $.extractAuth(hashTag);
if (tokenized) { if (tokenized) {
$.publish('auth', tokenized); $.publish('auth', tokenized);
return hashTag.split("&")[0] location.hash = hashTag.split("&")[0]
return location.hash
} }
return hashTag return hashTag
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment