Tooltips
This commit is contained in:
parent
eef09e5839
commit
4cf5383d0d
|
@ -3,3 +3,4 @@
|
||||||
.*.swp
|
.*.swp
|
||||||
.*.swo
|
.*.swo
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
|
dnmapper/settings.py
|
||||||
|
|
|
@ -84,6 +84,10 @@ class AS(models.Model):
|
||||||
def getPeerings(self):
|
def getPeerings(self):
|
||||||
return Peering.objects.filter(Q(as1=self)|Q(as2=self))
|
return Peering.objects.filter(Q(as1=self)|Q(as2=self))
|
||||||
|
|
||||||
|
def formatLastSeen(self):
|
||||||
|
if self.lastSeen:
|
||||||
|
return self.lastSeen.startTime.strftime("%d.%m.%Y %H:%I")
|
||||||
|
|
||||||
class BorderRouter(models.Model):
|
class BorderRouter(models.Model):
|
||||||
# as id, ip, check method, pingable, reachable
|
# as id, ip, check method, pingable, reachable
|
||||||
# unique: (crawl_id, asno, as id)
|
# unique: (crawl_id, asno, as id)
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
var asdata = [
|
var asdata = [
|
||||||
{% for AS in ASses %}
|
{% for AS in ASses %}
|
||||||
{id: {{AS.pk}}, nodetype: "AS", asnumber: {{AS.number}}, label: "{{AS.number}}", neighbors: {{AS.getPeerings.count}}, crawled: {%if AS.directlyCrawled%}true{%else%}false{%endif%}, online: {%if AS.online%}true{%else%}false{%endif%}, lastSeenDate: "{{AS.lastSeen}}", lastSeen: {%if AS.lastSeen%}{{AS.lastSeen.pk}}{%else%}null{%endif%}}{%if not forloop.last%},{%endif%}
|
{id: {{AS.pk}}, nodetype: "AS", asnumber: {{AS.number}}, label: "{{AS.number}}", neighbors: {{AS.getPeerings.count}}, crawled: {%if AS.directlyCrawled%}true{%else%}false{%endif%}, online: {%if AS.online%}true{%else%}false{%endif%}, lastSeenDate: "{{AS.formatLastSeen}}", lastSeen: {%if AS.lastSeen%}{{AS.lastSeen.pk}}{%else%}null{%endif%}, dismiss: true}{%if not forloop.last%},{%endif%}
|
||||||
{%endfor%}
|
{%endfor%}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -63,7 +63,6 @@ var svg = d3.select('#plotwin')
|
||||||
.attr('width', width)
|
.attr('width', width)
|
||||||
.attr('height', height);
|
.attr('height', height);
|
||||||
|
|
||||||
console.log(asdata);
|
|
||||||
var force = d3.layout.force()
|
var force = d3.layout.force()
|
||||||
.nodes(asdata)
|
.nodes(asdata)
|
||||||
.links(peerings)
|
.links(peerings)
|
||||||
|
@ -72,9 +71,7 @@ var force = d3.layout.force()
|
||||||
// .linkDistance(70)
|
// .linkDistance(70)
|
||||||
.linkStrength(0.65)
|
.linkStrength(0.65)
|
||||||
.linkDistance(function(l) {
|
.linkDistance(function(l) {
|
||||||
console.log(l);
|
|
||||||
neighs = Math.min(l.source.neighbors, l.target.neighbors);
|
neighs = Math.min(l.source.neighbors, l.target.neighbors);
|
||||||
console.log(neighs, "neighbors");
|
|
||||||
switch(neighs) {
|
switch(neighs) {
|
||||||
case 0: return 40;
|
case 0: return 40;
|
||||||
case 1:
|
case 1:
|
||||||
|
@ -103,6 +100,7 @@ var node = svg.selectAll('.node')
|
||||||
.data(asdata)
|
.data(asdata)
|
||||||
.enter()
|
.enter()
|
||||||
.append("g")
|
.append("g")
|
||||||
|
.attr("id", function(d) { return "node-" + d.id; })
|
||||||
.call(force.drag);
|
.call(force.drag);
|
||||||
|
|
||||||
node.append("ellipse")
|
node.append("ellipse")
|
||||||
|
@ -120,6 +118,7 @@ node.append("ellipse")
|
||||||
.attr("stroke", "black")
|
.attr("stroke", "black")
|
||||||
.attr("stroke-width", "1px");
|
.attr("stroke-width", "1px");
|
||||||
|
|
||||||
|
|
||||||
node.append('text')
|
node.append('text')
|
||||||
.attr("font-family", "sans-serif")
|
.attr("font-family", "sans-serif")
|
||||||
.attr("font-size", "13px")
|
.attr("font-size", "13px")
|
||||||
|
@ -128,6 +127,57 @@ node.append('text')
|
||||||
.attr("text-anchor", "middle")
|
.attr("text-anchor", "middle")
|
||||||
.text(function(d) { return d.label; });
|
.text(function(d) { return d.label; });
|
||||||
|
|
||||||
|
var lastMouseNode = null;
|
||||||
|
node.on("mouseover", function(d) {
|
||||||
|
if(lastMouseNode) {
|
||||||
|
if(!d.dismiss && lastMouseNode.id == d.id)
|
||||||
|
return;
|
||||||
|
else
|
||||||
|
$("#node-"+lastMouseNode.id).tipsy("hide");
|
||||||
|
}
|
||||||
|
|
||||||
|
d.dismiss = true;
|
||||||
|
$("#node-"+d.id).tipsy("show");
|
||||||
|
|
||||||
|
lastMouseNode = d;
|
||||||
|
})
|
||||||
|
.on("mouseout", function(d) {
|
||||||
|
if(d.dismiss)
|
||||||
|
$("#node-"+d.id).tipsy("hide");
|
||||||
|
});
|
||||||
|
|
||||||
|
$('svg g').tipsy({
|
||||||
|
gravity: 'e',
|
||||||
|
offset: 20,
|
||||||
|
html: true,
|
||||||
|
trigger: 'manual',
|
||||||
|
title: function() {
|
||||||
|
var d = this.__data__;
|
||||||
|
var content = '';
|
||||||
|
if(d.nodetype == 'AS') {
|
||||||
|
var state = null;
|
||||||
|
if(d.online)
|
||||||
|
state = '<span class="label label-success">Online</span>';
|
||||||
|
else if(d.lastSeen)
|
||||||
|
state = '<span class="label label-danger">Offline</span>';
|
||||||
|
else
|
||||||
|
state = '<span class="label label-default">Never seen</span>';
|
||||||
|
|
||||||
|
content = '<b>AS '+d.asnumber+'</b><br>';
|
||||||
|
content += '<table style="opacity: 1.0" class="table table-bordered">';
|
||||||
|
content += '<tr><th>State</th><td>'+state+'</td>';
|
||||||
|
if(d.lastSeen) {
|
||||||
|
content += '<tr><th>Last seen</th><td><a href="/map/'+d.lastSeen+'/">Crawl '+d.lastSeen+'<br>'+d.lastSeenDate+'</a></td></tr>';
|
||||||
|
}
|
||||||
|
if(d.crawled) {
|
||||||
|
content += '<tr><th>Note</th><td>Directly crawled</td></tr>';
|
||||||
|
}
|
||||||
|
content += '</table>';
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
force.on("tick", function() {
|
force.on("tick", function() {
|
||||||
//node.attr('cx', function(d) { return d.x; })
|
//node.attr('cx', function(d) { return d.x; })
|
||||||
// .attr('cy', function(d) { return d.y; });
|
// .attr('cy', function(d) { return d.y; });
|
||||||
|
@ -139,29 +189,47 @@ force.on("tick", function() {
|
||||||
.attr("y2", function(d) { return d.target.y; });
|
.attr("y2", function(d) { return d.target.y; });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
var lastNode = null;
|
var lastNode = null;
|
||||||
function click(d) {
|
function click(d) {
|
||||||
if(d3.event.defaultPrevented) return;
|
if(d3.event.defaultPrevented) return;
|
||||||
if(lastNode)
|
|
||||||
console.log("last time you clicked on another node", d);
|
//if(lastNode)
|
||||||
|
// $("#node-" + lastNode.id).tipsy("hide");
|
||||||
|
d.dismiss = false;
|
||||||
|
//$("#node-" + d.id).tipsy("show");
|
||||||
|
|
||||||
|
|
||||||
$("#infowin").fadeOut('fast', function() {
|
$("#infowin").fadeOut('fast', function() {
|
||||||
// set progress bar
|
// set progress bar
|
||||||
$("#infowin").html('<div class="progress"><div class="progress-bar progress-bar-striped active" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%"><span class="sr-only">Loading...</span></div></div>');
|
$("#infowin").html('<div class="progress"><div class="progress-bar progress-bar-striped active" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%"><span class="sr-only">Loading...</span></div></div>');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
console.log("fading done");
|
|
||||||
var content = '';
|
var content = '';
|
||||||
if(d.nodetype == 'AS' && d.crawled) {
|
if(d.nodetype == 'AS') {
|
||||||
console.log("doing javascript...");
|
//$("#node-"+d.id).popover({content: "Hallo Katze! :)"});
|
||||||
|
//$("#node-"+d.id).popover('show');
|
||||||
|
//console.log("My element", $("#node-"+d.id));
|
||||||
|
//$("#node-"+d.id).tipsy({
|
||||||
|
// gravity: 'e',
|
||||||
|
// html: true,
|
||||||
|
// //trigger: 'focus',
|
||||||
|
// trigger: 'hover',
|
||||||
|
// title: function() {
|
||||||
|
// var d = this.__data__;
|
||||||
|
// astable = '<table class="table table-bordered"><tr><td>FOOO</td><td>bar</td></table>';
|
||||||
|
// return astable;
|
||||||
|
// }
|
||||||
|
//});
|
||||||
|
|
||||||
|
if(d.crawled) {
|
||||||
$.ajax({url: "/map/api/borderrouter/?AS__crawl={{crawl.pk}}&AS__number=" + d.asnumber, success: function(result) {
|
$.ajax({url: "/map/api/borderrouter/?AS__crawl={{crawl.pk}}&AS__number=" + d.asnumber, success: function(result) {
|
||||||
console.log("SUCCESS", result);
|
|
||||||
$("#infowin").html('');
|
$("#infowin").html('');
|
||||||
$("#infowin").fadeIn('fast', function() {});
|
$("#infowin").fadeIn('fast', function() {});
|
||||||
for(var i=0; i<result.objects.length; i++) {
|
for(var i=0; i<result.objects.length; i++) {
|
||||||
(function(currRouter) {
|
(function(currRouter) {
|
||||||
$.ajax({url:"/map/api/announcement/?router="+currRouter.id, success: function(result2) {
|
$.ajax({url:"/map/api/announcement/?router="+currRouter.id, success: function(result2) {
|
||||||
console.log("Current router on success", currRouter);
|
|
||||||
astable = 'Table for AS'+d.asnumber+' router ID ' + currRouter.routerID;
|
astable = 'Table for AS'+d.asnumber+' router ID ' + currRouter.routerID;
|
||||||
astable += '<table class="table table-bordered">';
|
astable += '<table class="table table-bordered">';
|
||||||
astable += '<tr><th>Network</th><th>Next Hop</th><th>AS Path</th></tr>';
|
astable += '<tr><th>Network</th><th>Next Hop</th><th>AS Path</th></tr>';
|
||||||
|
@ -170,20 +238,13 @@ function click(d) {
|
||||||
astable += '<tr><td>'+ann.ip+'/'+ann.prefix+'</td><td>'+ann.nextHop+'</td><td>'+ann.ASPath+'</td></tr>';
|
astable += '<tr><td>'+ann.ip+'/'+ann.prefix+'</td><td>'+ann.nextHop+'</td><td>'+ann.ASPath+'</td></tr>';
|
||||||
}
|
}
|
||||||
astable += '</table>';
|
astable += '</table>';
|
||||||
console.log("Sending stuff for", currRouter.id, "aka", currRouter.routerID);
|
|
||||||
$('#infowin').append(astable);
|
$('#infowin').append(astable);
|
||||||
}});
|
}});
|
||||||
})(result.objects[i]);
|
})(result.objects[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
}});
|
}});
|
||||||
astable = '<table class="table table-bordered table-hover">';
|
}
|
||||||
astable += '<tr><th scope="row">AS</th><td>'+ d.asnumber +'<td></tr>';
|
|
||||||
astable += '<tr><th>Directly crawled</th><td><td></tr>';
|
|
||||||
astable += '<tr><th>Online</th><td><td></tr>';
|
|
||||||
astable += '<tr><th></th><td><td></tr>';
|
|
||||||
|
|
||||||
astable += '</table><div id="announcement"></div>';
|
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -191,10 +252,10 @@ function click(d) {
|
||||||
$("#infowin").fadeIn('fast', function() {});
|
$("#infowin").fadeIn('fast', function() {});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.log("Single click on", d);
|
|
||||||
lastNode = d;
|
lastNode = d;
|
||||||
}
|
}
|
||||||
node.on("click", click)
|
node.on("click", click);
|
||||||
|
|
||||||
|
|
||||||
force.start();
|
force.start();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
"""
|
||||||
|
Django settings for dnmapper project.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/1.7/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/1.7/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
|
import os
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = 'crv*hfx2pkxvq1s!)dbz*hdu+r7u2$y4djf6_#6mm)shk9e!58'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
TEMPLATE_DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
STATICFILES_DIRS = (
|
||||||
|
'static/',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = (
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'bgpdata',
|
||||||
|
'tastypie',
|
||||||
|
)
|
||||||
|
|
||||||
|
API_LIMIT_PER_PAGE = 100
|
||||||
|
|
||||||
|
MIDDLEWARE_CLASSES = (
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
)
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'dnmapper.urls'
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'dnmapper.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/1.7/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'de-de'
|
||||||
|
|
||||||
|
TIME_ZONE = 'CET'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
TEMPLATE_DIRS = (
|
||||||
|
'templates/',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/1.7/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = '/static/'
|
|
@ -0,0 +1,25 @@
|
||||||
|
.tipsy { font-size: 14px; position: absolute; padding: 5px; z-index: 100000; }
|
||||||
|
.tipsy-inner { background-color: #000; color: #FFF; max-width: 200px; padding: 5px 8px 4px 8px; text-align: center; }
|
||||||
|
|
||||||
|
/* Rounded corners */
|
||||||
|
.tipsy-inner { border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; }
|
||||||
|
|
||||||
|
/* Uncomment for shadow */
|
||||||
|
/*.tipsy-inner { box-shadow: 0 0 5px #000000; -webkit-box-shadow: 0 0 5px #000000; -moz-box-shadow: 0 0 5px #000000; }*/
|
||||||
|
|
||||||
|
.tipsy-arrow { position: absolute; width: 0; height: 0; line-height: 0; border: 5px dashed #000; }
|
||||||
|
|
||||||
|
/* Rules to colour arrows */
|
||||||
|
.tipsy-arrow-n { border-bottom-color: #000; }
|
||||||
|
.tipsy-arrow-s { border-top-color: #000; }
|
||||||
|
.tipsy-arrow-e { border-left-color: #000; }
|
||||||
|
.tipsy-arrow-w { border-right-color: #000; }
|
||||||
|
|
||||||
|
.tipsy-n .tipsy-arrow { top: 0px; left: 50%; margin-left: -5px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent; }
|
||||||
|
.tipsy-nw .tipsy-arrow { top: 0; left: 10px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent;}
|
||||||
|
.tipsy-ne .tipsy-arrow { top: 0; right: 10px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent;}
|
||||||
|
.tipsy-s .tipsy-arrow { bottom: 0; left: 50%; margin-left: -5px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; }
|
||||||
|
.tipsy-sw .tipsy-arrow { bottom: 0; left: 10px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; }
|
||||||
|
.tipsy-se .tipsy-arrow { bottom: 0; right: 10px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; }
|
||||||
|
.tipsy-e .tipsy-arrow { right: 0; top: 50%; margin-top: -5px; border-left-style: solid; border-right: none; border-top-color: transparent; border-bottom-color: transparent; }
|
||||||
|
.tipsy-w .tipsy-arrow { left: 0; top: 50%; margin-top: -5px; border-right-style: solid; border-left: none; border-top-color: transparent; border-bottom-color: transparent; }
|
|
@ -0,0 +1,260 @@
|
||||||
|
// tipsy, facebook style tooltips for jquery
|
||||||
|
// version 1.0.0a
|
||||||
|
// (c) 2008-2010 jason frame [jason@onehackoranother.com]
|
||||||
|
// released under the MIT license
|
||||||
|
|
||||||
|
(function($) {
|
||||||
|
|
||||||
|
function maybeCall(thing, ctx) {
|
||||||
|
return (typeof thing == 'function') ? (thing.call(ctx)) : thing;
|
||||||
|
};
|
||||||
|
|
||||||
|
function isElementInDOM(ele) {
|
||||||
|
while (ele = ele.parentNode) {
|
||||||
|
if (ele == document) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
function Tipsy(element, options) {
|
||||||
|
this.$element = $(element);
|
||||||
|
this.options = options;
|
||||||
|
this.enabled = true;
|
||||||
|
this.fixTitle();
|
||||||
|
};
|
||||||
|
|
||||||
|
Tipsy.prototype = {
|
||||||
|
show: function() {
|
||||||
|
var title = this.getTitle();
|
||||||
|
if (title && this.enabled) {
|
||||||
|
var $tip = this.tip();
|
||||||
|
|
||||||
|
$tip.find('.tipsy-inner')[this.options.html ? 'html' : 'text'](title);
|
||||||
|
$tip[0].className = 'tipsy'; // reset classname in case of dynamic gravity
|
||||||
|
$tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).prependTo(document.body);
|
||||||
|
|
||||||
|
var pos = $.extend({}, this.$element.offset(), {
|
||||||
|
width: this.$element[0].offsetWidth,
|
||||||
|
height: this.$element[0].offsetHeight
|
||||||
|
});
|
||||||
|
|
||||||
|
var actualWidth = $tip[0].offsetWidth,
|
||||||
|
actualHeight = $tip[0].offsetHeight,
|
||||||
|
gravity = maybeCall(this.options.gravity, this.$element[0]);
|
||||||
|
|
||||||
|
var tp;
|
||||||
|
switch (gravity.charAt(0)) {
|
||||||
|
case 'n':
|
||||||
|
tp = {top: pos.top + pos.height + this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
tp = {top: pos.top - actualHeight - this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
|
||||||
|
break;
|
||||||
|
case 'e':
|
||||||
|
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth - this.options.offset};
|
||||||
|
// XXX HACK: i want to set another offset so apparently I have to hardcode this into the code. obviously.
|
||||||
|
tp = {top: pos.top + pos.height / 2 - actualHeight / 2 + this.options.offset, left: pos.left - actualWidth};
|
||||||
|
break;
|
||||||
|
case 'w':
|
||||||
|
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width + this.options.offset};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gravity.length == 2) {
|
||||||
|
if (gravity.charAt(1) == 'w') {
|
||||||
|
tp.left = pos.left + pos.width / 2 - 15;
|
||||||
|
} else {
|
||||||
|
tp.left = pos.left + pos.width / 2 - actualWidth + 15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$tip.css(tp).addClass('tipsy-' + gravity);
|
||||||
|
$tip.find('.tipsy-arrow')[0].className = 'tipsy-arrow tipsy-arrow-' + gravity.charAt(0);
|
||||||
|
if (this.options.className) {
|
||||||
|
$tip.addClass(maybeCall(this.options.className, this.$element[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options.fade) {
|
||||||
|
$tip.stop().css({opacity: 0, display: 'block', visibility: 'visible'}).animate({opacity: this.options.opacity});
|
||||||
|
} else {
|
||||||
|
$tip.css({visibility: 'visible', opacity: this.options.opacity});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
hide: function() {
|
||||||
|
if (this.options.fade) {
|
||||||
|
this.tip().stop().fadeOut(function() { $(this).remove(); });
|
||||||
|
} else {
|
||||||
|
this.tip().remove();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
fixTitle: function() {
|
||||||
|
var $e = this.$element;
|
||||||
|
if ($e.attr('title') || typeof($e.attr('original-title')) != 'string') {
|
||||||
|
$e.attr('original-title', $e.attr('title') || '').removeAttr('title');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getTitle: function() {
|
||||||
|
var title, $e = this.$element, o = this.options;
|
||||||
|
this.fixTitle();
|
||||||
|
var title, o = this.options;
|
||||||
|
if (typeof o.title == 'string') {
|
||||||
|
title = $e.attr(o.title == 'title' ? 'original-title' : o.title);
|
||||||
|
} else if (typeof o.title == 'function') {
|
||||||
|
title = o.title.call($e[0]);
|
||||||
|
}
|
||||||
|
title = ('' + title).replace(/(^\s*|\s*$)/, "");
|
||||||
|
return title || o.fallback;
|
||||||
|
},
|
||||||
|
|
||||||
|
tip: function() {
|
||||||
|
if (!this.$tip) {
|
||||||
|
this.$tip = $('<div class="tipsy"></div>').html('<div class="tipsy-arrow"></div><div class="tipsy-inner"></div>');
|
||||||
|
this.$tip.data('tipsy-pointee', this.$element[0]);
|
||||||
|
}
|
||||||
|
return this.$tip;
|
||||||
|
},
|
||||||
|
|
||||||
|
validate: function() {
|
||||||
|
if (!this.$element[0].parentNode) {
|
||||||
|
this.hide();
|
||||||
|
this.$element = null;
|
||||||
|
this.options = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
enable: function() { this.enabled = true; },
|
||||||
|
disable: function() { this.enabled = false; },
|
||||||
|
toggleEnabled: function() { this.enabled = !this.enabled; }
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.tipsy = function(options) {
|
||||||
|
|
||||||
|
if (options === true) {
|
||||||
|
return this.data('tipsy');
|
||||||
|
} else if (typeof options == 'string') {
|
||||||
|
var tipsy = this.data('tipsy');
|
||||||
|
if (tipsy) tipsy[options]();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
options = $.extend({}, $.fn.tipsy.defaults, options);
|
||||||
|
|
||||||
|
function get(ele) {
|
||||||
|
var tipsy = $.data(ele, 'tipsy');
|
||||||
|
if (!tipsy) {
|
||||||
|
tipsy = new Tipsy(ele, $.fn.tipsy.elementOptions(ele, options));
|
||||||
|
$.data(ele, 'tipsy', tipsy);
|
||||||
|
}
|
||||||
|
return tipsy;
|
||||||
|
}
|
||||||
|
|
||||||
|
function enter() {
|
||||||
|
var tipsy = get(this);
|
||||||
|
tipsy.hoverState = 'in';
|
||||||
|
if (options.delayIn == 0) {
|
||||||
|
tipsy.show();
|
||||||
|
} else {
|
||||||
|
tipsy.fixTitle();
|
||||||
|
setTimeout(function() { if (tipsy.hoverState == 'in') tipsy.show(); }, options.delayIn);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function leave() {
|
||||||
|
var tipsy = get(this);
|
||||||
|
tipsy.hoverState = 'out';
|
||||||
|
if (options.delayOut == 0) {
|
||||||
|
tipsy.hide();
|
||||||
|
} else {
|
||||||
|
setTimeout(function() { if (tipsy.hoverState == 'out') tipsy.hide(); }, options.delayOut);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!options.live) this.each(function() { get(this); });
|
||||||
|
|
||||||
|
if (options.trigger != 'manual') {
|
||||||
|
var binder = options.live ? 'live' : 'bind',
|
||||||
|
eventIn = options.trigger == 'hover' ? 'mouseenter' : 'focus',
|
||||||
|
eventOut = options.trigger == 'hover' ? 'mouseleave' : 'blur';
|
||||||
|
this[binder](eventIn, enter)[binder](eventOut, leave);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.tipsy.defaults = {
|
||||||
|
className: null,
|
||||||
|
delayIn: 0,
|
||||||
|
delayOut: 0,
|
||||||
|
fade: false,
|
||||||
|
fallback: '',
|
||||||
|
gravity: 'n',
|
||||||
|
html: false,
|
||||||
|
live: false,
|
||||||
|
offset: 0,
|
||||||
|
opacity: 0.8,
|
||||||
|
title: 'title',
|
||||||
|
trigger: 'hover'
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.tipsy.revalidate = function() {
|
||||||
|
$('.tipsy').each(function() {
|
||||||
|
var pointee = $.data(this, 'tipsy-pointee');
|
||||||
|
if (!pointee || !isElementInDOM(pointee)) {
|
||||||
|
$(this).remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Overwrite this method to provide options on a per-element basis.
|
||||||
|
// For example, you could store the gravity in a 'tipsy-gravity' attribute:
|
||||||
|
// return $.extend({}, options, {gravity: $(ele).attr('tipsy-gravity') || 'n' });
|
||||||
|
// (remember - do not modify 'options' in place!)
|
||||||
|
$.fn.tipsy.elementOptions = function(ele, options) {
|
||||||
|
return $.metadata ? $.extend({}, options, $(ele).metadata()) : options;
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.tipsy.autoNS = function() {
|
||||||
|
return $(this).offset().top > ($(document).scrollTop() + $(window).height() / 2) ? 's' : 'n';
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.tipsy.autoWE = function() {
|
||||||
|
return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'e' : 'w';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* yields a closure of the supplied parameters, producing a function that takes
|
||||||
|
* no arguments and is suitable for use as an autogravity function like so:
|
||||||
|
*
|
||||||
|
* @param margin (int) - distance from the viewable region edge that an
|
||||||
|
* element should be before setting its tooltip's gravity to be away
|
||||||
|
* from that edge.
|
||||||
|
* @param prefer (string, e.g. 'n', 'sw', 'w') - the direction to prefer
|
||||||
|
* if there are no viewable region edges effecting the tooltip's
|
||||||
|
* gravity. It will try to vary from this minimally, for example,
|
||||||
|
* if 'sw' is preferred and an element is near the right viewable
|
||||||
|
* region edge, but not the top edge, it will set the gravity for
|
||||||
|
* that element's tooltip to be 'se', preserving the southern
|
||||||
|
* component.
|
||||||
|
*/
|
||||||
|
$.fn.tipsy.autoBounds = function(margin, prefer) {
|
||||||
|
return function() {
|
||||||
|
var dir = {ns: prefer[0], ew: (prefer.length > 1 ? prefer[1] : false)},
|
||||||
|
boundTop = $(document).scrollTop() + margin,
|
||||||
|
boundLeft = $(document).scrollLeft() + margin,
|
||||||
|
$this = $(this);
|
||||||
|
|
||||||
|
if ($this.offset().top < boundTop) dir.ns = 'n';
|
||||||
|
if ($this.offset().left < boundLeft) dir.ew = 'w';
|
||||||
|
if ($(window).width() + $(document).scrollLeft() - $this.offset().left < margin) dir.ew = 'e';
|
||||||
|
if ($(window).height() + $(document).scrollTop() - $this.offset().top < margin) dir.ns = 's';
|
||||||
|
|
||||||
|
return dir.ns + (dir.ew ? dir.ew : '');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
})(jQuery);
|
|
@ -4,10 +4,12 @@
|
||||||
<head>
|
<head>
|
||||||
<link href="{% static "css/bootstrap.min.css" %}" rel="stylesheet">
|
<link href="{% static "css/bootstrap.min.css" %}" rel="stylesheet">
|
||||||
<link href="{% static "css/main.css" %}" rel="stylesheet">
|
<link href="{% static "css/main.css" %}" rel="stylesheet">
|
||||||
|
<link href="{% static "css/tipsy.css" %}" rel="stylesheet">
|
||||||
{% block head %}{% endblock %}
|
{% block head %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script src="{% static "js/jquery.min.js" %}"></script>
|
<script src="{% static "js/jquery.min.js" %}"></script>
|
||||||
|
<script src="{% static "js/jquery.tipsy.js" %}"></script>
|
||||||
<script src="{% static "js/bootstrap.min.js" %}"></script>
|
<script src="{% static "js/bootstrap.min.js" %}"></script>
|
||||||
|
|
||||||
<!-- Static navbar -->
|
<!-- Static navbar -->
|
||||||
|
|
Loading…
Reference in New Issue