From: Charles Connell Date: Mon, 12 May 2014 21:55:08 +0000 (-0400) Subject: Upgrade datatables, load courses over ajax X-Git-Tag: release-20150131~91 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=814fad534c505dadd0b22337bf5b11253f247383;p=oweals%2Fkarmaworld.git Upgrade datatables, load courses over ajax --- diff --git a/karmaworld/apps/courses/migrations/0013_auto__add_field_course_thank_count.py b/karmaworld/apps/courses/migrations/0013_auto__add_field_course_thank_count.py new file mode 100644 index 0000000..6f11a44 --- /dev/null +++ b/karmaworld/apps/courses/migrations/0013_auto__add_field_course_thank_count.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'Course.thank_count' + db.add_column(u'courses_course', 'thank_count', + self.gf('django.db.models.fields.IntegerField')(default=0), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Course.thank_count' + db.delete_column(u'courses_course', 'thank_count') + + + models = { + u'courses.course': { + 'Meta': {'ordering': "['-file_count', 'school', 'name']", 'unique_together': "(('name', 'school'),)", 'object_name': 'Course'}, + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'department': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['courses.Department']", 'null': 'True', 'blank': 'True'}), + 'desc': ('django.db.models.fields.TextField', [], {'max_length': '511', 'null': 'True', 'blank': 'True'}), + 'file_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'flags': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'instructor_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + 'instructor_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'professor': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['courses.Professor']", 'null': 'True', 'blank': 'True'}), + 'school': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['courses.School']", 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '150', 'null': 'True'}), + 'thank_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'}), + 'url': ('django.db.models.fields.URLField', [], {'max_length': '511', 'null': 'True', 'blank': 'True'}) + }, + u'courses.department': { + 'Meta': {'unique_together': "(('name', 'school'),)", 'object_name': 'Department'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'school': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['courses.School']"}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '150', 'null': 'True'}), + 'url': ('django.db.models.fields.URLField', [], {'max_length': '511', 'null': 'True', 'blank': 'True'}) + }, + u'courses.professor': { + 'Meta': {'unique_together': "(('name', 'email'),)", 'object_name': 'Professor'}, + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + u'courses.school': { + 'Meta': {'ordering': "['-file_count', '-priority', 'name']", 'object_name': 'School'}, + 'alias': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'facebook_id': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'file_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'hashtag': ('django.db.models.fields.CharField', [], {'max_length': '16', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'priority': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '150', 'null': 'True'}), + 'url': ('django.db.models.fields.URLField', [], {'max_length': '511', 'blank': 'True'}), + 'usde_id': ('django.db.models.fields.BigIntegerField', [], {'unique': 'True', 'null': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['courses'] \ No newline at end of file diff --git a/karmaworld/apps/courses/migrations/0014_populate_file_count.py b/karmaworld/apps/courses/migrations/0014_populate_file_count.py new file mode 100644 index 0000000..bd865ef --- /dev/null +++ b/karmaworld/apps/courses/migrations/0014_populate_file_count.py @@ -0,0 +1,157 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + for course in orm.Course.objects.all(): + sum = 0 + for note in orm['notes.Note'].objects.filter(course=course): + sum += note.thanks + course.thank_count = sum + course.save() + + def backwards(self, orm): + pass + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'courses.course': { + 'Meta': {'ordering': "['-file_count', 'school', 'name']", 'unique_together': "(('name', 'school'),)", 'object_name': 'Course'}, + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'department': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['courses.Department']", 'null': 'True', 'blank': 'True'}), + 'desc': ('django.db.models.fields.TextField', [], {'max_length': '511', 'null': 'True', 'blank': 'True'}), + 'file_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'flags': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'instructor_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + 'instructor_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'professor': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['courses.Professor']", 'null': 'True', 'blank': 'True'}), + 'school': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['courses.School']", 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '150', 'null': 'True'}), + 'thank_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'}), + 'url': ('django.db.models.fields.URLField', [], {'max_length': '511', 'null': 'True', 'blank': 'True'}) + }, + u'courses.department': { + 'Meta': {'unique_together': "(('name', 'school'),)", 'object_name': 'Department'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'school': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['courses.School']"}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '150', 'null': 'True'}), + 'url': ('django.db.models.fields.URLField', [], {'max_length': '511', 'null': 'True', 'blank': 'True'}) + }, + u'courses.professor': { + 'Meta': {'unique_together': "(('name', 'email'),)", 'object_name': 'Professor'}, + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + u'courses.school': { + 'Meta': {'ordering': "['-file_count', '-priority', 'name']", 'object_name': 'School'}, + 'alias': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'facebook_id': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'file_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'hashtag': ('django.db.models.fields.CharField', [], {'max_length': '16', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'priority': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '150', 'null': 'True'}), + 'url': ('django.db.models.fields.URLField', [], {'max_length': '511', 'blank': 'True'}), + 'usde_id': ('django.db.models.fields.BigIntegerField', [], {'unique': 'True', 'null': 'True', 'blank': 'True'}) + }, + u'licenses.license': { + 'Meta': {'object_name': 'License'}, + 'html': ('django.db.models.fields.TextField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}) + }, + u'notes.note': { + 'Meta': {'ordering': "['-uploaded_at']", 'unique_together': "(('fp_file', 'upstream_link'),)", 'object_name': 'Note'}, + 'category': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'course': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['courses.Course']"}), + 'flags': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'fp_file': ('django_filepicker.models.FPFileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'gdrive_url': ('django.db.models.fields.URLField', [], {'max_length': '1024', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39', 'null': 'True', 'blank': 'True'}), + 'is_hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'license': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['licenses.License']", 'null': 'True', 'blank': 'True'}), + 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}), + 'text': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'thanks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'tweeted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow', 'null': 'True'}), + 'upstream_link': ('django.db.models.fields.URLField', [], {'max_length': '1024', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}) + }, + u'notes.notemarkdown': { + 'Meta': {'object_name': 'NoteMarkdown'}, + 'markdown': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'note': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['notes.Note']", 'unique': 'True', 'primary_key': 'True'}) + }, + u'notes.useruploadmapping': { + 'Meta': {'unique_together': "(('user', 'fp_file'),)", 'object_name': 'UserUploadMapping'}, + 'fp_file': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}) + }, + u'taggit.tag': { + 'Meta': {'object_name': 'Tag'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'}) + }, + u'taggit.taggeditem': { + 'Meta': {'object_name': 'TaggedItem'}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_tagged_items'", 'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_items'", 'to': u"orm['taggit.Tag']"}) + } + } + + complete_apps = ['notes', 'courses'] \ No newline at end of file diff --git a/karmaworld/apps/courses/models.py b/karmaworld/apps/courses/models.py index f6b3f8c..778d2b4 100644 --- a/karmaworld/apps/courses/models.py +++ b/karmaworld/apps/courses/models.py @@ -247,6 +247,7 @@ class Course(models.Model): # (vistigial) school = models.ForeignKey(School, null=True, blank=True) file_count = models.IntegerField(default=0) + thank_count = models.IntegerField(default=0) desc = models.TextField(max_length=511, blank=True, null=True) url = models.URLField(max_length=511, blank=True, null=True, @@ -310,15 +311,13 @@ class Course(models.Model): self.file_count = self.note_set.count() self.save() - def get_popularity(self): - """ Aggregate popularity of notes contained within. """ - # This is optimized for the case where note_set has already been - # loaded into memory. In that case, we avoid making any more database - # queries. - sum = 0 - for note in self.note_set.all(): - sum += note.thanks - return sum + + def update_thank_count(self): + """ Update the thank_count by summing the note_set + """ + self.thank_count = sum([note.thanks for note in self.note_set.all()]) + self.save() + def get_prof_names(self): """ Comma separated list of professor names. """ diff --git a/karmaworld/apps/courses/views.py b/karmaworld/apps/courses/views.py index b6d6fc8..260caf8 100644 --- a/karmaworld/apps/courses/views.py +++ b/karmaworld/apps/courses/views.py @@ -2,7 +2,12 @@ # -*- coding:utf8 -*- # Copyright (C) 2012 FinalsClub Foundation """ Views for the KarmaNotes Courses app """ +import calendar +from time import strftime +from django.db.models import Q +from django.utils.html import escape +from querystring_parser import parser as querystring_parser import json from django.conf import settings from django.core import serializers @@ -23,7 +28,7 @@ from karmaworld.apps.courses.forms import CourseForm from karmaworld.apps.notes.models import Note from karmaworld.apps.users.models import CourseKarmaEvent from karmaworld.apps.notes.forms import FileUploadForm -from karmaworld.utils import ajax_increment, format_session_increment_field +from karmaworld.utils import ajax_increment, format_session_increment_field, ajax_base from django.contrib import messages FLAG_FIELD = 'flags' @@ -244,6 +249,89 @@ def flag_course(request, pk): return ajax_increment(Course, request, pk, FLAG_FIELD, USER_PROFILE_FLAGS_FIELD, process_course_flag_events) +def course_json(course): + course_data = { + 'school': course.school.name if course.school else course.department.school.name, + 'department': course.department.name if course.department else None, + 'instructor': course.instructor_name if course.instructor_name else ', '.join([p.name for p in course.professor.all()]), + 'name': course.name, + 'link': course.get_absolute_url(), + 'file_count': course.file_count, + 'popularity': course.thank_count, + 'updated_at': strftime('%B %d, %Y', course.updated_at.utctimetuple()) + } + + # Prevent XSS attacks + for k in course_data: + course_data[k] = escape(course_data[k]) + + return course_data + + +def course_list_ajax_handler(request): + request_dict = querystring_parser.parse(request.GET.urlencode()) + draw = int(request_dict['draw']) + start = request_dict['start'] + length = request_dict['length'] + search = request_dict.get('search', None) + + objects = Course.objects.all() + + if search and search['value']: + objects = objects.filter(Q(name__icontains=search['value']) | + Q(school__name__icontains=search['value']) | + Q(department__school__name__icontains=search['value'])) + + order_fields = [] + for order_index in request_dict['order']: + order_field = None + order = request_dict['order'][order_index] + if order['column'] == 1: + order_field = 'updated_at' + elif order['column'] == 2: + order_field = 'file_count' + elif order['column'] == 3: + order_field = 'thank_count' + + if order['dir'] == 'desc': + order_field = '-' + order_field + + if order_field: + order_fields.append(order_field) + + objects = objects.order_by(*order_fields) + + displayRecords = objects.count() + + if start > 0: + objects = objects[start:] + + objects = objects[:length] + + row_data = [ + [ + course_json(course), + calendar.timegm(course.updated_at.timetuple()), + course.file_count, + course.thank_count, + course.school.name if course.school else course.department.school.name, + ] for course in objects + ] + + response_dict = { + 'draw': draw, + 'recordsTotal': Course.objects.count(), + 'recordsFiltered': displayRecords, + 'data': row_data + } + + return HttpResponse(json.dumps(response_dict), mimetype='application/json') + + +def course_list_ajax(request): + return ajax_base(request, course_list_ajax_handler, ['GET']) + + def edit_course(request, pk): """ Saves the edited course content diff --git a/karmaworld/apps/notes/models.py b/karmaworld/apps/notes/models.py index 760b87c..7ac1693 100644 --- a/karmaworld/apps/notes/models.py +++ b/karmaworld/apps/notes/models.py @@ -431,6 +431,7 @@ def update_note_counts(note_instance): pass else: # course exists + note_instance.course.update_thank_count() note_instance.course.update_note_count() if note_instance.course.school: note_instance.course.school.update_note_count() @@ -456,8 +457,8 @@ def note_save_receiver(sender, **kwargs): return note = kwargs['instance'] - if kwargs['created']: - update_note_counts(note) + + update_note_counts(note) try: index = SearchIndex() diff --git a/karmaworld/assets/css/global.css b/karmaworld/assets/css/global.css index 613f282..d9fcb98 100644 --- a/karmaworld/assets/css/global.css +++ b/karmaworld/assets/css/global.css @@ -193,7 +193,8 @@ span.table-thanks-count { margin-right: 15px; } -.table-course-name { +.table-course-name-wrapper, +.table-note-name { font-size: 1.8em; margin-right: 10px; } @@ -222,7 +223,7 @@ table.dataTable a { color: inherit; } -.dataTables_paginate { +#data_table_list_paginate { float: left; } diff --git a/karmaworld/assets/css/jquery.dataTables.css b/karmaworld/assets/css/jquery.dataTables.css index da73800..d57b597 100644 --- a/karmaworld/assets/css/jquery.dataTables.css +++ b/karmaworld/assets/css/jquery.dataTables.css @@ -1,221 +1,399 @@ - /* - * Table + * Table styles */ table.dataTable { - margin: 0 auto; - clear: both; - width: 100%; -} - -table.dataTable thead th { - padding: 3px 18px 3px 10px; - border-bottom: 1px solid black; - font-weight: bold; - cursor: pointer; - *cursor: hand; + width: 100%; + margin: 0 auto; + clear: both; + border-collapse: separate; + border-spacing: 0; + /* + * Header and footer styles + */ + /* + * Body styles + */ } - +table.dataTable thead th, table.dataTable tfoot th { - padding: 3px 18px 3px 10px; - border-top: 1px solid black; - font-weight: bold; + font-weight: bold; } - -table.dataTable td { - padding: 3px 10px; +table.dataTable thead th, +table.dataTable thead td { + padding: 10px 18px; + border-bottom: 1px solid #111111; } - +table.dataTable thead th:active, +table.dataTable thead td:active { + outline: none; +} +table.dataTable tfoot th, +table.dataTable tfoot td { + padding: 10px 18px 6px 18px; + border-top: 1px solid #111111; +} +table.dataTable thead .sorting_asc, +table.dataTable thead .sorting_desc, +table.dataTable thead .sorting { + cursor: pointer; + *cursor: hand; +} +table.dataTable thead .sorting { + background: url("../images/sort_both.png") no-repeat center right; +} +table.dataTable thead .sorting_asc { + background: url("../images/sort_asc.png") no-repeat center right; +} +table.dataTable thead .sorting_desc { + background: url("../images/sort_desc.png") no-repeat center right; +} +table.dataTable thead .sorting_asc_disabled { + background: url("../images/sort_asc_disabled.png") no-repeat center right; +} +table.dataTable thead .sorting_desc_disabled { + background: url("../images/sort_desc_disabled.png") no-repeat center right; +} +table.dataTable tbody tr { + background-color: white; +} +table.dataTable tbody tr.selected { + background-color: #b0bed9; +} +table.dataTable tbody th, +table.dataTable tbody td { + padding: 8px 10px; +} +table.dataTable th.center, table.dataTable td.center, table.dataTable td.dataTables_empty { - text-align: center; + text-align: center; +} +table.dataTable th.right, +table.dataTable td.right { + text-align: right; +} +table.dataTable.row-border tbody th, table.dataTable.row-border tbody td, table.dataTable.display tbody th, table.dataTable.display tbody td { + border-top: 1px solid #dddddd; +} +table.dataTable.row-border tbody tr:first-child th, +table.dataTable.row-border tbody tr:first-child td, table.dataTable.display tbody tr:first-child th, +table.dataTable.display tbody tr:first-child td { + border-top: none; +} +table.dataTable.cell-border tbody th, table.dataTable.cell-border tbody td { + border-top: 1px solid #dddddd; + border-right: 1px solid #dddddd; +} +table.dataTable.cell-border tbody tr th:first-child, +table.dataTable.cell-border tbody tr td:first-child { + border-left: 1px solid #dddddd; +} +table.dataTable.cell-border tbody tr:first-child th, +table.dataTable.cell-border tbody tr:first-child td { + border-top: none; +} +table.dataTable.stripe tbody tr.odd, table.dataTable.display tbody tr.odd { + background-color: #f9f9f9; +} +table.dataTable.stripe tbody tr.odd.selected, table.dataTable.display tbody tr.odd.selected { + background-color: #abb9d3; +} +table.dataTable.hover tbody tr:hover, +table.dataTable.hover tbody tr.odd:hover, +table.dataTable.hover tbody tr.even:hover, table.dataTable.display tbody tr:hover, +table.dataTable.display tbody tr.odd:hover, +table.dataTable.display tbody tr.even:hover { + background-color: whitesmoke; +} +table.dataTable.hover tbody tr:hover.selected, +table.dataTable.hover tbody tr.odd:hover.selected, +table.dataTable.hover tbody tr.even:hover.selected, table.dataTable.display tbody tr:hover.selected, +table.dataTable.display tbody tr.odd:hover.selected, +table.dataTable.display tbody tr.even:hover.selected { + background-color: #a9b7d1; +} +table.dataTable.order-column tbody tr > .sorting_1, +table.dataTable.order-column tbody tr > .sorting_2, +table.dataTable.order-column tbody tr > .sorting_3, table.dataTable.display tbody tr > .sorting_1, +table.dataTable.display tbody tr > .sorting_2, +table.dataTable.display tbody tr > .sorting_3 { + background-color: #f9f9f9; +} +table.dataTable.order-column tbody tr.selected > .sorting_1, +table.dataTable.order-column tbody tr.selected > .sorting_2, +table.dataTable.order-column tbody tr.selected > .sorting_3, table.dataTable.display tbody tr.selected > .sorting_1, +table.dataTable.display tbody tr.selected > .sorting_2, +table.dataTable.display tbody tr.selected > .sorting_3 { + background-color: #acbad4; +} +table.dataTable.display tbody tr.odd > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd > .sorting_1 { + background-color: #f1f1f1; +} +table.dataTable.display tbody tr.odd > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd > .sorting_2 { + background-color: #f3f3f3; +} +table.dataTable.display tbody tr.odd > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd > .sorting_3 { + background-color: whitesmoke; +} +table.dataTable.display tbody tr.odd.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_1 { + background-color: #a6b3cd; +} +table.dataTable.display tbody tr.odd.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_2 { + background-color: #a7b5ce; +} +table.dataTable.display tbody tr.odd.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_3 { + background-color: #a9b6d0; +} +table.dataTable.display tbody tr.even > .sorting_1, table.dataTable.order-column.stripe tbody tr.even > .sorting_1 { + background-color: #f9f9f9; +} +table.dataTable.display tbody tr.even > .sorting_2, table.dataTable.order-column.stripe tbody tr.even > .sorting_2 { + background-color: #fbfbfb; +} +table.dataTable.display tbody tr.even > .sorting_3, table.dataTable.order-column.stripe tbody tr.even > .sorting_3 { + background-color: #fdfdfd; +} +table.dataTable.display tbody tr.even.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_1 { + background-color: #acbad4; +} +table.dataTable.display tbody tr.even.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_2 { + background-color: #adbbd6; +} +table.dataTable.display tbody tr.even.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_3 { + background-color: #afbdd8; +} +table.dataTable.display tbody tr:hover > .sorting_1, +table.dataTable.display tbody tr.odd:hover > .sorting_1, +table.dataTable.display tbody tr.even:hover > .sorting_1, table.dataTable.order-column.hover tbody tr:hover > .sorting_1, +table.dataTable.order-column.hover tbody tr.odd:hover > .sorting_1, +table.dataTable.order-column.hover tbody tr.even:hover > .sorting_1 { + background-color: #eaeaea; +} +table.dataTable.display tbody tr:hover > .sorting_2, +table.dataTable.display tbody tr.odd:hover > .sorting_2, +table.dataTable.display tbody tr.even:hover > .sorting_2, table.dataTable.order-column.hover tbody tr:hover > .sorting_2, +table.dataTable.order-column.hover tbody tr.odd:hover > .sorting_2, +table.dataTable.order-column.hover tbody tr.even:hover > .sorting_2 { + background-color: #ebebeb; +} +table.dataTable.display tbody tr:hover > .sorting_3, +table.dataTable.display tbody tr.odd:hover > .sorting_3, +table.dataTable.display tbody tr.even:hover > .sorting_3, table.dataTable.order-column.hover tbody tr:hover > .sorting_3, +table.dataTable.order-column.hover tbody tr.odd:hover > .sorting_3, +table.dataTable.order-column.hover tbody tr.even:hover > .sorting_3 { + background-color: #eeeeee; +} +table.dataTable.display tbody tr:hover.selected > .sorting_1, +table.dataTable.display tbody tr.odd:hover.selected > .sorting_1, +table.dataTable.display tbody tr.even:hover.selected > .sorting_1, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_1, +table.dataTable.order-column.hover tbody tr.odd:hover.selected > .sorting_1, +table.dataTable.order-column.hover tbody tr.even:hover.selected > .sorting_1 { + background-color: #a1aec7; +} +table.dataTable.display tbody tr:hover.selected > .sorting_2, +table.dataTable.display tbody tr.odd:hover.selected > .sorting_2, +table.dataTable.display tbody tr.even:hover.selected > .sorting_2, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_2, +table.dataTable.order-column.hover tbody tr.odd:hover.selected > .sorting_2, +table.dataTable.order-column.hover tbody tr.even:hover.selected > .sorting_2 { + background-color: #a2afc8; +} +table.dataTable.display tbody tr:hover.selected > .sorting_3, +table.dataTable.display tbody tr.odd:hover.selected > .sorting_3, +table.dataTable.display tbody tr.even:hover.selected > .sorting_3, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_3, +table.dataTable.order-column.hover tbody tr.odd:hover.selected > .sorting_3, +table.dataTable.order-column.hover tbody tr.even:hover.selected > .sorting_3 { + background-color: #a4b2cb; +} +table.dataTable.no-footer { + border-bottom: 1px solid #111111; } -table.dataTable tr.odd { background-color: #E2E4FF; } -table.dataTable tr.even { background-color: white; } - -table.dataTable tr.odd td.sorting_1 { background-color: #D3D6FF; } -table.dataTable tr.odd td.sorting_2 { background-color: #DADCFF; } -table.dataTable tr.odd td.sorting_3 { background-color: #E0E2FF; } -table.dataTable tr.even td.sorting_1 { background-color: #EAEBFF; } -table.dataTable tr.even td.sorting_2 { background-color: #F2F3FF; } -table.dataTable tr.even td.sorting_3 { background-color: #F9F9FF; } - +table.dataTable, +table.dataTable th, +table.dataTable td { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} /* - * Table wrapper + * Control feature layout */ .dataTables_wrapper { - position: relative; - clear: both; - *zoom: 1; + position: relative; + clear: both; + *zoom: 1; + zoom: 1; } - - -/* - * Page length menu - */ -.dataTables_length { - float: left; +.dataTables_wrapper .dataTables_length { + float: left; } - - -/* - * Filter - */ -.dataTables_filter { - float: right; - text-align: right; +.dataTables_wrapper .dataTables_filter { + float: right; + text-align: right; } - - -/* - * Table information - */ -.dataTables_info { - clear: both; - float: left; +.dataTables_wrapper .dataTables_filter input { + margin-left: 0.5em; } - - -/* - * Pagination - */ -.dataTables_paginate { - float: right; - text-align: right; +.dataTables_wrapper .dataTables_info { + clear: both; + float: left; + padding-top: 0.755em; } - -/* Two button pagination - previous / next */ -.paginate_disabled_previous, -.paginate_enabled_previous, -.paginate_disabled_next, -.paginate_enabled_next { - height: 19px; - float: left; - cursor: pointer; - *cursor: hand; - color: #111 !important; -} -.paginate_disabled_previous:hover, -.paginate_enabled_previous:hover, -.paginate_disabled_next:hover, -.paginate_enabled_next:hover { - text-decoration: none !important; -} -.paginate_disabled_previous:active, -.paginate_enabled_previous:active, -.paginate_disabled_next:active, -.paginate_enabled_next:active { - outline: none; +.dataTables_wrapper .dataTables_paginate { + float: right; + text-align: right; + padding-top: 0.25em; } - -.paginate_disabled_previous, -.paginate_disabled_next { - color: #666 !important; +.dataTables_wrapper .dataTables_paginate .paginate_button { + box-sizing: border-box; + display: inline-block; + min-width: 1.5em; + padding: 0.5em 1em; + margin-left: 2px; + text-align: center; + text-decoration: none !important; + cursor: pointer; + *cursor: hand; + color: #333333 !important; + border: 1px solid transparent; } -.paginate_disabled_previous, -.paginate_enabled_previous { - padding-left: 23px; +.dataTables_wrapper .dataTables_paginate .paginate_button.current, .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover { + color: #333333 !important; + border: 1px solid #cacaca; + background-color: white; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(100%, gainsboro)); + /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, white 0%, gainsboro 100%); + /* Chrome10+,Safari5.1+ */ + background: -moz-linear-gradient(top, white 0%, gainsboro 100%); + /* FF3.6+ */ + background: -ms-linear-gradient(top, white 0%, gainsboro 100%); + /* IE10+ */ + background: -o-linear-gradient(top, white 0%, gainsboro 100%); + /* Opera 11.10+ */ + background: linear-gradient(to bottom, white 0%, gainsboro 100%); + /* W3C */ } -.paginate_disabled_next, -.paginate_enabled_next { - padding-right: 23px; - margin-left: 10px; +.dataTables_wrapper .dataTables_paginate .paginate_button.disabled, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active { + cursor: default; + color: #666 !important; + border: 1px solid transparent; + background: transparent; + box-shadow: none; } - -.paginate_enabled_previous { background: url('/static/css/images/back_enabled.png') no-repeat top left; } -.paginate_enabled_previous:hover { background: url('/static/css/images/back_enabled_hover.png') no-repeat top left; } -.paginate_disabled_previous { background: url('/static/css/images/back_disabled.png') no-repeat top left; } - -.paginate_enabled_next { background: url('/static/css/images/forward_enabled.png') no-repeat top right; } -.paginate_enabled_next:hover { background: url('/static/css/images/forward_enabled_hover.png') no-repeat top right; } -.paginate_disabled_next { background: url('/static/css/images/forward_disabled.png') no-repeat top right; } - -/* Full number pagination */ -.paging_full_numbers { - height: 22px; - line-height: 22px; +.dataTables_wrapper .dataTables_paginate .paginate_button:hover { + color: white !important; + border: 1px solid #111111; + background-color: #585858; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111111)); + /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #585858 0%, #111111 100%); + /* Chrome10+,Safari5.1+ */ + background: -moz-linear-gradient(top, #585858 0%, #111111 100%); + /* FF3.6+ */ + background: -ms-linear-gradient(top, #585858 0%, #111111 100%); + /* IE10+ */ + background: -o-linear-gradient(top, #585858 0%, #111111 100%); + /* Opera 11.10+ */ + background: linear-gradient(to bottom, #585858 0%, #111111 100%); + /* W3C */ } -.paging_full_numbers a:active { - outline: none +.dataTables_wrapper .dataTables_paginate .paginate_button:active { + outline: none; + background-color: #2b2b2b; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c)); + /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); + /* Chrome10+,Safari5.1+ */ + background: -moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); + /* FF3.6+ */ + background: -ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); + /* IE10+ */ + background: -o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); + /* Opera 11.10+ */ + background: linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%); + /* W3C */ + box-shadow: inset 0 0 3px #111; } -.paging_full_numbers a:hover { - text-decoration: none; +.dataTables_wrapper .dataTables_processing { + position: absolute; + top: 50%; + left: 50%; + width: 100%; + height: 40px; + margin-left: -50%; + margin-top: -25px; + padding-top: 20px; + text-align: center; + font-size: 1.2em; + background-color: white; + background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(25%, rgba(255, 255, 255, 0.9)), color-stop(75%, rgba(255, 255, 255, 0.9)), color-stop(100%, rgba(255, 255, 255, 0))); + /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); + /* Chrome10+,Safari5.1+ */ + background: -moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); + /* FF3.6+ */ + background: -ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); + /* IE10+ */ + background: -o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); + /* Opera 11.10+ */ + background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); + /* W3C */ } - -.paging_full_numbers a.paginate_button, -.paging_full_numbers a.paginate_active { - border: 1px solid #aaa; - -webkit-border-radius: 5px; - -moz-border-radius: 5px; - border-radius: 5px; - padding: 2px 5px; - margin: 0 3px; - cursor: pointer; - *cursor: hand; - color: #333 !important; +.dataTables_wrapper .dataTables_length, +.dataTables_wrapper .dataTables_filter, +.dataTables_wrapper .dataTables_info, +.dataTables_wrapper .dataTables_processing, +.dataTables_wrapper .dataTables_paginate { + color: #333333; } - -.paging_full_numbers a.paginate_button { - background-color: #ddd; +.dataTables_wrapper .dataTables_scroll { + clear: both; } - -.paging_full_numbers a.paginate_button:hover { - background-color: #ccc; - text-decoration: none !important; +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody { + *margin-top: -1px; + -webkit-overflow-scrolling: touch; } - -.paging_full_numbers a.paginate_active { - background-color: #99B3FF; +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody th > div.dataTables_sizing, +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody td > div.dataTables_sizing { + height: 0; + overflow: hidden; + margin: 0 !important; + padding: 0 !important; } - - -/* - * Processing indicator - */ -.dataTables_processing { - position: absolute; - top: 50%; - left: 50%; - width: 250px; - height: 30px; - margin-left: -125px; - margin-top: -15px; - padding: 14px 0 2px 0; - border: 1px solid #ddd; - text-align: center; - color: #999; - font-size: 14px; - background-color: white; +.dataTables_wrapper.no-footer .dataTables_scrollBody { + border-bottom: 1px solid #111111; } - - -/* - * Sorting - */ -.sorting { background: url('/static/css/images/sort_both.png') no-repeat center right; } -.sorting_asc { background: url('/static/css/images/sort_asc.png') no-repeat center right; } -.sorting_desc { background: url('/static/css/images/sort_desc.png') no-repeat center right; } - -.sorting_asc_disabled { background: url('/static/css/images/sort_asc_disabled.png') no-repeat center right; } -.sorting_desc_disabled { background: url('/static/css/images/sort_desc_disabled.png') no-repeat center right; } - -table.dataTable thead th:active, -table.dataTable thead td:active { - outline: none; +.dataTables_wrapper.no-footer div.dataTables_scrollHead table, +.dataTables_wrapper.no-footer div.dataTables_scrollBody table { + border-bottom: none; } - - -/* - * Scrolling - */ -.dataTables_scroll { - clear: both; +.dataTables_wrapper:after { + visibility: hidden; + display: block; + content: ""; + clear: both; + height: 0; } -.dataTables_scrollBody { - *margin-top: -1px; - -webkit-overflow-scrolling: touch; +@media screen and (max-width: 767px) { + .dataTables_wrapper .dataTables_info, + .dataTables_wrapper .dataTables_paginate { + float: none; + text-align: center; + } + .dataTables_wrapper .dataTables_paginate { + margin-top: 0.5em; + } +} +@media screen and (max-width: 640px) { + .dataTables_wrapper .dataTables_length, + .dataTables_wrapper .dataTables_filter { + float: none; + text-align: center; + } + .dataTables_wrapper .dataTables_filter { + margin-top: 0.5em; + } } - diff --git a/karmaworld/assets/js/course-list.js b/karmaworld/assets/js/course-list.js index 31d3c14..e67149f 100644 --- a/karmaworld/assets/js/course-list.js +++ b/karmaworld/assets/js/course-list.js @@ -1,38 +1,53 @@ $(function() { + function tableRow(courseData) { + var rowContents = $('#data-table-entry-prototype').clone(); + rowContents.find('.data-table-entry').removeClass('hide'); + rowContents.find('.table-school').text(courseData['school']); + rowContents.find('.table-department').text(courseData['department']); + rowContents.find('.table-instructor').text(courseData['instructor']); + rowContents.find('.table-course-name').text(courseData['name']); + rowContents.find('.table-course-link').attr('href', courseData['link']); + rowContents.find('.file-count').text(courseData['file_count']); + rowContents.find('.thanks-count').text(courseData['popularity']); + rowContents.find('.updated-at').text(courseData['updated_at']); + return rowContents.html(); + } + // load dataTable for course data var dataTable = $('#data_table_list').dataTable({ // we will set column widths explicitly - 'bAutoWidth': false, + 'autoWidth': false, // don't provide a option for the user to change the table page length - 'bLengthChange': false, + 'lengthChange': false, // sepcify the number of rows in a page - 'iDisplayLength': 20, + 'displayLength': 20, // Position the filter bar at the top - 'sDom': '<"top">rt<"bottom"p><"clear">', - // Specify options for each column - "aoColumnDefs": [ - { - // 3rd element: thanks - "aTargets": [ 2 ], - "bSortable": true, - "bVisible": true - }, - { - // 2nd element: notes - "aTargets": [ 1 ], - "bSortable": true, - "bVisible": true - }, - { - // 1st element: date - "aTargets": [ 0 ], - "bSortable": true, - "bVisible": true - } - ], + 'dom': '<"top">rt<"bottom"p><"clear">', // Initial sorting - 'aaSorting': [[2,'desc']] + 'aaSorting': [[2,'desc']], + 'paging': true, + 'columns': [ + { 'name': 'course', 'orderable': false, 'searchable': true, 'visible': true, + 'class': 'small-12 columns data-table-entry-wrapper' }, + { 'name': 'date', 'orderable': true, 'searchable': false, 'visible': false }, + { 'name': 'note_count', 'orderable': true, 'searchable': false, 'visible': false }, + { 'name': 'popularity', 'orderable': true, 'searchable': false, 'visible': false }, + ], + 'createdRow': function(row, data, index) { + $(row).addClass('table-row'); + }, + // Use server-side processing + 'processing': true, + 'serverSide': true, + 'ajax': function(data, callback, settings) { + $.get(course_list_ajax_url, data, function(dataWrapper, textStatus, jqXHR) { + for (i = 0; i < dataWrapper.data.length; i++) { + dataWrapper.data[i][0] = tableRow(dataWrapper.data[i][0]); + } + callback(dataWrapper); + }); + } }); // wire up search box diff --git a/karmaworld/assets/js/jquery.dataTables.js b/karmaworld/assets/js/jquery.dataTables.js index 1d8a220..fce3d1a 100644 --- a/karmaworld/assets/js/jquery.dataTables.js +++ b/karmaworld/assets/js/jquery.dataTables.js @@ -1,7459 +1,8717 @@ +/*! DataTables 1.10.0 + * ©2008-2014 SpryMedia Ltd - datatables.net/license + */ + /** * @summary DataTables - * @description Paginate, search and sort HTML tables - * @version 1.9.4 + * @description Paginate, search and order HTML tables + * @version 1.10.0 * @file jquery.dataTables.js - * @author Allan Jardine (www.sprymedia.co.uk) + * @author SpryMedia Ltd (www.sprymedia.co.uk) * @contact www.sprymedia.co.uk/contact + * @copyright Copyright 2008-2014 SpryMedia Ltd. * - * @copyright Copyright 2008-2012 Allan Jardine, all rights reserved. + * This source file is free software, available under the following license: + * MIT license - http://datatables.net/license * - * This source file is free software, under either the GPL v2 license or a - * BSD style license, available at: - * http://datatables.net/license_gpl2 - * http://datatables.net/license_bsd - * - * This source file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * This source file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. - * + * * For details please refer to: http://www.datatables.net */ /*jslint evil: true, undef: true, browser: true */ -/*globals $, jQuery,define,_fnExternApiFunc,_fnInitialise,_fnInitComplete,_fnLanguageCompat,_fnAddColumn,_fnColumnOptions,_fnAddData,_fnCreateTr,_fnGatherData,_fnBuildHead,_fnDrawHead,_fnDraw,_fnReDraw,_fnAjaxUpdate,_fnAjaxParameters,_fnAjaxUpdateDraw,_fnServerParams,_fnAddOptionsHtml,_fnFeatureHtmlTable,_fnScrollDraw,_fnAdjustColumnSizing,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnBuildSearchArray,_fnBuildSearchRow,_fnFilterCreateSearch,_fnDataToSearch,_fnSort,_fnSortAttachListener,_fnSortingClasses,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnFeatureHtmlLength,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnNodeToDataIndex,_fnVisbleColumns,_fnCalculateEnd,_fnConvertToWidth,_fnCalculateColumnWidths,_fnScrollingWidthAdjust,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnDetectType,_fnSettingsFromNode,_fnGetDataMaster,_fnGetTrNodes,_fnGetTdNodes,_fnEscapeRegex,_fnDeleteIndex,_fnReOrderIndex,_fnColumnOrdering,_fnLog,_fnClearTable,_fnSaveState,_fnLoadState,_fnCreateCookie,_fnReadCookie,_fnDetectHeader,_fnGetUniqueThs,_fnScrollBarWidth,_fnApplyToChildren,_fnMap,_fnGetRowData,_fnGetCellData,_fnSetCellData,_fnGetObjectDataFn,_fnSetObjectDataFn,_fnApplyColumnDefs,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnJsonString,_fnRender,_fnNodeToColumnIndex,_fnInfoMacros,_fnBrowserDetect,_fnGetColumns*/ +/*globals $,require,jQuery,define,_selector_run,_selector_opts,_selector_first,_selector_row_indexes,_ext,_Api,_api_register,_api_registerPlural,_re_new_lines,_re_html,_re_formatted_numeric,_re_escape_regex,_empty,_intVal,_numToDecimal,_isNumber,_isHtml,_htmlNumeric,_pluck,_pluck_order,_range,_stripHtml,_unique,_fnBuildAjax,_fnAjaxUpdate,_fnAjaxParameters,_fnAjaxUpdateDraw,_fnAjaxDataSrc,_fnAddColumn,_fnColumnOptions,_fnAdjustColumnSizing,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnVisbleColumns,_fnGetColumns,_fnColumnTypes,_fnApplyColumnDefs,_fnHungarianMap,_fnCamelToHungarian,_fnLanguageCompat,_fnBrowserDetect,_fnAddData,_fnAddTr,_fnNodeToDataIndex,_fnNodeToColumnIndex,_fnGetCellData,_fnSetCellData,_fnSplitObjNotation,_fnGetObjectDataFn,_fnSetObjectDataFn,_fnGetDataMaster,_fnClearTable,_fnDeleteIndex,_fnInvalidateRow,_fnGetRowElements,_fnCreateTr,_fnBuildHead,_fnDrawHead,_fnDraw,_fnReDraw,_fnAddOptionsHtml,_fnDetectHeader,_fnGetUniqueThs,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnFilterCreateSearch,_fnEscapeRegex,_fnFilterData,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnInfoMacros,_fnInitialise,_fnInitComplete,_fnLengthChange,_fnFeatureHtmlLength,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnFeatureHtmlTable,_fnScrollDraw,_fnApplyToChildren,_fnCalculateColumnWidths,_fnThrottle,_fnConvertToWidth,_fnScrollingWidthAdjust,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnScrollBarWidth,_fnSortFlatten,_fnSort,_fnSortAria,_fnSortListener,_fnSortAttachListener,_fnSortingClasses,_fnSortData,_fnSaveState,_fnLoadState,_fnSettingsFromNode,_fnLog,_fnMap,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnLengthOverflow,_fnRenderer,_fnDataSource,_fnRowAttributes*/ (/** @lends */function( window, document, undefined ) { (function( factory ) { "use strict"; - // Define as an AMD module if possible - if ( typeof define === 'function' && define.amd ) - { - define( ['jquery'], factory ); + if ( typeof define === 'function' && define.amd ) { + // Define as an AMD module if possible + define( 'datatables', ['jquery'], factory ); } - /* Define using browser globals otherwise - * Prevent multiple instantiations if the script is loaded twice - */ - else if ( jQuery && !jQuery.fn.dataTable ) - { + else if ( typeof exports === 'object' ) { + // Node/CommonJS + factory( require( 'jquery' ) ); + } + else if ( jQuery && !jQuery.fn.dataTable ) { + // Define using browser globals otherwise + // Prevent multiple instantiations if the script is loaded twice factory( jQuery ); } } (/** @lends */function( $ ) { "use strict"; - /** - * DataTables is a plug-in for the jQuery Javascript library. It is a - * highly flexible tool, based upon the foundations of progressive - * enhancement, which will add advanced interaction controls to any - * HTML table. For a full list of features please refer to - * DataTables.net. + + /** + * DataTables is a plug-in for the jQuery Javascript library. It is a highly + * flexible tool, based upon the foundations of progressive enhancement, + * which will add advanced interaction controls to any HTML table. For a + * full list of features please refer to + * [DataTables.net](href="http://datatables.net). * - * Note that the DataTable object is not a global variable but is - * aliased to jQuery.fn.DataTable and jQuery.fn.dataTable through which - * it may be accessed. + * Note that the `DataTable` object is not a global variable but is aliased + * to `jQuery.fn.DataTable` and `jQuery.fn.dataTable` through which it may + * be accessed. * * @class - * @param {object} [oInit={}] Configuration object for DataTables. Options + * @param {object} [init={}] Configuration object for DataTables. Options * are defined by {@link DataTable.defaults} - * @requires jQuery 1.3+ - * + * @requires jQuery 1.7+ + * * @example * // Basic initialisation * $(document).ready( function { * $('#example').dataTable(); * } ); - * + * * @example * // Initialisation with configuration options - in this case, disable * // pagination and sorting. * $(document).ready( function { * $('#example').dataTable( { - * "bPaginate": false, - * "bSort": false + * "paginate": false, + * "sort": false * } ); * } ); */ - var DataTable = function( oInit ) - { - - - /** - * Add a column to the list used for the table with default values - * @param {object} oSettings dataTables settings object - * @param {node} nTh The th element for this column - * @memberof DataTable#oApi - */ - function _fnAddColumn( oSettings, nTh ) - { - var oDefaults = DataTable.defaults.columns; - var iCol = oSettings.aoColumns.length; - var oCol = $.extend( {}, DataTable.models.oColumn, oDefaults, { - "sSortingClass": oSettings.oClasses.sSortable, - "sSortingClassJUI": oSettings.oClasses.sSortJUI, - "nTh": nTh ? nTh : document.createElement('th'), - "sTitle": oDefaults.sTitle ? oDefaults.sTitle : nTh ? nTh.innerHTML : '', - "aDataSort": oDefaults.aDataSort ? oDefaults.aDataSort : [iCol], - "mData": oDefaults.mData ? oDefaults.oDefaults : iCol - } ); - oSettings.aoColumns.push( oCol ); - - /* Add a column specific filter */ - if ( oSettings.aoPreSearchCols[ iCol ] === undefined || oSettings.aoPreSearchCols[ iCol ] === null ) - { - oSettings.aoPreSearchCols[ iCol ] = $.extend( {}, DataTable.models.oSearch ); - } - else - { - var oPre = oSettings.aoPreSearchCols[ iCol ]; - - /* Don't require that the user must specify bRegex, bSmart or bCaseInsensitive */ - if ( oPre.bRegex === undefined ) - { - oPre.bRegex = true; - } - - if ( oPre.bSmart === undefined ) - { - oPre.bSmart = true; - } - - if ( oPre.bCaseInsensitive === undefined ) - { - oPre.bCaseInsensitive = true; - } - } - - /* Use the column options function to initialise classes etc */ - _fnColumnOptions( oSettings, iCol, null ); + var DataTable; + + + /* + * It is useful to have variables which are scoped locally so only the + * DataTables functions can access them and they don't leak into global space. + * At the same time these functions are often useful over multiple files in the + * core and API, so we list, or at least document, all variables which are used + * by DataTables as private variables here. This also ensures that there is no + * clashing of variable names and that they can easily referenced for reuse. + */ + + + // Defined else where + // _selector_run + // _selector_opts + // _selector_first + // _selector_row_indexes + + var _ext; // DataTable.ext + var _Api; // DataTable.Api + var _api_register; // DataTable.Api.register + var _api_registerPlural; // DataTable.Api.registerPlural + + var _re_dic = {}; + var _re_new_lines = /[\r\n]/g; + var _re_html = /<.*?>/g; + var _re_date_start = /^[\d\+\-a-zA-Z]/; + + // Escape regular expression special characters + var _re_escape_regex = new RegExp( '(\\' + [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ].join('|\\') + ')', 'g' ); + + // U+2009 is thin space and U+202F is narrow no-break space, both used in many + // standards as thousands separators + var _re_formatted_numeric = /[',$£€¥%\u2009\u202F]/g; + + + var _empty = function ( d ) { + return !d || d === '-' ? true : false; + }; + + + var _intVal = function ( s ) { + var integer = parseInt( s, 10 ); + return !isNaN(integer) && isFinite(s) ? integer : null; + }; + + // Convert from a formatted number with characters other than `.` as the + // decimal place, to a Javascript number + var _numToDecimal = function ( num, decimalPoint ) { + // Cache created regular expressions for speed as this function is called often + if ( ! _re_dic[ decimalPoint ] ) { + _re_dic[ decimalPoint ] = new RegExp( _fnEscapeRegex( decimalPoint ), 'g' ); } - - - /** - * Apply options for a column - * @param {object} oSettings dataTables settings object - * @param {int} iCol column index to consider - * @param {object} oOptions object with sType, bVisible and bSearchable etc - * @memberof DataTable#oApi - */ - function _fnColumnOptions( oSettings, iCol, oOptions ) - { - var oCol = oSettings.aoColumns[ iCol ]; - - /* User specified column options */ - if ( oOptions !== undefined && oOptions !== null ) - { - /* Backwards compatibility for mDataProp */ - if ( oOptions.mDataProp && !oOptions.mData ) - { - oOptions.mData = oOptions.mDataProp; - } - - if ( oOptions.sType !== undefined ) - { - oCol.sType = oOptions.sType; - oCol._bAutoType = false; - } - - $.extend( oCol, oOptions ); - _fnMap( oCol, oOptions, "sWidth", "sWidthOrig" ); - - /* iDataSort to be applied (backwards compatibility), but aDataSort will take - * priority if defined - */ - if ( oOptions.iDataSort !== undefined ) - { - oCol.aDataSort = [ oOptions.iDataSort ]; + return typeof num === 'string' ? + num.replace( /\./g, '' ).replace( _re_dic[ decimalPoint ], '.' ) : + num; + }; + + + var _isNumber = function ( d, decimalPoint, formatted ) { + var strType = typeof d === 'string'; + + if ( decimalPoint && strType ) { + d = _numToDecimal( d, decimalPoint ); + } + + if ( formatted && strType ) { + d = d.replace( _re_formatted_numeric, '' ); + } + + return !d || d==='-' || (!isNaN( parseFloat(d) ) && isFinite( d )); + }; + + + // A string without HTML in it can be considered to be HTML still + var _isHtml = function ( d ) { + return !d || typeof d === 'string'; + }; + + + var _htmlNumeric = function ( d, decimalPoint, formatted ) { + if ( _empty( d ) ) { + return true; + } + + var html = _isHtml( d ); + return ! html ? + null : + _isNumber( _stripHtml( d ), decimalPoint, formatted ) ? + true : + null; + }; + + + var _pluck = function ( a, prop, prop2 ) { + var out = []; + var i=0, ien=a.length; + + // Could have the test in the loop for slightly smaller code, but speed + // is essential here + if ( prop2 !== undefined ) { + for ( ; i