mirror of
https://github.com/nikdoof/limetime.git
synced 2025-12-13 09:42:26 +00:00
Basic styling, lots of feature work
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
.idea
|
.idea
|
||||||
*.db3
|
*.db3
|
||||||
|
*.pyc
|
||||||
|
|||||||
1109
app/limetime/static/css/bootstrap-responsive.css
vendored
Normal file
1109
app/limetime/static/css/bootstrap-responsive.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
9
app/limetime/static/css/bootstrap-responsive.min.css
vendored
Normal file
9
app/limetime/static/css/bootstrap-responsive.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
6158
app/limetime/static/css/bootstrap.css
vendored
Normal file
6158
app/limetime/static/css/bootstrap.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
9
app/limetime/static/css/bootstrap.min.css
vendored
Normal file
9
app/limetime/static/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
app/limetime/static/img/glyphicons-halflings-white.png
Normal file
BIN
app/limetime/static/img/glyphicons-halflings-white.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.6 KiB |
BIN
app/limetime/static/img/glyphicons-halflings.png
Normal file
BIN
app/limetime/static/img/glyphicons-halflings.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
2276
app/limetime/static/js/bootstrap.js
vendored
Normal file
2276
app/limetime/static/js/bootstrap.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
6
app/limetime/static/js/bootstrap.min.js
vendored
Normal file
6
app/limetime/static/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
48
app/limetime/templates/base.html
Normal file
48
app/limetime/templates/base.html
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
{% load staticfiles %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Timer</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="description" content="">
|
||||||
|
<meta name="author" content="">
|
||||||
|
<link href="{% static "css/bootstrap.css" %}" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<link href="{% static "css/bootstrap-responsive.css" %}" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="navbar navbar-inverse navbar-fixed-top">
|
||||||
|
<div class="navbar-inner">
|
||||||
|
<div class="container">
|
||||||
|
<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
</button>
|
||||||
|
<a class="brand" href="#">Timer</a>
|
||||||
|
<div class="nav-collapse collapse">
|
||||||
|
<ul class="nav">
|
||||||
|
<li class="active"><a href="{% url "timer-list" %}">Timers</a></li>
|
||||||
|
<li class=""><a href="{% url "timer-create" %}">Add</a></li>
|
||||||
|
</ul>
|
||||||
|
</div><!--/.nav-collapse -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
|
||||||
|
{% block scripts %}
|
||||||
|
{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
10
app/timer/forms.py
Normal file
10
app/timer/forms.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from django import forms
|
||||||
|
from timer.models import Timer, Location
|
||||||
|
|
||||||
|
|
||||||
|
class TimerForm(forms.ModelForm):
|
||||||
|
|
||||||
|
location = forms.ModelChoiceField(Location.objects.none())
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Timer
|
||||||
@@ -3,6 +3,16 @@ from .utils import InheritanceQuerySet
|
|||||||
from .owners import Corporation
|
from .owners import Corporation
|
||||||
|
|
||||||
class LocationManager(models.Manager):
|
class LocationManager(models.Manager):
|
||||||
|
use_for_related_fields = True
|
||||||
|
|
||||||
|
def get_query_set(self):
|
||||||
|
return InheritanceQuerySet(self.model)
|
||||||
|
|
||||||
|
def select_subclasses(self, *subclasses):
|
||||||
|
return self.get_query_set().select_subclasses(*subclasses)
|
||||||
|
|
||||||
|
def get_subclass(self, *args, **kwargs):
|
||||||
|
return self.get_query_set().select_subclasses().get(*args, **kwargs)
|
||||||
|
|
||||||
def all_subclassed(self):
|
def all_subclassed(self):
|
||||||
return InheritanceQuerySet(model=self.model).select_subclasses()
|
return InheritanceQuerySet(model=self.model).select_subclasses()
|
||||||
@@ -15,8 +25,17 @@ class Location(models.Model):
|
|||||||
y = models.BigIntegerField('Y Location', null=True)
|
y = models.BigIntegerField('Y Location', null=True)
|
||||||
z = models.BigIntegerField('Z Location', null=True)
|
z = models.BigIntegerField('Z Location', null=True)
|
||||||
|
|
||||||
|
objects = LocationManager()
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return "%(name)s (%(id)d)" % self.__dict__
|
return "%(name)s" % self.__dict__
|
||||||
|
|
||||||
|
def get_subclass(self):
|
||||||
|
return Location.objects.get_subclass(pk=self.pk)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_type(self):
|
||||||
|
return self.get_subclass().__class__.__name__
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
app_label = 'timer'
|
app_label = 'timer'
|
||||||
@@ -77,6 +96,10 @@ class Planet(Location):
|
|||||||
class Moon(Location):
|
class Moon(Location):
|
||||||
planet = models.ForeignKey(Planet, related_name='moons')
|
planet = models.ForeignKey(Planet, related_name='moons')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def system(self):
|
||||||
|
return self.planet.system
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
app_label = 'timer'
|
app_label = 'timer'
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ class Timer(models.Model):
|
|||||||
(STATE_EXPIRED, 'Expired'),
|
(STATE_EXPIRED, 'Expired'),
|
||||||
)
|
)
|
||||||
|
|
||||||
TYPE_SHEILD_REENFORCEMENT = 1
|
TYPE_SHIELD_REENFORCEMENT = 1
|
||||||
TYPE_ARMOR_REENFORCEMENT = 2
|
TYPE_ARMOR_REENFORCEMENT = 2
|
||||||
|
|
||||||
TYPE_CHOICES = (
|
TYPE_CHOICES = (
|
||||||
(TYPE_SHEILD_REENFORCEMENT, 'Sheild Reenforcement'),
|
(TYPE_SHIELD_REENFORCEMENT, 'Shield Reenforcement'),
|
||||||
(TYPE_ARMOR_REENFORCEMENT, 'Armor Reenforcement'),
|
(TYPE_ARMOR_REENFORCEMENT, 'Armor Reenforcement'),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,6 +30,12 @@ class Timer(models.Model):
|
|||||||
return self.STATE_EXPIRED
|
return self.STATE_EXPIRED
|
||||||
return self.STATE_ACTIVE
|
return self.STATE_ACTIVE
|
||||||
|
|
||||||
|
def get_state_display(self):
|
||||||
|
state = self.state
|
||||||
|
for v, disp in self.STATE_CHOICES:
|
||||||
|
if state == v:
|
||||||
|
return disp
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
app_label = 'timer'
|
app_label = 'timer'
|
||||||
ordering = ['-expiration']
|
ordering = ['-expiration']
|
||||||
@@ -1,30 +1,91 @@
|
|||||||
from django.db.models.fields.related import SingleRelatedObjectDescriptor
|
import django
|
||||||
from django.db.models.query import QuerySet
|
from django.db.models.query import QuerySet
|
||||||
|
from django.db.models.fields.related import OneToOneField
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
|
||||||
|
try:
|
||||||
|
from django.db.models.constants import LOOKUP_SEP
|
||||||
|
except ImportError: # Django < 1.5
|
||||||
|
from django.db.models.sql.constants import LOOKUP_SEP
|
||||||
|
|
||||||
|
|
||||||
class InheritanceQuerySet(QuerySet):
|
class InheritanceQuerySet(QuerySet):
|
||||||
def select_subclasses(self, *subclasses):
|
def select_subclasses(self, *subclasses):
|
||||||
if not subclasses:
|
if not subclasses:
|
||||||
subclasses = [o for o in dir(self.model)
|
# only recurse one level on Django < 1.6 to avoid triggering
|
||||||
if isinstance(getattr(self.model, o), SingleRelatedObjectDescriptor) \
|
# https://code.djangoproject.com/ticket/16572
|
||||||
and issubclass(getattr(self.model, o).related.model, self.model)]
|
levels = None
|
||||||
|
if django.VERSION < (1, 6, 0):
|
||||||
|
levels = 1
|
||||||
|
subclasses = self._get_subclasses_recurse(self.model, levels=levels)
|
||||||
|
# workaround https://code.djangoproject.com/ticket/16855
|
||||||
|
field_dict = self.query.select_related
|
||||||
new_qs = self.select_related(*subclasses)
|
new_qs = self.select_related(*subclasses)
|
||||||
|
if isinstance(new_qs.query.select_related, dict) and isinstance(field_dict, dict):
|
||||||
|
new_qs.query.select_related.update(field_dict)
|
||||||
new_qs.subclasses = subclasses
|
new_qs.subclasses = subclasses
|
||||||
return new_qs
|
return new_qs
|
||||||
|
|
||||||
|
|
||||||
def _clone(self, klass=None, setup=False, **kwargs):
|
def _clone(self, klass=None, setup=False, **kwargs):
|
||||||
try:
|
for name in ['subclasses', '_annotated']:
|
||||||
kwargs.update({'subclasses': self.subclasses})
|
if hasattr(self, name):
|
||||||
except AttributeError:
|
kwargs[name] = getattr(self, name)
|
||||||
pass
|
|
||||||
return super(InheritanceQuerySet, self)._clone(klass, setup, **kwargs)
|
return super(InheritanceQuerySet, self)._clone(klass, setup, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def annotate(self, *args, **kwargs):
|
||||||
|
qset = super(InheritanceQuerySet, self).annotate(*args, **kwargs)
|
||||||
|
qset._annotated = [a.default_alias for a in args] + list(kwargs.keys())
|
||||||
|
return qset
|
||||||
|
|
||||||
|
|
||||||
def iterator(self):
|
def iterator(self):
|
||||||
iter = super(InheritanceQuerySet, self).iterator()
|
iter = super(InheritanceQuerySet, self).iterator()
|
||||||
if getattr(self, 'subclasses', False):
|
if getattr(self, 'subclasses', False):
|
||||||
for obj in iter:
|
for obj in iter:
|
||||||
obj = [getattr(obj, s) for s in self.subclasses if getattr(obj, s)] or [obj]
|
sub_obj = None
|
||||||
yield obj[0]
|
for s in self.subclasses:
|
||||||
|
sub_obj = self._get_sub_obj_recurse(obj, s)
|
||||||
|
if sub_obj:
|
||||||
|
break
|
||||||
|
if not sub_obj:
|
||||||
|
sub_obj = obj
|
||||||
|
|
||||||
|
if getattr(self, '_annotated', False):
|
||||||
|
for k in self._annotated:
|
||||||
|
setattr(sub_obj, k, getattr(obj, k))
|
||||||
|
|
||||||
|
yield sub_obj
|
||||||
else:
|
else:
|
||||||
for obj in iter:
|
for obj in iter:
|
||||||
yield obj
|
yield obj
|
||||||
|
|
||||||
|
|
||||||
|
def _get_subclasses_recurse(self, model, levels=None):
|
||||||
|
rels = [rel for rel in model._meta.get_all_related_objects()
|
||||||
|
if isinstance(rel.field, OneToOneField)
|
||||||
|
and issubclass(rel.field.model, model)]
|
||||||
|
subclasses = []
|
||||||
|
if levels:
|
||||||
|
levels -= 1
|
||||||
|
for rel in rels:
|
||||||
|
if levels or levels is None:
|
||||||
|
for subclass in self._get_subclasses_recurse(
|
||||||
|
rel.field.model, levels=levels):
|
||||||
|
subclasses.append(rel.var_name + LOOKUP_SEP + subclass)
|
||||||
|
subclasses.append(rel.var_name)
|
||||||
|
return subclasses
|
||||||
|
|
||||||
|
|
||||||
|
def _get_sub_obj_recurse(self, obj, s):
|
||||||
|
rel, _, s = s.partition(LOOKUP_SEP)
|
||||||
|
try:
|
||||||
|
node = getattr(obj, rel)
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
return None
|
||||||
|
if s:
|
||||||
|
child = self._get_sub_obj_recurse(node, s)
|
||||||
|
return child or node
|
||||||
|
else:
|
||||||
|
return node
|
||||||
346
app/timer/static/js/jquery.livesearch.input_dropdown.js
Normal file
346
app/timer/static/js/jquery.livesearch.input_dropdown.js
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
(function ($) {
|
||||||
|
|
||||||
|
/* KEY constant copied from jquery autocomplete:
|
||||||
|
* http://bassistance.de/jquery-plugins/jquery-plugin-autocomplete/
|
||||||
|
*/
|
||||||
|
var KEY = {
|
||||||
|
UP: 38,
|
||||||
|
DOWN: 40,
|
||||||
|
DEL: 46,
|
||||||
|
TAB: 9,
|
||||||
|
ENTER: 13,
|
||||||
|
ESC: 27,
|
||||||
|
COMMA: 188,
|
||||||
|
PAGEUP: 33,
|
||||||
|
PAGEDOWN: 34,
|
||||||
|
BACKSPACE: 8
|
||||||
|
};
|
||||||
|
|
||||||
|
function InputDropdown($elem, options) {
|
||||||
|
this.$elem = $elem;
|
||||||
|
this.options = $.extend({
|
||||||
|
update_input: true,
|
||||||
|
no_results_html: 'Sorry, we couldn\'t find anything.',
|
||||||
|
ignore_history: true,
|
||||||
|
process_results: function (r) { return r; }
|
||||||
|
}, options);
|
||||||
|
|
||||||
|
var $field_with_icon = this.$elem.closest('.field_with_icon'),
|
||||||
|
$existing_results = $([]);
|
||||||
|
|
||||||
|
if ($field_with_icon.length) {
|
||||||
|
$existing_results = $field_with_icon.siblings('.results');
|
||||||
|
} else {
|
||||||
|
$existing_results = this.$elem.siblings('.results');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($existing_results.length) {
|
||||||
|
this.$results = $existing_results;
|
||||||
|
this.$results.find('div.no_results').remove();
|
||||||
|
} else {
|
||||||
|
this.$results = $('<div class="results"></div>');
|
||||||
|
if ($field_with_icon.length) {
|
||||||
|
$field_with_icon.after(this.$results);
|
||||||
|
} else {
|
||||||
|
this.$elem.after(this.$results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.$results.find('ul.result_list').length) {
|
||||||
|
this.$results.append('<ul class="result_list"></ul>');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$no_results = $('<div class="no_results">' + this.options.no_results_html + '</div>');
|
||||||
|
this.$no_results.hide();
|
||||||
|
this.$results.append(this.$no_results);
|
||||||
|
|
||||||
|
this.handle_optional_content();
|
||||||
|
|
||||||
|
// css should do this
|
||||||
|
//this.$results.hide();
|
||||||
|
//this.$results.width(this.$elem.outerWidth());
|
||||||
|
this.livesearch = $elem.livesearch(this.options).data('livesearch');
|
||||||
|
this._attach();
|
||||||
|
}
|
||||||
|
|
||||||
|
$.fn.livesearch_input_dropdown = function (options) {
|
||||||
|
options = options || {};
|
||||||
|
return $(this).each(function () {
|
||||||
|
var input_dropdown = $(this).data('livesearch.input_dropdown');
|
||||||
|
if (!input_dropdown) {
|
||||||
|
input_dropdown = new InputDropdown($(this), options);
|
||||||
|
$(this).data('livesearch.input_dropdown', input_dropdown);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$.extend(InputDropdown.prototype, {
|
||||||
|
_attach: function () {
|
||||||
|
var _this = this;
|
||||||
|
|
||||||
|
this.$elem.bind('livesearch:results', function (e, results) {
|
||||||
|
if (_this.options.process_results) {
|
||||||
|
results = _this.options.process_results(results);
|
||||||
|
}
|
||||||
|
_this.show_results(results);
|
||||||
|
_this.push_history(results);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.bind_results();
|
||||||
|
this.bind_popstate();
|
||||||
|
|
||||||
|
this.$elem.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function (e) {
|
||||||
|
var something_selected = !!_this.$results.find('.selected').length,
|
||||||
|
$prev,
|
||||||
|
$next,
|
||||||
|
$selected;
|
||||||
|
switch (e.keyCode) {
|
||||||
|
|
||||||
|
case KEY.UP:
|
||||||
|
if (something_selected) {
|
||||||
|
$prev = _this.$results.find('.selected').prev(':not(.not_result)');
|
||||||
|
if (!$prev.length) {
|
||||||
|
$prev = _this.$results.find('li:not(.not_result)').last();
|
||||||
|
}
|
||||||
|
_this.select($prev, false);
|
||||||
|
} else {
|
||||||
|
_this.select(_this.$results.find('li:not(.not_result)').last(), false);
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
break;
|
||||||
|
case KEY.DOWN:
|
||||||
|
if (something_selected) {
|
||||||
|
$next = _this.$results.find('.selected').next(':not(.not_result)');
|
||||||
|
if (!$next.length) {
|
||||||
|
$next = _this.$results.find('li:not(.not_result)').first();
|
||||||
|
}
|
||||||
|
_this.select($next, false);
|
||||||
|
} else {
|
||||||
|
_this.select(_this.$results.find('li:not(.not_result)').first(), false);
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
break;
|
||||||
|
case KEY.ENTER:
|
||||||
|
// we want to trigger the selected event
|
||||||
|
$selected = _this.$results.find('.selected');
|
||||||
|
|
||||||
|
if (!$selected.length && _this.input_can_submit()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_this.select($selected, true);
|
||||||
|
e.preventDefault();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
history_api_supported: function () {
|
||||||
|
return window.history && window.history.pushState;
|
||||||
|
},
|
||||||
|
|
||||||
|
bind_popstate: function () {
|
||||||
|
if (!this.history_api_supported() || this.options.ignore_history) { return; }
|
||||||
|
var _this = this;
|
||||||
|
this.replacing_history_state = false;
|
||||||
|
window.onpopstate = function (e) {
|
||||||
|
if (e.state && e.state.livesearch) {
|
||||||
|
// We've got a search object in history, so let's restore the input to that state.
|
||||||
|
_this.show_results(e.state.livesearch.results);
|
||||||
|
_this.livesearch.suspend_while(function () {
|
||||||
|
_this.$elem.val(e.state.livesearch.input_value).trigger('input');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// We're at a new or null history state. We may have got
|
||||||
|
// here via the back button from an executed search, or have landed here via http search.
|
||||||
|
// Let's ensure a blank input.
|
||||||
|
_this.reset(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
input_can_submit: function () {
|
||||||
|
var
|
||||||
|
readyState = this.livesearch.search_xhr.readyState,
|
||||||
|
search_in_progress = (readyState > 0 && readyState < 4);
|
||||||
|
return this.options.input_can_submit_on_enter && !search_in_progress && this.$elem.is(":focus");
|
||||||
|
},
|
||||||
|
|
||||||
|
bind_results: function () {
|
||||||
|
var _this = this;
|
||||||
|
|
||||||
|
this.$results.find('li').bind('click', function () {
|
||||||
|
_this.select($(this), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$results.find('li').bind('mouseover', function () {
|
||||||
|
_this.select($(this));
|
||||||
|
_this.unselect_on_mouseout = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$results.bind('mouseout', function () {
|
||||||
|
if (_this.unselect_on_mouseout) {
|
||||||
|
_this.unselect_currently_selected();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$elem.bind('livesearch:reveal_results', function () {
|
||||||
|
_this.reveal_results();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Accepts an array of results, and returns an array of result
|
||||||
|
* names (strings)
|
||||||
|
*/
|
||||||
|
build_result_names_list: function (results) {
|
||||||
|
var result_names = [],
|
||||||
|
_this = this;
|
||||||
|
$.each(results, function () {
|
||||||
|
var name = this;
|
||||||
|
if (_this.options.return_name_from_result) {
|
||||||
|
name = _this.options.return_name_from_result(this);
|
||||||
|
} else if (this !== 'string') {
|
||||||
|
name = this[0];
|
||||||
|
}
|
||||||
|
result_names.push(name);
|
||||||
|
});
|
||||||
|
|
||||||
|
return result_names;
|
||||||
|
},
|
||||||
|
|
||||||
|
show_results: function (results) {
|
||||||
|
var $results_ul = this.$results.children('ul'),
|
||||||
|
$full_search_link = $([]),
|
||||||
|
result_names,
|
||||||
|
text,
|
||||||
|
arrow_icon,
|
||||||
|
meta;
|
||||||
|
|
||||||
|
if (results.results) {
|
||||||
|
meta = results;
|
||||||
|
results = results.results;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.unselect_currently_selected();
|
||||||
|
$results_ul.empty();
|
||||||
|
|
||||||
|
if (!results.length) {
|
||||||
|
this.$no_results.show();
|
||||||
|
$results_ul.hide();
|
||||||
|
} else {
|
||||||
|
this.$no_results.hide();
|
||||||
|
$results_ul.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
result_names = this.build_result_names_list(results);
|
||||||
|
|
||||||
|
$.each(result_names, function (index) {
|
||||||
|
var name = this,
|
||||||
|
$li = $('<li>' + name + '</li>');
|
||||||
|
$li.data('livesearch_result', results[index]);
|
||||||
|
$results_ul.append($li);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (meta && meta.count > results.length) {
|
||||||
|
text = 'see all ' + meta.count + ' results';
|
||||||
|
arrow_icon = '<span class="icon-arrow"></span> ';
|
||||||
|
$full_search_link = $('<a>').attr({
|
||||||
|
'href': this.livesearch.url + '?' + this.livesearch.last_search,
|
||||||
|
'title': text,
|
||||||
|
'class': 'see_all not_result'
|
||||||
|
}).html(arrow_icon + text).wrap('<li>');
|
||||||
|
$results_ul.append($full_search_link);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bind_results();
|
||||||
|
|
||||||
|
this.$elem.trigger('livesearch:reveal_results');
|
||||||
|
},
|
||||||
|
|
||||||
|
push_history: function (results) {
|
||||||
|
if (!this.history_api_supported() || this.options.ignore_history) { return; }
|
||||||
|
|
||||||
|
if (typeof results === 'string') {
|
||||||
|
results = JSON.parse(results);
|
||||||
|
}
|
||||||
|
|
||||||
|
var state = {
|
||||||
|
livesearch : {
|
||||||
|
input_value : this.$elem.val(),
|
||||||
|
results : results
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.replacing_history_state) {
|
||||||
|
window.history.replaceState(state);
|
||||||
|
this.replacing_history_state = false;
|
||||||
|
} else {
|
||||||
|
window.history.pushState(state, "", '?' + this.livesearch.last_search);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
reveal_results: function () {
|
||||||
|
var _this = this;
|
||||||
|
this.$results.slideDown(function () {
|
||||||
|
$(window).resize();
|
||||||
|
_this.$results.trigger('sticky_bar:fix_to_bottom');
|
||||||
|
_this.$results.trigger('shifted');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
reset: function (clear) {
|
||||||
|
this.$results.hide();
|
||||||
|
this.$elem.val('');
|
||||||
|
if (clear) {
|
||||||
|
this.$results.children('ul.result_list').html('');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
unselect_currently_selected: function () {
|
||||||
|
var $results_ul = this.$results.children('ul'),
|
||||||
|
$last_selected = $results_ul.children('li.selected');
|
||||||
|
$last_selected.trigger('livesearch:unselect');
|
||||||
|
$last_selected.removeClass('selected');
|
||||||
|
// We're here because of a mouseout, or because the user selected something with
|
||||||
|
// the keyboard, or clicking. In either case, we don't want to unselect on the
|
||||||
|
// next mouseout.
|
||||||
|
this.unselect_on_mouseout = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
// There are two kinds of selects:
|
||||||
|
// - Hard selects, these are triggered by clicks or the enter key. They
|
||||||
|
// trigger the select event.
|
||||||
|
// - Soft selects, these are triggered by arrowing up or down. They do not
|
||||||
|
// trigger the select event.
|
||||||
|
select: function ($li, trigger) {
|
||||||
|
var _this = this;
|
||||||
|
|
||||||
|
if ($li.is('.not_result')) { return; }
|
||||||
|
|
||||||
|
this.unselect_currently_selected();
|
||||||
|
|
||||||
|
$li.addClass('selected');
|
||||||
|
|
||||||
|
if (this.options.update_input) {
|
||||||
|
this.livesearch.suspend_while(function () {
|
||||||
|
_this.$elem.val($li.text());
|
||||||
|
_this.$elem.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$li.trigger('livesearch:soft_select');
|
||||||
|
if (trigger) {
|
||||||
|
$li.trigger('livesearch:selected', [$li.data('livesearch_result')]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handle_optional_content: function () {
|
||||||
|
if (this.options.prepend) {
|
||||||
|
this.$results.prepend(this.options.prepend);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}(jQuery));
|
||||||
148
app/timer/static/js/jquery.livesearch.js
Normal file
148
app/timer/static/js/jquery.livesearch.js
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
(function ($) {
|
||||||
|
|
||||||
|
//The timeout has to live in this scope
|
||||||
|
var timeout;
|
||||||
|
|
||||||
|
function LiveSearch($elem, options) {
|
||||||
|
this.$elem = $elem;
|
||||||
|
this.$form = $elem.closest('form');
|
||||||
|
this.options = $.extend({
|
||||||
|
delay: 400,
|
||||||
|
minimum_characters: 3,
|
||||||
|
serialize: this.$form,
|
||||||
|
client_side_cache: true,
|
||||||
|
process_data: false
|
||||||
|
}, options);
|
||||||
|
if (this.options.file_extension) {
|
||||||
|
this.ajax_url = this.ensure_file_extension(this.options.file_extension);
|
||||||
|
} else {
|
||||||
|
this.ajax_url = this.url;
|
||||||
|
}
|
||||||
|
this.last_search = false;
|
||||||
|
this.search_xhr = false;
|
||||||
|
if (this.options.client_side_cache) {
|
||||||
|
this.cache = {};
|
||||||
|
} else {
|
||||||
|
this.cache = false;
|
||||||
|
}
|
||||||
|
this.active = true;
|
||||||
|
this._attach();
|
||||||
|
}
|
||||||
|
|
||||||
|
$.fn.livesearch = function (options) {
|
||||||
|
options = options || {};
|
||||||
|
return $(this).each(function () {
|
||||||
|
var livesearch = $(this).data('livesearch');
|
||||||
|
if (!livesearch) {
|
||||||
|
livesearch = new LiveSearch($(this), options);
|
||||||
|
$(this).data('livesearch', livesearch);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
$.extend(LiveSearch.prototype, {
|
||||||
|
_attach: function () {
|
||||||
|
var _this = this;
|
||||||
|
|
||||||
|
this.$elem.attr('autocomplete', 'off'); //we got this, yall
|
||||||
|
|
||||||
|
this.$elem.bind("keypress cut paste input", function () {
|
||||||
|
if (!_this.active) { return; }
|
||||||
|
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(function () {
|
||||||
|
_this.search();
|
||||||
|
}, _this.options.delay);
|
||||||
|
});
|
||||||
|
this.options.serialize.bind('change', function () {
|
||||||
|
_this.search();
|
||||||
|
});
|
||||||
|
this.$elem.bind('livesearch:suspend', function () {
|
||||||
|
_this.active = false;
|
||||||
|
});
|
||||||
|
this.$elem.bind('livesearch:activate', function () {
|
||||||
|
_this.active = true;
|
||||||
|
});
|
||||||
|
this.$elem.bind('livesearch:cancel', function () {
|
||||||
|
if (_this.search_xhr) {
|
||||||
|
_this.search_xhr.abort();
|
||||||
|
}
|
||||||
|
_this.last_search = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
ensure_file_extension: function (extension) {
|
||||||
|
var
|
||||||
|
host_regexp_string = window.location.host.replace(/[^\w\d]/g, function (m) { return '\\' + m; }),
|
||||||
|
file_extension_regexp = new RegExp("((?:" + host_regexp_string + ")?[^\\.$#\\?]+)(\\.\\w*|)($|#|\\?)");
|
||||||
|
|
||||||
|
return this.url.replace(file_extension_regexp, function (m, _1, _2, _3) { return _1 + '.' + extension + _3; });
|
||||||
|
},
|
||||||
|
|
||||||
|
suspend_while: function (func) {
|
||||||
|
this.active = false;
|
||||||
|
func();
|
||||||
|
// TODO: this timeout is to to allow events to bubble before re-enabling,
|
||||||
|
// but I'm not sure why bubbling doesn't occur synchronously.
|
||||||
|
var _this = this;
|
||||||
|
setTimeout(function () {
|
||||||
|
_this.active = true;
|
||||||
|
}, 100);
|
||||||
|
},
|
||||||
|
|
||||||
|
search: function () {
|
||||||
|
var _this = this,
|
||||||
|
form_data = this.options.serialize.serialize();
|
||||||
|
|
||||||
|
|
||||||
|
if (this.options.process_data) {
|
||||||
|
form_data = this.options.process_data.apply(this, [form_data]);
|
||||||
|
if (typeof form_data === 'object') {
|
||||||
|
form_data = $.param(form_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (form_data === this.last_search) { return; }
|
||||||
|
if (this.$elem.val().length < this.options.minimum_characters) { return; }
|
||||||
|
|
||||||
|
if (this.search_xhr) {
|
||||||
|
this.search_xhr.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.cache && this.cache[form_data] && typeof (this.cache[form_data]) !== 'function') {
|
||||||
|
this.$elem.trigger('livesearch:results', [this.cache[form_data]]);
|
||||||
|
} else {
|
||||||
|
this.$elem.trigger('livesearch:searching');
|
||||||
|
this.$elem.addClass('searching');
|
||||||
|
|
||||||
|
this.search_xhr = $.ajax({
|
||||||
|
type: 'get',
|
||||||
|
url: this.options.url || this.$form.attr('action'),
|
||||||
|
dataType: 'json',
|
||||||
|
data: form_data,
|
||||||
|
global: false,
|
||||||
|
success: function (data, textStatus, xhr) {
|
||||||
|
// this is the best workaround I can think of for
|
||||||
|
// http://dev.jquery.com/ticket/6173
|
||||||
|
if (data === null) { return; }
|
||||||
|
|
||||||
|
_this.$elem.trigger('livesearch:results', [data]);
|
||||||
|
_this.$elem.removeClass('searching');
|
||||||
|
if (_this.cache) {
|
||||||
|
_this.cache[form_data] = data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function () {
|
||||||
|
_this.$elem.trigger('livesearch:ajax_error');
|
||||||
|
_this.$elem.removeClass('searching');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.last_search = form_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}(jQuery));
|
||||||
100
app/timer/static/js/jquery.livesearch.multi_selector.js
Normal file
100
app/timer/static/js/jquery.livesearch.multi_selector.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
(function ($) {
|
||||||
|
|
||||||
|
$.fn.livesearch_multi_selector = function (options) {
|
||||||
|
options = $.extend({
|
||||||
|
url: false,
|
||||||
|
cancel_copy: 'Remove',
|
||||||
|
sortable: false,
|
||||||
|
update_input: false
|
||||||
|
}, options);
|
||||||
|
return $(this).each(function () {
|
||||||
|
var $div = $(this),
|
||||||
|
$input = $div.find('input[type="text"]'),
|
||||||
|
$field_with_icon = $input.closest('.field_with_icon'),
|
||||||
|
$list = $div.find('ol.search-selected,ul.search-selected'),
|
||||||
|
$search_loading_icon = $input.siblings('.icon-search'),
|
||||||
|
name = $input.attr('name'),
|
||||||
|
position_name = $input.siblings('.position').attr('name'),
|
||||||
|
|
||||||
|
this_options = options,
|
||||||
|
input_dropdown;
|
||||||
|
|
||||||
|
this_options.url = options.url || $div.closest('form').attr('action');
|
||||||
|
input_dropdown = $input.livesearch_input_dropdown(this_options);
|
||||||
|
|
||||||
|
if (options.sortable) {
|
||||||
|
$list.sortable({
|
||||||
|
containment: 'document',
|
||||||
|
axis: 'y',
|
||||||
|
update: function () {
|
||||||
|
$list.find('input.position').each(function (i) {
|
||||||
|
$(this).val(i);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function unselectable($li) {
|
||||||
|
var $cancel = $('<a class="cancel-link" href="#">' + options.cancel_copy + '</a>'),
|
||||||
|
$destroy = $li.find('.destroy');
|
||||||
|
$li.append($cancel);
|
||||||
|
if ($destroy.val() && $destroy.val() !== 'false') {
|
||||||
|
$li.hide();
|
||||||
|
}
|
||||||
|
$cancel.click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$li.fadeOut(function () {
|
||||||
|
$destroy.val('1');
|
||||||
|
if (!$destroy.length) { //new record
|
||||||
|
$li.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
$input.val('');
|
||||||
|
$field_with_icon.siblings('.results').slideUp();
|
||||||
|
$input.trigger('livesearch:cancel');
|
||||||
|
}
|
||||||
|
|
||||||
|
$div.addClass('search');
|
||||||
|
$input.attr('name', '');
|
||||||
|
|
||||||
|
$list.find('li').each(function () {
|
||||||
|
unselectable($(this));
|
||||||
|
});
|
||||||
|
|
||||||
|
$input.bind('livesearch:searching', function () {
|
||||||
|
$search_loading_icon.removeClass('icon-search').addClass('icon-loading-small');
|
||||||
|
});
|
||||||
|
|
||||||
|
$input.bind('livesearch:results livesearch:ajax_error', function () {
|
||||||
|
$search_loading_icon.removeClass('icon-loading-small').addClass('icon-search');
|
||||||
|
});
|
||||||
|
|
||||||
|
$div.bind('livesearch:selected', function (e, data) {
|
||||||
|
if (!data) { return; }
|
||||||
|
var this_name = name.replace('[template]', '[' + $list.children('li').length + ']'),
|
||||||
|
this_position_name = false,
|
||||||
|
$li;
|
||||||
|
if (position_name) {
|
||||||
|
this_position_name = position_name.replace('[template]', '[' + $list.children('li').length + ']');
|
||||||
|
}
|
||||||
|
$li = $('<li>' + data[0] + '<input type="hidden" name="' + this_name + '" value="' + data[1] + '"/></li>');
|
||||||
|
if (options.sortable) {
|
||||||
|
if (this_position_name) {
|
||||||
|
$li.append('<input type="hidden" class="position" name="' + this_position_name + '" />');
|
||||||
|
}
|
||||||
|
$list.find('input.position').each(function (i) {
|
||||||
|
$(this).val(i);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$list.append($li);
|
||||||
|
unselectable($li);
|
||||||
|
reset();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
}(jQuery));
|
||||||
52
app/timer/static/js/jquery.livesearch.navigator.js
Normal file
52
app/timer/static/js/jquery.livesearch.navigator.js
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
(function ($) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Behavior:
|
||||||
|
*
|
||||||
|
* A user starts typing into an input, when they stop typing, a list of matching pages are
|
||||||
|
* displayed. The user can select an entry with the up and down arrow key and hit enter,
|
||||||
|
* or they can click a result. When a result is selected, the corresponding url is navigated
|
||||||
|
* to.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
*
|
||||||
|
* jquery.livesearch
|
||||||
|
* jquery.livesearch.input_dropdown
|
||||||
|
* jquery.livesearch.pretty_input_dropdown
|
||||||
|
*
|
||||||
|
* Expected markup:
|
||||||
|
*
|
||||||
|
* <form>
|
||||||
|
* <div>
|
||||||
|
* <span class='icon-search'></span>
|
||||||
|
* <span class='icon-search-clear'></span>
|
||||||
|
* <input></input>
|
||||||
|
* <div class='results'></div>
|
||||||
|
* </div>
|
||||||
|
* </form>
|
||||||
|
*
|
||||||
|
* Expected JSON response from the XHR:
|
||||||
|
*
|
||||||
|
* [
|
||||||
|
* ['Title of page', 'http://url/'],
|
||||||
|
* ['Title of another page', 'http://url2/']
|
||||||
|
* ]
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
$.fn.livesearch_navigator = function (options) {
|
||||||
|
options = options || {};
|
||||||
|
return $(this).each(function () {
|
||||||
|
var $form = $(this);
|
||||||
|
|
||||||
|
$form.livesearch_pretty_input_dropdown();
|
||||||
|
|
||||||
|
$form.bind('livesearch:selected', function (e, data) {
|
||||||
|
if (data) {
|
||||||
|
window.location = data[1];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
}(jQuery));
|
||||||
103
app/timer/static/js/jquery.livesearch.pretty_input_dropdown.js
Normal file
103
app/timer/static/js/jquery.livesearch.pretty_input_dropdown.js
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
(function ($) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Behavior:
|
||||||
|
*
|
||||||
|
* A user starts typing into an input, when they stop typing, a list of matching pages are
|
||||||
|
* displayed. The user can select an entry with the up and down arrow key and hit enter,
|
||||||
|
* or they can click a result. When a result is selected, the corresponding url is navigated
|
||||||
|
* to.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
*
|
||||||
|
* jquery.livesearch
|
||||||
|
* jquery.livesearch.input_dropdown
|
||||||
|
*
|
||||||
|
* Expected markup:
|
||||||
|
*
|
||||||
|
* <form>
|
||||||
|
* <div>
|
||||||
|
* <span class='icon-search'></span>
|
||||||
|
* <span class='icon-search-clear'></span>
|
||||||
|
* <input></input>
|
||||||
|
* <div class='results'></div>
|
||||||
|
* </div>
|
||||||
|
* </form>
|
||||||
|
*
|
||||||
|
* Expected JSON response from the XHR:
|
||||||
|
*
|
||||||
|
* [
|
||||||
|
* ['Title of page', 'http://url/'],
|
||||||
|
* ['Title of another page', 'http://url2/']
|
||||||
|
* ]
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
$.fn.livesearch_pretty_input_dropdown = function (options) {
|
||||||
|
options = options || {};
|
||||||
|
return $(this).each(function () {
|
||||||
|
var $form = $(this),
|
||||||
|
$text = $form.find('input[type=text]'),
|
||||||
|
$icon_search_clear = $form.find('.icon-search-clear'),
|
||||||
|
$search_loading_icon = $text.siblings('.icon-search'),
|
||||||
|
input_dropdown;
|
||||||
|
|
||||||
|
$text.livesearch_input_dropdown($.extend(options, {update_input : false}));
|
||||||
|
input_dropdown = $text.data('livesearch.input_dropdown');
|
||||||
|
|
||||||
|
function handle_close_button() {
|
||||||
|
if (this.value) {
|
||||||
|
$icon_search_clear.show();
|
||||||
|
} else {
|
||||||
|
$icon_search_clear.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_close_button.call($text[0]);
|
||||||
|
|
||||||
|
$text.bind('keypress cut paste input livesearch:close_results blur', handle_close_button);
|
||||||
|
|
||||||
|
$text.bind('livesearch:searching', function () {
|
||||||
|
$search_loading_icon.removeClass('icon-search').addClass('icon-loading-small');
|
||||||
|
});
|
||||||
|
|
||||||
|
$text.bind('livesearch:results livesearch:ajax_error', function () {
|
||||||
|
$search_loading_icon.removeClass('icon-loading-small').addClass('icon-search');
|
||||||
|
});
|
||||||
|
|
||||||
|
$text.bind('livesearch:results', function () {
|
||||||
|
// if this option is set, assume we want the cursor to stay in the input after search is done
|
||||||
|
if (options.input_can_submit_on_enter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var $results = $text.siblings('.results');
|
||||||
|
input_dropdown.select($results.find('li:first'));
|
||||||
|
});
|
||||||
|
|
||||||
|
function clear_results() {
|
||||||
|
$text.val('');
|
||||||
|
$text.trigger('livesearch:cancel').trigger('livesearch:close_results');
|
||||||
|
}
|
||||||
|
|
||||||
|
$icon_search_clear.bind('click', function () {
|
||||||
|
$text.focus();
|
||||||
|
clear_results();
|
||||||
|
});
|
||||||
|
|
||||||
|
$text.bind('livesearch:close_results', function () {
|
||||||
|
$text.siblings('.results').slideUp(function () {
|
||||||
|
$(window).resize();
|
||||||
|
$(this).trigger('sticky_bar:fix_to_bottom');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$text.bind('blur', function () {
|
||||||
|
if ($text.val().length < 3) {
|
||||||
|
clear_results();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
}(jQuery));
|
||||||
69
app/timer/static/js/jquery.livesearch.selector.js
Normal file
69
app/timer/static/js/jquery.livesearch.selector.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
(function ($) {
|
||||||
|
|
||||||
|
$.fn.livesearch_selector = function (options) {
|
||||||
|
options = $.extend({url: false, cancel_copy: 'Cancel', target_input: false}, options);
|
||||||
|
return $(this).each(function () {
|
||||||
|
var $div = $(this),
|
||||||
|
$input = $div.find('input[type="text"]'),
|
||||||
|
$hidden_input = options.target_input || $div.find('input[type="hidden"]'),
|
||||||
|
$search_loading_icon = $input.siblings('.icon-search'),
|
||||||
|
input_dropdown;
|
||||||
|
|
||||||
|
$div.addClass('search');
|
||||||
|
|
||||||
|
function select() {
|
||||||
|
$input.hide();
|
||||||
|
$input.attr('disabled', 'disabled');
|
||||||
|
$input.siblings('.icon-search-clear, .icon-search').hide();
|
||||||
|
var $value_div = $('<div class="field-selected"><span class="value">' + $input.val() + '</span><a class="cancel-link" href="#">' + options.cancel_copy + '</a></div>');
|
||||||
|
$input.siblings('div.field-selected').remove();
|
||||||
|
$input.after($value_div);
|
||||||
|
$input.siblings('.results').slideUp();
|
||||||
|
$input.trigger('livesearch_selector:select', [ $input.val() ]);
|
||||||
|
|
||||||
|
$value_div.find('a.cancel-link').click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$input.val('');
|
||||||
|
$value_div.remove();
|
||||||
|
$input.removeAttr('disabled');
|
||||||
|
$input.siblings('.icon-search-clear, .icon-search').show();
|
||||||
|
$input.show();
|
||||||
|
$input.focus();
|
||||||
|
$hidden_input.val('');
|
||||||
|
$input.trigger('dirty');
|
||||||
|
$input.trigger('livesearch_selector:unselect');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//if the page loads with values in the inputs, switch to selected state
|
||||||
|
if ($hidden_input.val() && $input.val()) {
|
||||||
|
select();
|
||||||
|
}
|
||||||
|
|
||||||
|
options.url = options.url || $div.closest('form').attr('action');
|
||||||
|
input_dropdown = $input.livesearch_input_dropdown(options);
|
||||||
|
|
||||||
|
$input.bind('livesearch:searching', function () {
|
||||||
|
$search_loading_icon.removeClass('icon-search').addClass('icon-loading-small');
|
||||||
|
});
|
||||||
|
|
||||||
|
$input.bind('livesearch:results livesearch:ajax_error', function () {
|
||||||
|
$search_loading_icon.removeClass('icon-loading-small').addClass('icon-search');
|
||||||
|
});
|
||||||
|
|
||||||
|
$input.bind('livesearch:results', function () {
|
||||||
|
var $results = $input.siblings('.results');
|
||||||
|
input_dropdown.select($results.find('li:first'));
|
||||||
|
});
|
||||||
|
|
||||||
|
$div.bind('livesearch:selected', function (e, data) {
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$hidden_input.val(data[1]);
|
||||||
|
select();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
}(jQuery));
|
||||||
25
app/timer/templates/timer/timer_form.html
Normal file
25
app/timer/templates/timer/timer_form.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>Add Timer</h1>
|
||||||
|
</div>
|
||||||
|
<form method="post">
|
||||||
|
{{ form.as_p }}
|
||||||
|
{% csrf_token %}
|
||||||
|
<input class="btn btn-success" type="submit" value="Add Timer" />
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script type="text/javascript" src="{% static "js/jquery.livesearch.js" %}"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(function() {
|
||||||
|
$('id_location').livesearch();
|
||||||
|
$('form').bind('livesearch:results', function(e, results) {
|
||||||
|
console.log(results);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@@ -1,3 +1,75 @@
|
|||||||
{% for timer in timer_list %}
|
{% extends "base.html" %}
|
||||||
{{ timer.location.name }} - {{ timer.expiration_datetime }}
|
|
||||||
{% endfor %}
|
{% block content %}
|
||||||
|
<div class="row-fluid">
|
||||||
|
<div class="span9">
|
||||||
|
<p class="pull-left">
|
||||||
|
<a class="badge {% if not list_type or list_type == 0 %}badge-info{% endif %}" href="?{% if list_all %}all={{ list_all }}&{% endif %}type=0">All</a>
|
||||||
|
<a class="badge {% if list_type == 1 %}badge-info{% endif %}" href="?{% if list_all %}all={{ list_all }}&{% endif %}type=1">Stations</a>
|
||||||
|
<a class="badge {% if list_type == 2 %}badge-info{% endif %}" href="?{% if list_all %}all={{ list_all }}&{% endif %}type=2">IHub</a>
|
||||||
|
<a class="badge {% if list_type == 3 %}badge-info{% endif %}" href="?{% if list_all %}all={{ list_all }}&{% endif %}type=3">POS</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="span3">
|
||||||
|
<p class="pull-right">
|
||||||
|
<a class="badge {% if not list_all %}badge-info{% endif %}" href="?{% if list_type %}type={{ list_type }}&{% endif %}all=0">Active</a>
|
||||||
|
<a class="badge {% if list_all %}badge-info{% endif %}" href="?{% if list_type %}type={{ list_type }}&{% endif %}all=1">All</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row-fluid">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr><th>Location</th><th>Location Type</th><th>System</th><th>Type</th><th>Expiration</th><th>Status</th><th>Actions</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for timer in object_list %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ timer.location.name }}</td>
|
||||||
|
<td>{{ timer.location.get_type }}</td>
|
||||||
|
<td><a href="http://evemaps.dotlan.net/system/{{ timer.location.get_subclass.system }}">{{ timer.location.get_subclass.system }}</a></td>
|
||||||
|
<td>{{ timer.get_reenforcement_type_display }}</td>
|
||||||
|
<td class="time" data-unixtime="{{ timer.expiration|date:"U" }}">{{ timer.expiration }}</td>
|
||||||
|
<td>{{ timer.get_state_display }}</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
function secondsToTime(secs)
|
||||||
|
{
|
||||||
|
var hours = Math.floor(secs / (60 * 60));
|
||||||
|
var divisor_for_minutes = secs % (60 * 60);
|
||||||
|
var minutes = Math.floor(divisor_for_minutes / 60);
|
||||||
|
var divisor_for_seconds = divisor_for_minutes % 60;
|
||||||
|
var seconds = Math.ceil(divisor_for_seconds);
|
||||||
|
var obj = {
|
||||||
|
"h": hours,
|
||||||
|
"m": minutes,
|
||||||
|
"s": seconds
|
||||||
|
};
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
function update_timers() {
|
||||||
|
$('td.time').each(
|
||||||
|
function (e) {
|
||||||
|
if (this.attributes['data-unixtime']) {
|
||||||
|
var now = new Date();
|
||||||
|
var secs = this.attributes['data-unixtime'].value - Math.round((now.getTime() / 1000));
|
||||||
|
if (secs > 0) {
|
||||||
|
var res = secondsToTime();
|
||||||
|
this.innerHTML = res.h + "h " + res.m + "m " + res.s + "s";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setInterval(update_timers, 1000);
|
||||||
|
update_timers();
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
from django.conf.urls import patterns, url
|
from django.conf.urls import patterns, url
|
||||||
from timer.views import TimerListView
|
from timer.views import TimerListView, TimerCreateView
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
url(r'^$', TimerListView.as_view(), name='timer-list'),
|
url(r'^$', TimerListView.as_view(), name='timer-list'),
|
||||||
|
url(r'^create/$', TimerCreateView.as_view(), name='timer-create')
|
||||||
|
|
||||||
)
|
)
|
||||||
@@ -1,14 +1,38 @@
|
|||||||
from django.views.generic import ListView
|
from django.views.generic import ListView, CreateView
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from timer.models import Timer
|
from timer.models import Timer, Location, Station, Moon, System
|
||||||
|
from timer.forms import TimerForm
|
||||||
|
|
||||||
|
|
||||||
|
class TimerCreateView(CreateView):
|
||||||
|
model = Timer
|
||||||
|
form_class = TimerForm
|
||||||
|
|
||||||
|
|
||||||
class TimerListView(ListView):
|
class TimerListView(ListView):
|
||||||
model = Timer
|
model = Timer
|
||||||
|
template_name = 'timer/timer_list.html'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = super(TimerListView, self).get_queryset()
|
qs = super(TimerListView, self).get_queryset().select_related('location', 'station', 'moon', 'system')
|
||||||
if 'active' in self.kwargs:
|
|
||||||
qs = qs.filter(expiry_datetime__gt=now())
|
if int(self.request.GET.get('all', 0)) == 0:
|
||||||
|
qs = qs.filter(expiration__gt=now())
|
||||||
|
|
||||||
|
type = int(self.request.GET.get('type', 0))
|
||||||
|
if type == 1:
|
||||||
|
qs = [m for m in qs if m.location.get_type == 'Station']
|
||||||
|
if type == 2:
|
||||||
|
qs = [m for m in qs if m.location.get_type == 'System']
|
||||||
|
if type == 3:
|
||||||
|
qs = [m for m in qs if m.location.get_type == 'Moon']
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
ctx = super(TimerListView, self).get_context_data(**kwargs)
|
||||||
|
ctx.update({
|
||||||
|
'list_all': int(self.request.GET.get('all', 0)),
|
||||||
|
'list_type': int(self.request.GET.get('type', 0)),
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
Reference in New Issue
Block a user