summaryrefslogtreecommitdiff
path: root/lib/grappelli/media/js
diff options
context:
space:
mode:
authorluxagraf <sng@luxagraf.net>2010-10-23 19:46:20 -0400
committerluxagraf <sng@luxagraf.net>2010-10-23 19:46:20 -0400
commitc59a2a69fb38b92b6c45bcf7431d2b1a3c5dce3c (patch)
tree2fc44ca867839d5e591e21467b6e4526f7a9f080 /lib/grappelli/media/js
parented77da873e675f02f12cbab9be27f342f825444b (diff)
added grappelli, filebrowser, chunks and tagging to lcal repo
Diffstat (limited to 'lib/grappelli/media/js')
-rw-r--r--lib/grappelli/media/js/GRAPPELLI_INFO.TXT57
-rw-r--r--lib/grappelli/media/js/LICENSE-JQUERY.txt20
-rw-r--r--lib/grappelli/media/js/SelectBox.js111
-rw-r--r--lib/grappelli/media/js/SelectFilter2.js117
-rw-r--r--lib/grappelli/media/js/actions.js3
-rw-r--r--lib/grappelli/media/js/actions.min.js3
-rw-r--r--lib/grappelli/media/js/admin/DateTimeShortcuts.js3
-rw-r--r--lib/grappelli/media/js/admin/RelatedObjectLookups.js3
-rw-r--r--lib/grappelli/media/js/admin/ordering.js137
-rw-r--r--lib/grappelli/media/js/calendar.js3
-rw-r--r--lib/grappelli/media/js/collapse.js3
-rw-r--r--lib/grappelli/media/js/collapse.min.js3
-rw-r--r--lib/grappelli/media/js/compress.py47
-rw-r--r--lib/grappelli/media/js/core.js221
-rw-r--r--lib/grappelli/media/js/dateparse.js233
-rw-r--r--lib/grappelli/media/js/documentation.js21
-rw-r--r--lib/grappelli/media/js/getElementsBySelector.js167
-rw-r--r--lib/grappelli/media/js/grappelli.RelatedObjectLookups.js341
-rw-r--r--lib/grappelli/media/js/grappelli.change_list.js317
-rw-r--r--lib/grappelli/media/js/grappelli.init.js4
-rw-r--r--lib/grappelli/media/js/grappelli.js131
-rw-r--r--lib/grappelli/media/js/grappelli.min.js252
-rw-r--r--lib/grappelli/media/js/grappelli.timepicker.js171
-rw-r--r--lib/grappelli/media/js/inlines.js354
-rw-r--r--lib/grappelli/media/js/inlines.min.js380
-rw-r--r--lib/grappelli/media/js/jquery.init.js3
-rw-r--r--lib/grappelli/media/js/jquery.js3
-rw-r--r--lib/grappelli/media/js/jquery.min.js3
-rw-r--r--lib/grappelli/media/js/prepopulate.min.js3
-rw-r--r--lib/grappelli/media/js/timeparse.js94
-rw-r--r--lib/grappelli/media/js/urlify.js140
31 files changed, 3348 insertions, 0 deletions
diff --git a/lib/grappelli/media/js/GRAPPELLI_INFO.TXT b/lib/grappelli/media/js/GRAPPELLI_INFO.TXT
new file mode 100644
index 0000000..c26d607
--- /dev/null
+++ b/lib/grappelli/media/js/GRAPPELLI_INFO.TXT
@@ -0,0 +1,57 @@
+JavaScript infos about grappelli:
+
+grappelli adds much additional features to the django.contrib.admin. to accomplish this the js loaded in grappelli is pretty different to django admin's.
+
+###
+loading order of grappelli:
+###
+
+ * jquery-1.4.2.min.js
+ * jquery-ui-1.8.custom.min.js (with datepicker)
+ * grappelli.init.js
+ * grappelli.timepicker.js
+ * grappelli.RelatedObjectLookups.js
+ * grappelli.js
+ * (optional) grappelli.change_list.js
+
+
+###
+django.admin js removed:
+###
+
+grappelli has its own adminmedia folder. some of django's js files are missing (intentionally)
+
+actions.js, actions.min.js
+jquery.js, jquery.min.js
+collapse.js, collapse.min.js
+calendar.js
+jquery.init.js
+
+###
+django.admin js updated:
+###
+
+datetimeshortcuts.js
+ - outcommented initialization of js for date fields (use jquery-ui datepicker @see grappelli.js)
+RelatedObjectLookups.js
+ Popups:
+ - trigger focus/click event of element to open cancel/save footer in changelist
+ - open popups with 500x920 instead of 500x800
+
+###
+js added by grappelli:
+###
+
+ grappelli.change_list.js
+ - all js magic for change_list
+
+ grappelli.RelatedObjectLookups.js
+ - customization and initialization
+
+ grappelli.js
+ - grappelli wide features like collapsables
+ - hacks like editing dom which is rendered from python code (not customizable via templates)
+
+ media/jquery/*
+ - "own" jquery (use newer version than original)
+ - jquery-ui stuff like datepicker \ No newline at end of file
diff --git a/lib/grappelli/media/js/LICENSE-JQUERY.txt b/lib/grappelli/media/js/LICENSE-JQUERY.txt
new file mode 100644
index 0000000..a4c5bd7
--- /dev/null
+++ b/lib/grappelli/media/js/LICENSE-JQUERY.txt
@@ -0,0 +1,20 @@
+Copyright (c) 2010 John Resig, http://jquery.com/
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file
diff --git a/lib/grappelli/media/js/SelectBox.js b/lib/grappelli/media/js/SelectBox.js
new file mode 100644
index 0000000..f28c861
--- /dev/null
+++ b/lib/grappelli/media/js/SelectBox.js
@@ -0,0 +1,111 @@
+var SelectBox = {
+ cache: new Object(),
+ init: function(id) {
+ var box = document.getElementById(id);
+ var node;
+ SelectBox.cache[id] = new Array();
+ var cache = SelectBox.cache[id];
+ for (var i = 0; (node = box.options[i]); i++) {
+ cache.push({value: node.value, text: node.text, displayed: 1});
+ }
+ },
+ redisplay: function(id) {
+ // Repopulate HTML select box from cache
+ var box = document.getElementById(id);
+ box.options.length = 0; // clear all options
+ for (var i = 0, j = SelectBox.cache[id].length; i < j; i++) {
+ var node = SelectBox.cache[id][i];
+ if (node.displayed) {
+ box.options[box.options.length] = new Option(node.text, node.value, false, false);
+ }
+ }
+ },
+ filter: function(id, text) {
+ // Redisplay the HTML select box, displaying only the choices containing ALL
+ // the words in text. (It's an AND search.)
+ var tokens = text.toLowerCase().split(/\s+/);
+ var node, token;
+ for (var i = 0; (node = SelectBox.cache[id][i]); i++) {
+ node.displayed = 1;
+ for (var j = 0; (token = tokens[j]); j++) {
+ if (node.text.toLowerCase().indexOf(token) == -1) {
+ node.displayed = 0;
+ }
+ }
+ }
+ SelectBox.redisplay(id);
+ },
+ delete_from_cache: function(id, value) {
+ var node, delete_index = null;
+ for (var i = 0; (node = SelectBox.cache[id][i]); i++) {
+ if (node.value == value) {
+ delete_index = i;
+ break;
+ }
+ }
+ var j = SelectBox.cache[id].length - 1;
+ for (var i = delete_index; i < j; i++) {
+ SelectBox.cache[id][i] = SelectBox.cache[id][i+1];
+ }
+ SelectBox.cache[id].length--;
+ },
+ add_to_cache: function(id, option) {
+ SelectBox.cache[id].push({value: option.value, text: option.text, displayed: 1});
+ },
+ cache_contains: function(id, value) {
+ // Check if an item is contained in the cache
+ var node;
+ for (var i = 0; (node = SelectBox.cache[id][i]); i++) {
+ if (node.value == value) {
+ return true;
+ }
+ }
+ return false;
+ },
+ move: function(from, to) {
+ var from_box = document.getElementById(from);
+ var to_box = document.getElementById(to);
+ var option;
+ for (var i = 0; (option = from_box.options[i]); i++) {
+ if (option.selected && SelectBox.cache_contains(from, option.value)) {
+ SelectBox.add_to_cache(to, {value: option.value, text: option.text, displayed: 1});
+ SelectBox.delete_from_cache(from, option.value);
+ }
+ }
+ SelectBox.redisplay(from);
+ SelectBox.redisplay(to);
+ },
+ move_all: function(from, to) {
+ var from_box = document.getElementById(from);
+ var to_box = document.getElementById(to);
+ var option;
+ for (var i = 0; (option = from_box.options[i]); i++) {
+ if (SelectBox.cache_contains(from, option.value)) {
+ SelectBox.add_to_cache(to, {value: option.value, text: option.text, displayed: 1});
+ SelectBox.delete_from_cache(from, option.value);
+ }
+ }
+ SelectBox.redisplay(from);
+ SelectBox.redisplay(to);
+ },
+ sort: function(id) {
+ SelectBox.cache[id].sort( function(a, b) {
+ a = a.text.toLowerCase();
+ b = b.text.toLowerCase();
+ try {
+ if (a > b) return 1;
+ if (a < b) return -1;
+ }
+ catch (e) {
+ // silently fail on IE 'unknown' exception
+ }
+ return 0;
+ } );
+ },
+ select_all: function(id) {
+ var box = document.getElementById(id);
+ for (var i = 0; i < box.options.length; i++) {
+ box.options[i].selected = 'selected';
+ }
+ }
+}
diff --git a/lib/grappelli/media/js/SelectFilter2.js b/lib/grappelli/media/js/SelectFilter2.js
new file mode 100644
index 0000000..9b50cb9
--- /dev/null
+++ b/lib/grappelli/media/js/SelectFilter2.js
@@ -0,0 +1,117 @@
+/*
+SelectFilter2 - Turns a multiple-select box into a filter interface.
+
+Different than SelectFilter because this is coupled to the admin framework.
+
+Requires core.js, SelectBox.js and addevent.js.
+*/
+
+function findForm(node) {
+ // returns the node of the form containing the given node
+ if (node.tagName.toLowerCase() != 'form') {
+ return findForm(node.parentNode);
+ }
+ return node;
+}
+
+var SelectFilter = {
+ init: function(field_id, field_name, is_stacked, admin_media_prefix) {
+ if (field_id.match(/__prefix__/)){
+ // Don't intialize on empty forms.
+ return;
+ }
+ var from_box = document.getElementById(field_id);
+ from_box.id += '_from'; // change its ID
+ from_box.className = 'filtered';
+
+ // Remove <p class="info">, because it just gets in the way.
+ var ps = from_box.parentNode.getElementsByTagName('p');
+ for (var i=0; i<ps.length; i++) {
+ from_box.parentNode.removeChild(ps[i]);
+ }
+
+ // <div class="selector"> or <div class="selector stacked">
+ var selector_div = quickElement('div', from_box.parentNode);
+ selector_div.className = is_stacked ? 'selector stacked' : 'selector';
+
+ // <div class="selector-available">
+ var selector_available = quickElement('div', selector_div, '');
+ selector_available.className = 'selector-available';
+ quickElement('h2', selector_available, interpolate(gettext('Available %s'), [field_name]));
+ var filter_p = quickElement('p', selector_available, '');
+ filter_p.className = 'selector-filter';
+ quickElement('img', filter_p, '', 'src', admin_media_prefix + 'img/admin/selector-search.gif');
+ filter_p.appendChild(document.createTextNode(' '));
+ var filter_input = quickElement('input', filter_p, '', 'type', 'text');
+ filter_input.id = field_id + '_input';
+ selector_available.appendChild(from_box);
+ var choose_all = quickElement('a', selector_available, gettext('Choose all'), 'href', 'javascript: (function(){ SelectBox.move_all("' + field_id + '_from", "' + field_id + '_to"); })()');
+ choose_all.className = 'selector-chooseall';
+
+ // <ul class="selector-chooser">
+ var selector_chooser = quickElement('ul', selector_div, '');
+ selector_chooser.className = 'selector-chooser';
+ var add_link = quickElement('a', quickElement('li', selector_chooser, ''), gettext('Add'), 'href', 'javascript: (function(){ SelectBox.move("' + field_id + '_from","' + field_id + '_to");})()');
+ add_link.className = 'selector-add';
+ var remove_link = quickElement('a', quickElement('li', selector_chooser, ''), gettext('Remove'), 'href', 'javascript: (function(){ SelectBox.move("' + field_id + '_to","' + field_id + '_from");})()');
+ remove_link.className = 'selector-remove';
+
+ // <div class="selector-chosen">
+ var selector_chosen = quickElement('div', selector_div, '');
+ selector_chosen.className = 'selector-chosen';
+ quickElement('h2', selector_chosen, interpolate(gettext('Chosen %s'), [field_name]));
+ var selector_filter = quickElement('p', selector_chosen, gettext('Select your choice(s) and click '));
+ selector_filter.className = 'selector-filter';
+ quickElement('img', selector_filter, '', 'src', admin_media_prefix + (is_stacked ? 'img/admin/selector_stacked-add.gif':'img/admin/selector-add.gif'), 'alt', 'Add');
+ var to_box = quickElement('select', selector_chosen, '', 'id', field_id + '_to', 'multiple', 'multiple', 'size', from_box.size, 'name', from_box.getAttribute('name'));
+ to_box.className = 'filtered';
+ var clear_all = quickElement('a', selector_chosen, gettext('Clear all'), 'href', 'javascript: (function() { SelectBox.move_all("' + field_id + '_to", "' + field_id + '_from");})()');
+ clear_all.className = 'selector-clearall';
+
+ from_box.setAttribute('name', from_box.getAttribute('name') + '_old');
+
+ // Set up the JavaScript event handlers for the select box filter interface
+ addEvent(filter_input, 'keyup', function(e) { SelectFilter.filter_key_up(e, field_id); });
+ addEvent(filter_input, 'keydown', function(e) { SelectFilter.filter_key_down(e, field_id); });
+ addEvent(from_box, 'dblclick', function() { SelectBox.move(field_id + '_from', field_id + '_to'); });
+ addEvent(to_box, 'dblclick', function() { SelectBox.move(field_id + '_to', field_id + '_from'); });
+ addEvent(findForm(from_box), 'submit', function() { SelectBox.select_all(field_id + '_to'); });
+ SelectBox.init(field_id + '_from');
+ SelectBox.init(field_id + '_to');
+ // Move selected from_box options to to_box
+ SelectBox.move(field_id + '_from', field_id + '_to');
+ },
+ filter_key_up: function(event, field_id) {
+ from = document.getElementById(field_id + '_from');
+ // don't submit form if user pressed Enter
+ if ((event.which && event.which == 13) || (event.keyCode && event.keyCode == 13)) {
+ from.selectedIndex = 0;
+ SelectBox.move(field_id + '_from', field_id + '_to');
+ from.selectedIndex = 0;
+ return false;
+ }
+ var temp = from.selectedIndex;
+ SelectBox.filter(field_id + '_from', document.getElementById(field_id + '_input').value);
+ from.selectedIndex = temp;
+ return true;
+ },
+ filter_key_down: function(event, field_id) {
+ from = document.getElementById(field_id + '_from');
+ // right arrow -- move across
+ if ((event.which && event.which == 39) || (event.keyCode && event.keyCode == 39)) {
+ var old_index = from.selectedIndex;
+ SelectBox.move(field_id + '_from', field_id + '_to');
+ from.selectedIndex = (old_index == from.length) ? from.length - 1 : old_index;
+ return false;
+ }
+ // down arrow -- wrap around
+ if ((event.which && event.which == 40) || (event.keyCode && event.keyCode == 40)) {
+ from.selectedIndex = (from.length == from.selectedIndex + 1) ? 0 : from.selectedIndex + 1;
+ }
+ // up arrow -- wrap around
+ if ((event.which && event.which == 38) || (event.keyCode && event.keyCode == 38)) {
+ from.selectedIndex = (from.selectedIndex == 0) ? from.length - 1 : from.selectedIndex - 1;
+ }
+ return true;
+ }
+}
diff --git a/lib/grappelli/media/js/actions.js b/lib/grappelli/media/js/actions.js
new file mode 100644
index 0000000..b8a75ab
--- /dev/null
+++ b/lib/grappelli/media/js/actions.js
@@ -0,0 +1,3 @@
+// dropped
+// not used in grappelli
+// kept this file to prevent 404 \ No newline at end of file
diff --git a/lib/grappelli/media/js/actions.min.js b/lib/grappelli/media/js/actions.min.js
new file mode 100644
index 0000000..b8a75ab
--- /dev/null
+++ b/lib/grappelli/media/js/actions.min.js
@@ -0,0 +1,3 @@
+// dropped
+// not used in grappelli
+// kept this file to prevent 404 \ No newline at end of file
diff --git a/lib/grappelli/media/js/admin/DateTimeShortcuts.js b/lib/grappelli/media/js/admin/DateTimeShortcuts.js
new file mode 100644
index 0000000..b8a75ab
--- /dev/null
+++ b/lib/grappelli/media/js/admin/DateTimeShortcuts.js
@@ -0,0 +1,3 @@
+// dropped
+// not used in grappelli
+// kept this file to prevent 404 \ No newline at end of file
diff --git a/lib/grappelli/media/js/admin/RelatedObjectLookups.js b/lib/grappelli/media/js/admin/RelatedObjectLookups.js
new file mode 100644
index 0000000..6ab56dc
--- /dev/null
+++ b/lib/grappelli/media/js/admin/RelatedObjectLookups.js
@@ -0,0 +1,3 @@
+// dropped
+// use grappelli.RelatedObjectLookups.js instead
+// kept this file to prevent 404 \ No newline at end of file
diff --git a/lib/grappelli/media/js/admin/ordering.js b/lib/grappelli/media/js/admin/ordering.js
new file mode 100644
index 0000000..53c42f3
--- /dev/null
+++ b/lib/grappelli/media/js/admin/ordering.js
@@ -0,0 +1,137 @@
+addEvent(window, 'load', reorder_init);
+
+var lis;
+var top = 0;
+var left = 0;
+var height = 30;
+
+function reorder_init() {
+ lis = document.getElementsBySelector('ul#orderthese li');
+ var input = document.getElementsBySelector('input[name=order_]')[0];
+ setOrder(input.value.split(','));
+ input.disabled = true;
+ draw();
+ // Now initialise the dragging behaviour
+ var limit = (lis.length - 1) * height;
+ for (var i = 0; i < lis.length; i++) {
+ var li = lis[i];
+ var img = document.getElementById('handle'+li.id);
+ li.style.zIndex = 1;
+ Drag.init(img, li, left + 10, left + 10, top + 10, top + 10 + limit);
+ li.onDragStart = startDrag;
+ li.onDragEnd = endDrag;
+ img.style.cursor = 'move';
+ }
+}
+
+function submitOrderForm() {
+ var inputOrder = document.getElementsBySelector('input[name=order_]')[0];
+ inputOrder.value = getOrder();
+ inputOrder.disabled=false;
+}
+
+function startDrag() {
+ this.style.zIndex = '10';
+ this.className = 'dragging';
+}
+
+function endDrag(x, y) {
+ this.style.zIndex = '1';
+ this.className = '';
+ // Work out how far along it has been dropped, using x co-ordinate
+ var oldIndex = this.index;
+ var newIndex = Math.round((y - 10 - top) / height);
+ // 'Snap' to the correct position
+ this.style.top = (10 + top + newIndex * height) + 'px';
+ this.index = newIndex;
+ moveItem(oldIndex, newIndex);
+}
+
+function moveItem(oldIndex, newIndex) {
+ // Swaps two items, adjusts the index and left co-ord for all others
+ if (oldIndex == newIndex) {
+ return; // Nothing to swap;
+ }
+ var direction, lo, hi;
+ if (newIndex > oldIndex) {
+ lo = oldIndex;
+ hi = newIndex;
+ direction = -1;
+ } else {
+ direction = 1;
+ hi = oldIndex;
+ lo = newIndex;
+ }
+ var lis2 = new Array(); // We will build the new order in this array
+ for (var i = 0; i < lis.length; i++) {
+ if (i < lo || i > hi) {
+ // Position of items not between the indexes is unaffected
+ lis2[i] = lis[i];
+ continue;
+ } else if (i == newIndex) {
+ lis2[i] = lis[oldIndex];
+ continue;
+ } else {
+ // Item is between the two indexes - move it along 1
+ lis2[i] = lis[i - direction];
+ }
+ }
+ // Re-index everything
+ reIndex(lis2);
+ lis = lis2;
+ draw();
+// document.getElementById('hiddenOrder').value = getOrder();
+ document.getElementsBySelector('input[name=order_]')[0].value = getOrder();
+}
+
+function reIndex(lis) {
+ for (var i = 0; i < lis.length; i++) {
+ lis[i].index = i;
+ }
+}
+
+function draw() {
+ for (var i = 0; i < lis.length; i++) {
+ var li = lis[i];
+ li.index = i;
+ li.style.position = 'absolute';
+ li.style.left = (10 + left) + 'px';
+ li.style.top = (10 + top + (i * height)) + 'px';
+ }
+}
+
+function getOrder() {
+ var order = new Array(lis.length);
+ for (var i = 0; i < lis.length; i++) {
+ order[i] = lis[i].id.substring(1, 100);
+ }
+ return order.join(',');
+}
+
+function setOrder(id_list) {
+ /* Set the current order to match the lsit of IDs */
+ var temp_lis = new Array();
+ for (var i = 0; i < id_list.length; i++) {
+ var id = 'p' + id_list[i];
+ temp_lis[temp_lis.length] = document.getElementById(id);
+ }
+ reIndex(temp_lis);
+ lis = temp_lis;
+ draw();
+}
+
+function addEvent(elm, evType, fn, useCapture)
+// addEvent and removeEvent
+// cross-browser event handling for IE5+, NS6 and Mozilla
+// By Scott Andrew
+{
+ if (elm.addEventListener){
+ elm.addEventListener(evType, fn, useCapture);
+ return true;
+ } else if (elm.attachEvent){
+ var r = elm.attachEvent("on"+evType, fn);
+ return r;
+ } else {
+ elm['on'+evType] = fn;
+ }
+}
diff --git a/lib/grappelli/media/js/calendar.js b/lib/grappelli/media/js/calendar.js
new file mode 100644
index 0000000..b8a75ab
--- /dev/null
+++ b/lib/grappelli/media/js/calendar.js
@@ -0,0 +1,3 @@
+// dropped
+// not used in grappelli
+// kept this file to prevent 404 \ No newline at end of file
diff --git a/lib/grappelli/media/js/collapse.js b/lib/grappelli/media/js/collapse.js
new file mode 100644
index 0000000..b8a75ab
--- /dev/null
+++ b/lib/grappelli/media/js/collapse.js
@@ -0,0 +1,3 @@
+// dropped
+// not used in grappelli
+// kept this file to prevent 404 \ No newline at end of file
diff --git a/lib/grappelli/media/js/collapse.min.js b/lib/grappelli/media/js/collapse.min.js
new file mode 100644
index 0000000..b8a75ab
--- /dev/null
+++ b/lib/grappelli/media/js/collapse.min.js
@@ -0,0 +1,3 @@
+// dropped
+// not used in grappelli
+// kept this file to prevent 404 \ No newline at end of file
diff --git a/lib/grappelli/media/js/compress.py b/lib/grappelli/media/js/compress.py
new file mode 100644
index 0000000..8d2caa2
--- /dev/null
+++ b/lib/grappelli/media/js/compress.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+import os
+import optparse
+import subprocess
+import sys
+
+here = os.path.dirname(__file__)
+
+def main():
+ usage = "usage: %prog [file1..fileN]"
+ description = """With no file paths given this script will automatically
+compress all jQuery-based files of the admin app. Requires the Google Closure
+Compiler library and Java version 6 or later."""
+ parser = optparse.OptionParser(usage, description=description)
+ parser.add_option("-c", dest="compiler", default="~/bin/compiler.jar",
+ help="path to Closure Compiler jar file")
+ parser.add_option("-v", "--verbose",
+ action="store_true", dest="verbose")
+ parser.add_option("-q", "--quiet",
+ action="store_false", dest="verbose")
+ (options, args) = parser.parse_args()
+
+ compiler = os.path.expanduser(options.compiler)
+ if not os.path.exists(compiler):
+ sys.exit("Google Closure compiler jar file %s not found. Please use the -c option to specify the path." % compiler)
+
+ if not args:
+ if options.verbose:
+ sys.stdout.write("No filenames given; defaulting to admin scripts\n")
+ args = [os.path.join(here, f) for f in [
+ "actions.js", "collapse.js", "inlines.js", "prepopulate.js"]]
+
+ for arg in args:
+ if not arg.endswith(".js"):
+ arg = arg + ".js"
+ to_compress = os.path.expanduser(arg)
+ if os.path.exists(to_compress):
+ to_compress_min = "%s.min.js" % "".join(arg.rsplit(".js"))
+ cmd = "java -jar %s --js %s --js_output_file %s" % (compiler, to_compress, to_compress_min)
+ if options.verbose:
+ sys.stdout.write("Running: %s\n" % cmd)
+ subprocess.call(cmd.split())
+ else:
+ sys.stdout.write("File %s not found. Sure it exists?\n" % to_compress)
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/grappelli/media/js/core.js b/lib/grappelli/media/js/core.js
new file mode 100644
index 0000000..3ca8ad0
--- /dev/null
+++ b/lib/grappelli/media/js/core.js
@@ -0,0 +1,221 @@
+// Core javascript helper functions
+
+// basic browser identification & version
+var isOpera = (navigator.userAgent.indexOf("Opera")>=0) && parseFloat(navigator.appVersion);
+var isIE = ((document.all) && (!isOpera)) && parseFloat(navigator.appVersion.split("MSIE ")[1].split(";")[0]);
+
+// Cross-browser event handlers.
+function addEvent(obj, evType, fn) {
+ if (obj.addEventListener) {
+ obj.addEventListener(evType, fn, false);
+ return true;
+ } else if (obj.attachEvent) {
+ var r = obj.attachEvent("on" + evType, fn);
+ return r;
+ } else {
+ return false;
+ }
+}
+
+function removeEvent(obj, evType, fn) {
+ if (obj.removeEventListener) {
+ obj.removeEventListener(evType, fn, false);
+ return true;
+ } else if (obj.detachEvent) {
+ obj.detachEvent("on" + evType, fn);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+// quickElement(tagType, parentReference, textInChildNode, [, attribute, attributeValue ...]);
+function quickElement() {
+ var obj = document.createElement(arguments[0]);
+ if (arguments[2] != '' && arguments[2] != null) {
+ var textNode = document.createTextNode(arguments[2]);
+ obj.appendChild(textNode);
+ }
+ var len = arguments.length;
+ for (var i = 3; i < len; i += 2) {
+ obj.setAttribute(arguments[i], arguments[i+1]);
+ }
+ arguments[1].appendChild(obj);
+ return obj;
+}
+
+// ----------------------------------------------------------------------------
+// Cross-browser xmlhttp object
+// from http://jibbering.com/2002/4/httprequest.html
+// ----------------------------------------------------------------------------
+var xmlhttp;
+/*@cc_on @*/
+/*@if (@_jscript_version >= 5)
+ try {
+ xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
+ } catch (e) {
+ try {
+ xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
+ } catch (E) {
+ xmlhttp = false;
+ }
+ }
+@else
+ xmlhttp = false;
+@end @*/
+if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
+ xmlhttp = new XMLHttpRequest();
+}
+
+// ----------------------------------------------------------------------------
+// Find-position functions by PPK
+// See http://www.quirksmode.org/js/findpos.html
+// ----------------------------------------------------------------------------
+function findPosX(obj) {
+ var curleft = 0;
+ if (obj.offsetParent) {
+ while (obj.offsetParent) {
+ curleft += obj.offsetLeft - ((isOpera) ? 0 : obj.scrollLeft);
+ obj = obj.offsetParent;
+ }
+ // IE offsetParent does not include the top-level
+ if (isIE && obj.parentElement){
+ curleft += obj.offsetLeft - obj.scrollLeft;
+ }
+ } else if (obj.x) {
+ curleft += obj.x;
+ }
+ return curleft;
+}
+
+function findPosY(obj) {
+ var curtop = 0;
+ if (obj.offsetParent) {
+ while (obj.offsetParent) {
+ curtop += obj.offsetTop - ((isOpera) ? 0 : obj.scrollTop);
+ obj = obj.offsetParent;
+ }
+ // IE offsetParent does not include the top-level
+ if (isIE && obj.parentElement){
+ curtop += obj.offsetTop - obj.scrollTop;
+ }
+ } else if (obj.y) {
+ curtop += obj.y;
+ }
+ return curtop;
+}
+
+//-----------------------------------------------------------------------------
+// Date object extensions
+// ----------------------------------------------------------------------------
+Date.prototype.getCorrectYear = function() {
+ // Date.getYear() is unreliable --
+ // see http://www.quirksmode.org/js/introdate.html#year
+ var y = this.getYear() % 100;
+ return (y < 38) ? y + 2000 : y + 1900;
+}
+
+Date.prototype.getTwelveHours = function() {
+ hours = this.getHours();
+ if (hours == 0) {
+ return 12;
+ }
+ else {
+ return hours <= 12 ? hours : hours-12
+ }
+}
+
+Date.prototype.getTwoDigitMonth = function() {
+ return (this.getMonth() < 9) ? '0' + (this.getMonth()+1) : (this.getMonth()+1);
+}
+
+Date.prototype.getTwoDigitDate = function() {
+ return (this.getDate() < 10) ? '0' + this.getDate() : this.getDate();
+}
+
+Date.prototype.getTwoDigitTwelveHour = function() {
+ return (this.getTwelveHours() < 10) ? '0' + this.getTwelveHours() : this.getTwelveHours();
+}
+
+Date.prototype.getTwoDigitHour = function() {
+ return (this.getHours() < 10) ? '0' + this.getHours() : this.getHours();
+}
+
+Date.prototype.getTwoDigitMinute = function() {
+ return (this.getMinutes() < 10) ? '0' + this.getMinutes() : this.getMinutes();
+}
+
+Date.prototype.getTwoDigitSecond = function() {
+ return (this.getSeconds() < 10) ? '0' + this.getSeconds() : this.getSeconds();
+}
+
+Date.prototype.getISODate = function() {
+ return this.getCorrectYear() + '-' + this.getTwoDigitMonth() + '-' + this.getTwoDigitDate();
+}
+
+Date.prototype.getHourMinute = function() {
+ return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute();
+}
+
+Date.prototype.getHourMinuteSecond = function() {
+ return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute() + ':' + this.getTwoDigitSecond();
+}
+
+Date.prototype.strftime = function(format) {
+ var fields = {
+ c: this.toString(),
+ d: this.getTwoDigitDate(),
+ H: this.getTwoDigitHour(),
+ I: this.getTwoDigitTwelveHour(),
+ m: this.getTwoDigitMonth(),
+ M: this.getTwoDigitMinute(),
+ p: (this.getHours() >= 12) ? 'PM' : 'AM',
+ S: this.getTwoDigitSecond(),
+ w: '0' + this.getDay(),
+ x: this.toLocaleDateString(),
+ X: this.toLocaleTimeString(),
+ y: ('' + this.getFullYear()).substr(2, 4),
+ Y: '' + this.getFullYear(),
+ '%' : '%'
+ };
+ var result = '', i = 0;
+ while (i < format.length) {
+ if (format.charAt(i) === '%') {
+ result = result + fields[format.charAt(i + 1)];
+ ++i;
+ }
+ else {
+ result = result + format.charAt(i);
+ }
+ ++i;
+ }
+ return result;
+}
+
+// ----------------------------------------------------------------------------
+// String object extensions
+// ----------------------------------------------------------------------------
+String.prototype.pad_left = function(pad_length, pad_string) {
+ var new_string = this;
+ for (var i = 0; new_string.length < pad_length; i++) {
+ new_string = pad_string + new_string;
+ }
+ return new_string;
+}
+
+// ----------------------------------------------------------------------------
+// Get the computed style for and element
+// ----------------------------------------------------------------------------
+function getStyle(oElm, strCssRule){
+ var strValue = "";
+ if(document.defaultView && document.defaultView.getComputedStyle){
+ strValue = document.defaultView.getComputedStyle(oElm, "").getPropertyValue(strCssRule);
+ }
+ else if(oElm.currentStyle){
+ strCssRule = strCssRule.replace(/\-(\w)/g, function (strMatch, p1){
+ return p1.toUpperCase();
+ });
+ strValue = oElm.currentStyle[strCssRule];
+ }
+ return strValue;
+}
diff --git a/lib/grappelli/media/js/dateparse.js b/lib/grappelli/media/js/dateparse.js
new file mode 100644
index 0000000..e1c870e
--- /dev/null
+++ b/lib/grappelli/media/js/dateparse.js
@@ -0,0 +1,233 @@
+/* 'Magic' date parsing, by Simon Willison (6th October 2003)
+ http://simon.incutio.com/archive/2003/10/06/betterDateInput
+ Adapted for 6newslawrence.com, 28th January 2004
+*/
+
+/* Finds the index of the first occurence of item in the array, or -1 if not found */
+if (typeof Array.prototype.indexOf == 'undefined') {
+ Array.prototype.indexOf = function(item) {
+ var len = this.length;
+ for (var i = 0; i < len; i++) {
+ if (this[i] == item) {
+ return i;
+ }
+ }
+ return -1;
+ };
+}
+/* Returns an array of items judged 'true' by the passed in test function */
+if (typeof Array.prototype.filter == 'undefined') {
+ Array.prototype.filter = function(test) {
+ var matches = [];
+ var len = this.length;
+ for (var i = 0; i < len; i++) {
+ if (test(this[i])) {
+ matches[matches.length] = this[i];
+ }
+ }
+ return matches;
+ };
+}
+
+var monthNames = gettext("January February March April May June July August September October November December").split(" ");
+var weekdayNames = gettext("Sunday Monday Tuesday Wednesday Thursday Friday Saturday").split(" ");
+
+/* Takes a string, returns the index of the month matching that string, throws
+ an error if 0 or more than 1 matches
+*/
+function parseMonth(month) {
+ var matches = monthNames.filter(function(item) {
+ return new RegExp("^" + month, "i").test(item);
+ });
+ if (matches.length == 0) {
+ throw new Error("Invalid month string");
+ }
+ if (matches.length > 1) {
+ throw new Error("Ambiguous month");
+ }
+ return monthNames.indexOf(matches[0]);
+}
+/* Same as parseMonth but for days of the week */
+function parseWeekday(weekday) {
+ var matches = weekdayNames.filter(function(item) {
+ return new RegExp("^" + weekday, "i").test(item);
+ });
+ if (matches.length == 0) {
+ throw new Error("Invalid day string");
+ }
+ if (matches.length > 1) {
+ throw new Error("Ambiguous weekday");
+ }
+ return weekdayNames.indexOf(matches[0]);
+}
+
+/* Array of objects, each has 're', a regular expression and 'handler', a
+ function for creating a date from something that matches the regular
+ expression. Handlers may throw errors if string is unparseable.
+*/
+var dateParsePatterns = [
+ // Today
+ { re: /^tod/i,
+ handler: function() {
+ return new Date();
+ }
+ },
+ // Tomorrow
+ { re: /^tom/i,
+ handler: function() {
+ var d = new Date();
+ d.setDate(d.getDate() + 1);
+ return d;
+ }
+ },
+ // Yesterday
+ { re: /^yes/i,
+ handler: function() {
+ var d = new Date();
+ d.setDate(d.getDate() - 1);
+ return d;
+ }
+ },
+ // 4th
+ { re: /^(\d{1,2})(st|nd|rd|th)?$/i,
+ handler: function(bits) {
+ var d = new Date();
+ d.setDate(parseInt(bits[1], 10));
+ return d;
+ }
+ },
+ // 4th Jan
+ { re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+)$/i,
+ handler: function(bits) {
+ var d = new Date();
+ d.setDate(parseInt(bits[1], 10));
+ d.setMonth(parseMonth(bits[2]));
+ return d;
+ }
+ },
+ // 4th Jan 2003
+ { re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+),? (\d{4})$/i,
+ handler: function(bits) {
+ var d = new Date();
+ d.setDate(parseInt(bits[1], 10));
+ d.setMonth(parseMonth(bits[2]));
+ d.setYear(bits[3]);
+ return d;
+ }
+ },
+ // Jan 4th
+ { re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?$/i,
+ handler: function(bits) {
+ var d = new Date();
+ d.setDate(parseInt(bits[2], 10));
+ d.setMonth(parseMonth(bits[1]));
+ return d;
+ }
+ },
+ // Jan 4th 2003
+ { re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?,? (\d{4})$/i,
+ handler: function(bits) {
+ var d = new Date();
+ d.setDate(parseInt(bits[2], 10));
+ d.setMonth(parseMonth(bits[1]));
+ d.setYear(bits[3]);
+ return d;
+ }
+ },
+ // next Tuesday - this is suspect due to weird meaning of "next"
+ { re: /^next (\w+)$/i,
+ handler: function(bits) {
+ var d = new Date();
+ var day = d.getDay();
+ var newDay = parseWeekday(bits[1]);
+ var addDays = newDay - day;
+ if (newDay <= day) {
+ addDays += 7;
+ }
+ d.setDate(d.getDate() + addDays);
+ return d;
+ }
+ },
+ // last Tuesday
+ { re: /^last (\w+)$/i,
+ handler: function(bits) {
+ throw new Error("Not yet implemented");
+ }
+ },
+ // mm/dd/yyyy (American style)
+ { re: /(\d{1,2})\/(\d{1,2})\/(\d{4})/,
+ handler: function(bits) {
+ var d = new Date();
+ d.setYear(bits[3]);
+ d.setDate(parseInt(bits[2], 10));
+ d.setMonth(parseInt(bits[1], 10) - 1); // Because months indexed from 0
+ return d;
+ }
+ },
+ // yyyy-mm-dd (ISO style)
+ { re: /(\d{4})-(\d{1,2})-(\d{1,2})/,
+ handler: function(bits) {
+ var d = new Date();
+ d.setYear(parseInt(bits[1]));
+ d.setMonth(parseInt(bits[2], 10) - 1);
+ d.setDate(parseInt(bits[3], 10));
+ return d;
+ }
+ },
+];
+
+function parseDateString(s) {
+ for (var i = 0; i < dateParsePatterns.length; i++) {
+ var re = dateParsePatterns[i].re;
+ var handler = dateParsePatterns[i].handler;
+ var bits = re.exec(s);
+ if (bits) {
+ return handler(bits);
+ }
+ }
+ throw new Error("Invalid date string");
+}
+
+function fmt00(x) {
+ // fmt00: Tags leading zero onto numbers 0 - 9.
+ // Particularly useful for displaying results from Date methods.
+ //
+ if (Math.abs(parseInt(x)) < 10){
+ x = "0"+ Math.abs(x);
+ }
+ return x;
+}
+
+function parseDateStringISO(s) {
+ try {
+ var d = parseDateString(s);
+ return d.getFullYear() + '-' + (fmt00(d.getMonth() + 1)) + '-' + fmt00(d.getDate())
+ }
+ catch (e) { return s; }
+}
+function magicDate(input) {
+ var messagespan = input.id + 'Msg';
+ try {
+ var d = parseDateString(input.value);
+ input.value = d.getFullYear() + '-' + (fmt00(d.getMonth() + 1)) + '-' +
+ fmt00(d.getDate());
+ input.className = '';
+ // Human readable date
+ if (document.getElementById(messagespan)) {
+ document.getElementById(messagespan).firstChild.nodeValue = d.toDateString();
+ document.getElementById(messagespan).className = 'normal';
+ }
+ }
+ catch (e) {
+ input.className = 'error';
+ var message = e.message;
+ // Fix for IE6 bug
+ if (message.indexOf('is null or not an object') > -1) {
+ message = 'Invalid date string';
+ }
+ if (document.getElementById(messagespan)) {
+ document.getElementById(messagespan).firstChild.nodeValue = message;
+ document.getElementById(messagespan).className = 'error';
+ }
+ }
+}
diff --git a/lib/grappelli/media/js/documentation.js b/lib/grappelli/media/js/documentation.js
new file mode 100644
index 0000000..b00c120
--- /dev/null
+++ b/lib/grappelli/media/js/documentation.js
@@ -0,0 +1,21 @@
+(function($) {
+ $(document).ready(function(){
+ // Correct the position of anchors (because "#header" & "#breadcrumbs" have a "position:fixed")
+ $('.table-of-contents a').click(function(){
+ var myReference = ".rte " + $(this).attr('href');
+ // if collapsible
+ var myParentCollapsible = $(myReference).parent().parent();
+ if ($(myParentCollapsible).hasClass('closed')){
+ $(myParentCollapsible).toggleClass('open').toggleClass('closed');
+ }
+ // anchor offset
+ var targetOffset = $(myReference).offset().top;
+ $('html,body').scrollTop(targetOffset - 60);
+ return(false);
+ })
+ // Remove emtpy elements: wrkaround for problem reported in django-ticket #11817
+ $('.rte h4:empty').remove();
+ $('.rte p:empty').remove();
+ $('.rte hr').remove();
+ });
+})(django.jQuery); \ No newline at end of file
diff --git a/lib/grappelli/media/js/getElementsBySelector.js b/lib/grappelli/media/js/getElementsBySelector.js
new file mode 100644
index 0000000..15b57a1
--- /dev/null
+++ b/lib/grappelli/media/js/getElementsBySelector.js
@@ -0,0 +1,167 @@
+/* document.getElementsBySelector(selector)
+ - returns an array of element objects from the current document
+ matching the CSS selector. Selectors can contain element names,
+ class names and ids and can be nested. For example:
+
+ elements = document.getElementsBySelect('div#main p a.external')
+
+ Will return an array of all 'a' elements with 'external' in their
+ class attribute that are contained inside 'p' elements that are
+ contained inside the 'div' element which has id="main"
+
+ New in version 0.4: Support for CSS2 and CSS3 attribute selectors:
+ See http://www.w3.org/TR/css3-selectors/#attribute-selectors
+
+ Version 0.4 - Simon Willison, March 25th 2003
+ -- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows
+ -- Opera 7 fails
+*/
+
+function getAllChildren(e) {
+ // Returns all children of element. Workaround required for IE5/Windows. Ugh.
+ return e.all ? e.all : e.getElementsByTagName('*');
+}
+
+document.getElementsBySelector = function(selector) {
+ // Attempt to fail gracefully in lesser browsers
+ if (!document.getElementsByTagName) {
+ return new Array();
+ }
+ // Split selector in to tokens
+ var tokens = selector.split(' ');
+ var currentContext = new Array(document);
+ for (var i = 0; i < tokens.length; i++) {
+ token = tokens[i].replace(/^\s+/,'').replace(/\s+$/,'');;
+ if (token.indexOf('#') > -1) {
+ // Token is an ID selector
+ var bits = token.split('#');
+ var tagName = bits[0];
+ var id = bits[1];
+ var element = document.getElementById(id);
+ if (!element || (tagName && element.nodeName.toLowerCase() != tagName)) {
+ // ID not found or tag with that ID not found, return false.
+ return new Array();
+ }
+ // Set currentContext to contain just this element
+ currentContext = new Array(element);
+ continue; // Skip to next token
+ }
+ if (token.indexOf('.') > -1) {
+ // Token contains a class selector
+ var bits = token.split('.');
+ var tagName = bits[0];
+ var className = bits[1];
+ if (!tagName) {
+ tagName = '*';
+ }
+ // Get elements matching tag, filter them for class selector
+ var found = new Array;
+ var foundCount = 0;
+ for (var h = 0; h < currentContext.length; h++) {
+ var elements;
+ if (tagName == '*') {
+ elements = getAllChildren(currentContext[h]);
+ } else {
+ try {
+ elements = currentContext[h].getElementsByTagName(tagName);
+ }
+ catch(e) {
+ elements = [];
+ }
+ }
+ for (var j = 0; j < elements.length; j++) {
+ found[foundCount++] = elements[j];
+ }
+ }
+ currentContext = new Array;
+ var currentContextIndex = 0;
+ for (var k = 0; k < found.length; k++) {
+ if (found[k].className && found[k].className.match(new RegExp('\\b'+className+'\\b'))) {
+ currentContext[currentContextIndex++] = found[k];
+ }
+ }
+ continue; // Skip to next token
+ }
+ // Code to deal with attribute selectors
+ if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) {
+ var tagName = RegExp.$1;
+ var attrName = RegExp.$2;
+ var attrOperator = RegExp.$3;
+ var attrValue = RegExp.$4;
+ if (!tagName) {
+ tagName = '*';
+ }
+ // Grab all of the tagName elements within current context
+ var found = new Array;
+ var foundCount = 0;
+ for (var h = 0; h < currentContext.length; h++) {
+ var elements;
+ if (tagName == '*') {
+ elements = getAllChildren(currentContext[h]);
+ } else {
+ elements = currentContext[h].getElementsByTagName(tagName);
+ }
+ for (var j = 0; j < elements.length; j++) {
+ found[foundCount++] = elements[j];
+ }
+ }
+ currentContext = new Array;
+ var currentContextIndex = 0;
+ var checkFunction; // This function will be used to filter the elements
+ switch (attrOperator) {
+ case '=': // Equality
+ checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue); };
+ break;
+ case '~': // Match one of space seperated words
+ checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('\\b'+attrValue+'\\b'))); };
+ break;
+ case '|': // Match start with value followed by optional hyphen
+ checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))); };
+ break;
+ case '^': // Match starts with value
+ checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0); };
+ break;
+ case '$': // Match ends with value - fails with "Warning" in Opera 7
+ checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); };
+ break;
+ case '*': // Match ends with value
+ checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1); };
+ break;
+ default :
+ // Just test for existence of attribute
+ checkFunction = function(e) { return e.getAttribute(attrName); };
+ }
+ currentContext = new Array;
+ var currentContextIndex = 0;
+ for (var k = 0; k < found.length; k++) {
+ if (checkFunction(found[k])) {
+ currentContext[currentContextIndex++] = found[k];
+ }
+ }
+ // alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue);
+ continue; // Skip to next token
+ }
+ // If we get here, token is JUST an element (not a class or ID selector)
+ tagName = token;
+ var found = new Array;
+ var foundCount = 0;
+ for (var h = 0; h < currentContext.length; h++) {
+ var elements = currentContext[h].getElementsByTagName(tagName);
+ for (var j = 0; j < elements.length; j++) {
+ found[foundCount++] = elements[j];
+ }
+ }
+ currentContext = found;
+ }
+ return currentContext;
+}
+
+/* That revolting regular expression explained
+/^(\w+)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/
+ \---/ \---/\-------------/ \-------/
+ | | | |
+ | | | The value
+ | | ~,|,^,$,* or =
+ | Attribute
+ Tag
+*/
diff --git a/lib/grappelli/media/js/grappelli.RelatedObjectLookups.js b/lib/grappelli/media/js/grappelli.RelatedObjectLookups.js
new file mode 100644
index 0000000..2857a39
--- /dev/null
+++ b/lib/grappelli/media/js/grappelli.RelatedObjectLookups.js
@@ -0,0 +1,341 @@
+// TODO: klemens: drop ADMIN_URL
+
+// Handles related-objects functionality: lookup link for raw_id_fields
+// and Add Another links.
+
+function html_unescape(text) {
+ // Unescape a string that was escaped using django.utils.html.escape.
+ text = text.replace(/&lt;/g, '<');
+ text = text.replace(/&gt;/g, '>');
+ text = text.replace(/&quot;/g, '"');
+ text = text.replace(/&#39;/g, "'");
+ text = text.replace(/&amp;/g, '&');
+ return text;
+}
+
+// IE doesn't accept periods or dashes in the window name, but the element IDs
+// we use to generate popup window names may contain them, therefore we map them
+// to allowed characters in a reversible way so that we can locate the correct
+// element when the popup window is dismissed.
+function id_to_windowname(text) {
+ text = text.replace(/\./g, '__dot__');
+ text = text.replace(/\-/g, '__dash__');
+ return text;
+}
+
+function windowname_to_id(text) {
+ text = text.replace(/__dot__/g, '.');
+ text = text.replace(/__dash__/g, '-');
+ return text;
+}
+
+var CHAR_MAX_LENGTH = 30;
+
+// customized from RelatedObjectLoopups.js
+function showRelatedObjectLookupPopup(triggeringLink) {
+ var name = triggeringLink.id.replace(/^lookup_/, '');
+ name = id_to_windowname(name);
+ var href;
+ if (triggeringLink.href.search(/\?/) >= 0) {
+ href = triggeringLink.href + '&pop=1';
+ } else {
+ href = triggeringLink.href + '?pop=1';
+ }
+ //grappelli custom
+ var win = window.open(href, name, 'height=500,width=980,resizable=yes,scrollbars=yes');
+ // end
+ win.focus();
+ return false;
+}
+
+// customized from RelatedObjectLoopups.js
+function dismissRelatedLookupPopup(win, chosenId) {
+ var name = windowname_to_id(win.name);
+ var elem = document.getElementById(name);
+ if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) {
+ elem.value += ',' + chosenId;
+ } else {
+ document.getElementById(name).value = chosenId;
+ }
+ // grappelli custom
+ elem.focus();
+ // end
+ win.close();
+}
+
+// customized from RelatedObjectLoopups.js
+function showAddAnotherPopup(triggeringLink) {
+ var name = triggeringLink.id.replace(/^add_/, '');
+ name = id_to_windowname(name);
+ href = triggeringLink.href;
+ if (href.indexOf('?') == -1) {
+ href += '?_popup=1';
+ } else {
+ href += '&_popup=1';
+ }
+ var win = window.open(href, name, 'height=500,width=1000,resizable=yes,scrollbars=yes');
+ win.focus();
+ return false;
+}
+
+// customized from RelatedObjectLoopups.js
+function dismissAddAnotherPopup(win, newId, newRepr) {
+ // newId and newRepr are expected to have previously been escaped by
+ // django.utils.html.escape.
+ newId = html_unescape(newId);
+ newRepr = html_unescape(newRepr);
+ var name = windowname_to_id(win.name);
+ var elem = document.getElementById(name);
+ if (elem) {
+ if (elem.nodeName == 'SELECT') {
+ var o = new Option(newRepr, newId);
+ elem.options[elem.options.length] = o;
+ o.selected = true;
+ } else if (elem.nodeName == 'INPUT') {
+ if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) {
+ elem.value += ',' + newId;
+ } else {
+ elem.value = newId;
+ }
+ // NOTE: via http://code.djangoproject.com/attachment/ticket/10191/RelatedObjectLookups-updated.js.patch
+ // check if the className contains radiolist - if it's HORIZONTAL, then it won't match if we compare explicitly
+ } else if (elem.className.indexOf('radiolist') > -1) {
+ var cnt = elem.getElementsByTagName('li').length;
+ var idName = elem.id+'_'+cnt;
+ var newLi = document.createElement('li');
+ var newLabel = document.createElement('label');
+ var newText = document.createTextNode(' '+newRepr);
+ try {
+ // IE doesn't support settings name, type, or class by setAttribute
+ var newInput = document.createElement('<input type=\'radio\' name=\''+name.slice(3)+'\' checked=\'checked\' class=\''+elem.className+'\' />');
+ } catch(err) {
+ var newInput = document.createElement('input');
+ newInput.setAttribute('class', elem.className);
+ newInput.setAttribute('type', 'radio');
+ newInput.setAttribute('name', name.slice(3));
+ }
+ newLabel.setAttribute('for', idName);
+ newInput.setAttribute('id', idName);
+ newInput.setAttribute('value', newId);
+ newInput.setAttribute('checked', 'checked');
+ newLabel.appendChild(newInput);
+ // check if the content being added is a tag - useful for image lists
+ if (newRepr.charAt(0) == '<' && newRepr.charAt(newRepr.length-1) == '>') {
+ newLabel.innerHTML += newRepr;
+ }
+ else {
+ newLabel.appendChild(newText);
+ }
+ newLi.appendChild(newLabel);
+ elem.appendChild(newLi);
+ }
+ } else {
+ var toId = name + "_to";
+ elem = document.getElementById(toId);
+ var o = new Option(newRepr, newId);
+ SelectBox.add_to_cache(toId, o);
+ SelectBox.redisplay(toId);
+ }
+
+ win.close();
+}
+
+(function($) {
+ function RelatedLookup(obj) {
+ // check if val isn't empty string or the same value as before
+ if (obj.val() == obj.data('old_val')) return;
+ obj.data('old_val', obj.val());
+
+ var link = obj.next();
+ var spliturl = link.attr('href').split('/');
+ var app_label = spliturl[spliturl.length-3];
+ var model_name= spliturl[spliturl.length-2];
+
+ var text = obj.next().next();
+ if (obj.val() == "") {
+ text.text('');
+ return;
+ }
+
+ text.text('loading ...');
+
+ // get object
+ $.get('/grappelli/lookup/related/', {
+ object_id: obj.val(),
+ app_label: app_label,
+ model_name: model_name
+ }, function(data) {
+ var item = data;
+ text.text('');
+ if (item) {
+ if (item.length > CHAR_MAX_LENGTH) {
+ text.text(decodeURI(item.substr(0, CHAR_MAX_LENGTH) + " ..."));
+ } else {
+ text.text(decodeURI(item));
+ }
+
+ }
+ });
+ }
+
+ function M2MLookup(obj) {
+ // check if val isn't empty string or the same value as before
+ if (obj.val() == obj.data('old_val')) return;
+ obj.data('old_val', obj.val());
+
+ var link = obj.next();
+ var spliturl = link.attr('href').split('/');
+ var app_label = spliturl[spliturl.length-3];
+ var model_name= spliturl[spliturl.length-2];
+
+ var text = obj.next().next();
+ if (obj.val() == "") {
+ text.text('');
+ return;
+ }
+
+ text.text('loading ...');
+
+ // get object
+ $.get('/grappelli/lookup/m2m/', {
+ object_id: obj.val(),
+ app_label: app_label,
+ model_name: model_name
+ }, function(data) {
+ var item = data;
+ text.text('');
+ if (item) {
+ if (item.length > CHAR_MAX_LENGTH) {
+ text.text(decodeURI(item.substr(0, CHAR_MAX_LENGTH) + " ..."));
+ } else {
+ text.text(decodeURI(item));
+ }
+ }
+ });
+ }
+
+ function GenericLookup(obj, force_update) {
+ // check if val isn't empty string or the same value as before
+ if (!force_update && obj.val() == obj.data('old_val')) return;
+ obj.data('old_val', obj.val());
+
+ var text = obj.next().next();
+ if (obj.val() == "") {
+ text.text("");
+ return;
+ }
+ text.text('loading ...');
+
+ var link = obj.next();
+ if (link.length == 0) return;
+ var spliturl = link.attr('href').split('/');
+ var app_label = spliturl[spliturl.length-3];
+ var model_name= spliturl[spliturl.length-2];
+
+ // get object
+ $.get('/grappelli/lookup/related/', {object_id: obj.val(), app_label: app_label, model_name: model_name}, function(data) {
+ var item = data;
+ text.text('');
+ if (item) {
+ if (item.length > CHAR_MAX_LENGTH) {
+ text.text(decodeURI(item.substr(0, CHAR_MAX_LENGTH) + " ..."));
+ } else {
+ text.text(decodeURI(item));
+ }
+ }
+ });
+ }
+
+ function RelatedHandler(obj) {
+ // related lookup handler
+ obj.bind("change focus keyup blur", function() {
+ RelatedLookup($(this));
+ });
+ }
+
+ function M2MHandler(obj) {
+ // related lookup handler
+ obj.bind("change focus keyup blur", function() {
+ M2MLookup($(this));
+ });
+ }
+
+ function InitObjectID(obj) {
+ obj.each(function() {
+ var ct = $(this).closest('div[class*="object_id"]').prev().find(':input[name*="content_type"]').val();
+ if (ct) {
+ var lookupLink = $('<a class="related-lookup"></a>');
+ lookupLink.attr('id', 'lookup_'+this.id);
+ lookupLink.attr('href', ADMIN_URL + MODEL_URL_ARRAY[ct].app + "/" + MODEL_URL_ARRAY[ct].model + '/?t=id');
+ lookupLink.attr('onClick', 'return showRelatedObjectLookupPopup(this);');
+ var lookupText = '<strong>&nbsp;</strong>';
+ $(this).after(lookupText).after(lookupLink);
+ if ($(this).val() != "") {
+ lookupText = GenericLookup($(this));
+ }
+ }
+ });
+ }
+
+ function InitContentType(obj) {
+ obj.bind("change", function() {
+ var node = $(this).closest('div[class*="content_type"]').next(),
+ lookupLink = node.find('a.related-lookup'),
+ obj_id = node.find('input[name*="object_id"]');
+ if ($(this).val()) {
+ var href = ADMIN_URL + MODEL_URL_ARRAY[$(this).val()].app + "/" + MODEL_URL_ARRAY[$(this).val()].model + '/?t=id';
+ if (lookupLink.attr('href')) {
+ lookupLink.attr('href', href);
+ } else {
+ lookupLink = $('<a class="related-lookup"></a>');
+ lookupLink.attr('id', 'lookup_' + obj_id.attr('id'));
+ lookupLink.attr('href', ADMIN_URL + MODEL_URL_ARRAY[$(this).val()].app + "/" + MODEL_URL_ARRAY[$(this).val()].model + '/?t=id');
+ lookupLink.attr('onClick', 'return showRelatedObjectLookupPopup(this);');
+ var lookupText = '<strong>&nbsp;</strong>';
+ obj_id.after(lookupText).after(lookupLink);
+ }
+ GenericLookup(obj_id, true);
+ } else {
+ obj_id.val('');
+ lookupLink.remove();
+ node.find('strong').remove();
+ }
+ });
+ }
+
+ function GenericHandler(obj) {
+ // related lookup handler
+ obj.bind("change focus keyup", function() {
+ GenericLookup($(this));
+ });
+ }
+ $(document).ready(function() {
+ // change related-lookups in order to get the right URL.
+ $('a.related-lookup').each(function() {
+ href = $(this).attr('href').replace('../../../', ADMIN_URL);
+ $(this).attr('href', href);
+ });
+
+ // related lookup setup
+ $("input.vForeignKeyRawIdAdminField").each(function() {
+ // insert empty text-elements after all empty foreignkeys
+ if ($(this).val() == "") {
+ $(this).next().after('&nbsp;<strong></strong>');
+ }
+ });
+
+ // m2m lookup setup
+ $("input.vManyToManyRawIdAdminField").each(function() {
+ // insert empty text-elements after all m2m fields
+ $(this).next().after('&nbsp;<strong>&nbsp;</strong>');
+ M2MLookup($(this));
+ });
+
+ RelatedHandler($("input.vForeignKeyRawIdAdminField"));
+ M2MHandler($("input.vManyToManyRawIdAdminField"));
+
+ InitObjectID($('input[name*="object_id"]'));
+ InitContentType($(':input[name*="content_type"]'));
+ GenericHandler($('input[name*="object_id"]'));
+ });
+})(django.jQuery);
diff --git a/lib/grappelli/media/js/grappelli.change_list.js b/lib/grappelli/media/js/grappelli.change_list.js
new file mode 100644
index 0000000..3a8c967
--- /dev/null
+++ b/lib/grappelli/media/js/grappelli.change_list.js
@@ -0,0 +1,317 @@
+/**
+ * manages all interactions in the admin change_list
+ */
+(function($) {
+ $.fn.change_list = function(opts) {
+ var options = $.extend({}, $.fn.change_list.defaults, opts),
+ actionCheckboxes = $(options.actionCheckboxes);
+
+ checker = function(checked) {
+ if (checked) {
+ showQuestion();
+ } else {
+ reset();
+ }
+ actionCheckboxes.attr("checked", checked).parent().parent().toggleClass(options.selectedClass, checked);
+ };
+
+ updateCounter = function() {
+ var count = actionCheckboxes.filter(":checked").length;
+
+ if (count > 0) {
+ hideSubmitFooter();
+ $(options.actionContainer).show();
+ } else {
+ $(options.actionContainer).hide();
+ showSubmitFooter();
+ }
+
+ if ($(options.actionContainer).find("span._acnt").length > 0) {
+ $(options.actionContainer).find("span._acnt").html(count);
+ } else {
+ var actionCounter = $(options.actionContainer).find("span.action-counter"),
+ text = actionCounter.html();
+ actionCounter.html('<span class="_acnt">' + count + '</span>' + text.substring(1));
+ }
+
+ $(options.allToggle).attr("checked", function() {
+ if (count == actionCheckboxes.length) {
+ value = true;
+ showQuestion();
+ } else {
+ value = false;
+ clearAcross();
+ }
+ return value;
+ });
+ };
+
+ showQuestion = function() {
+ $(options.acrossClears).hide();
+ $(options.acrossQuestions).show();
+ $(options.allContainer).hide();
+ };
+
+ showClear = function() {
+ $(options.acrossClears).show();
+ $(options.allContainer).show();
+
+ $(options.acrossQuestions).hide();
+ $(options.counterContainer).hide();
+
+ $(options.actionContainer).toggleClass(options.selectedClass);
+ };
+
+ reset = function() {
+ $(options.acrossClears).hide();
+ $(options.acrossQuestions).hide();
+ $(options.allContainer).hide();
+ $(options.counterContainer).show();
+ };
+
+ clearAcross = function() {
+ reset();
+ $(options.acrossInput).val(0);
+ $(options.actionContainer).removeClass(options.selectedClass);
+ };
+
+ clearSelection = function() {
+ $(options.allToggle).attr("checked", false);
+ clearAcross();
+ checker(false);
+ updateCounter();
+ };
+
+ initializeFlexibleLayout = function(content) {
+ var SpanGrid,
+ PaddingRight,
+ MarginRight;
+ $('#content').addClass('content-flexible');
+ $(content).find('.span-flexible').next('.column').parent().addClass('layout-flexible-grid');
+ $(content).find('.column').next('.span-flexible').parent().addClass('layout-grid-flexible');
+ $(content).append('<br clear="all" />');
+ // Layout Flexible + Grid
+ if ($(content).hasClass('layout-flexible-grid')) {
+ SpanGrid = $(content).find('.span-flexible').next('.column').outerWidth();
+ PaddingRight = SpanGrid + 20;
+ MarginRight = - SpanGrid - 20;
+ $(content).css({
+ 'padding-right': PaddingRight
+ });
+ $(content).find('.span-flexible').next('.column').css({
+ 'margin-right': -10000
+ });
+ }
+ // Layout Grid + Flexible
+ if ($(content).hasClass('layout-grid-flexible')) {
+ SpanGrid = $(content).find('.span-flexible').prev('.column').outerWidth();
+ PaddingLeft = SpanGrid + 20;
+ MarginLeft = - SpanGrid - 20;
+ $(content).css({
+ 'padding-left': PaddingLeft
+ });
+ $(content).find('.span-flexible').prev('.column').css({
+ 'margin-left': MarginLeft
+ });
+ }
+ };
+
+ HorizontalOverflow = function(content) {
+ var TableWidth = $(content).find('table').outerWidth();
+ var SpanFlexibleWidth = $(content).find('.span-flexible').outerWidth();
+ if (TableWidth > SpanFlexibleWidth) {
+ $(content).find('.span-flexible').css({
+ 'min-width' : TableWidth + 1 + 'px'
+ });
+ $(content).find('.span-flexible').next('.column').css({
+ 'border-right' : '20px solid transparent'
+ });
+ }
+ if (TableWidth < SpanFlexibleWidth) {
+ $(content).find('.span-flexible').css({
+ 'min-width': 'auto'
+ });
+ }
+ };
+
+ ModifyTableElements = function() {
+ // UGLY HACK: add no-wrap to table-elements
+ // should be there already.
+ $('.changelist-results a.add-another').parent().addClass('nowrap');
+ };
+
+ initFilter = function() {
+ $("a.toggle-filters").click(function() {
+ $(".filter-pulldown").toggle();
+ $("#filters").toggleClass("open");
+ });
+
+ $(".filter_choice").change(function(){
+ location.href = $(this).val();
+ });
+
+ var filter_choice = $(".filter_choice");
+
+ for (var i = 0; i < filter_choice.length; i++) {
+ if (!$(filter_choice[i]).find(':first-child').attr('selected')) {
+ $("#filters").addClass('selected');
+ }
+ }
+ };
+
+ initLayout = function() {
+ initializeFlexibleLayout('.container-flexible');
+
+ $(window).resize(function(){
+ HorizontalOverflow('.container-flexible');
+ });
+
+ //window.onload = function () {
+ HorizontalOverflow('.container-flexible');
+ //};
+ };
+
+ showSubmitFooter = function() {
+ $("div#submit").show();
+
+ // need to uncheck all actions checkboxes and update counter
+ // (actions are not working if you want to edit items in the change_list)
+
+ //actionCheckboxes.attr("checked", false);
+ //clearSelection();
+ };
+
+ hideSubmitFooter = function() {
+ $("div#submit").hide();
+ };
+
+ // Show counter by default
+ $(options.counterContainer).show();
+
+ $(options.allToggle).show().click(function() {
+ checker($(this).attr("checked"));
+ updateCounter();
+ });
+
+ $("div.changelist-actions li.question a").click(function(event) {
+ event.preventDefault();
+ $(options.acrossInput).val(1);
+ showClear();
+ });
+
+ $("div.changelist-actions li.clear-selection a").click(function(event) {
+ event.preventDefault();
+ clearSelection();
+ });
+
+ lastChecked = null;
+ actionCheckboxes.click(function(event) {
+ if (!event) {
+ event = window.event;
+ }
+ var target = event.target ? event.target : event.srcElement;
+ if (lastChecked && $.data(lastChecked) != $.data(target) && event.shiftKey == true) {
+ var inrange = false;
+ $(lastChecked).attr("checked", target.checked).parent().parent().toggleClass(options.selectedClass, target.checked);
+ actionCheckboxes.each(function() {
+ if ($.data(this) == $.data(lastChecked) || $.data(this) == $.data(target)) {
+ inrange = (inrange) ? false : true;
+ }
+ if (inrange) {
+ $(this).attr("checked", target.checked)
+ .parent().parent().toggleClass(options.selectedClass, target.checked);
+ }
+ });
+ }
+ $(target).parent().parent().toggleClass(options.selectedClass, target.checked);
+ lastChecked = target;
+ updateCounter();
+ });
+
+ $(options.actionSelect).attr("autocomplete", "off").change(function(evt){
+ $(this).parents("form").submit();
+ });
+
+ initLayout();
+
+ initFilter();
+ /*
+ * django version < 1.2.3 only
+ * hide the last coll if its an editable list
+ * because this coll has just the hidden input with the id (breaks ui)
+
+ if ($("#changelist").hasClass("editable")) {
+ // UGLY HACK: add th for thead when list_editables are activated.
+ $(".changelist-results tr").each(function() {
+ $(this).find("td:last").hide();
+ });
+ }
+ */
+
+
+ /*
+ * deprecated code
+ * used to show submit footer if something changed only.
+ * way to complex to achieve this feature.
+ * now submit footer is visible if list_editable (and toggles visibility with action footer).
+
+ $("input.action-select, input#action-toggle, a.cancel-link").click(function() {
+ $("div#submit").hide();
+ });
+
+ //var edit_inlines = $("input[name!='_selected_action'][id!='action-toggle'][id!='searchbar']");
+ //edit_inlines.focus(showSubmitFooter);
+ // safari (5) needs this because focus event doesn't work on checkboxes (anymore)
+ //edit_inlines.end().find(":checkbox").click(showSubmitFooter);
+ var input_nodes = $("input[name!='_selected_action'][id!='action-toggle'][id!='searchbar']");
+ input_nodes.bind("change click", showSubmitFooter);
+ $("select[class!='filter_choice'][name!='action']").bind("change click", showSubmitFooter);
+ // FilebrowseField's button
+ $("a.fb_show").click(showSubmitFooter);
+ */
+
+ $("td input.vForeignKeyRawIdAdminField, td input.vFileBrowseField, td a.add-another").each(function() {
+ $(this).parent().addClass('nowrap');
+ });
+
+ // Check state of checkboxes and reinit state if needed
+ actionCheckboxes.filter(":checked").each(function(i) {
+ $(this).parent().parent().toggleClass(options.selectedClass);
+ updateCounter();
+ if ($(options.acrossInput).val() == 1) {
+ showClear();
+ }
+ });
+
+ // if (!$("input#searchbar").val()) {
+ // $("input#searchbar").val(options.initialSearchVal);
+ // }
+ // $("input#searchbar").focus(function(){
+ // if ($(this).val() == options.initialSearchVal) {
+ // $(this).val("");
+ // }
+ // });
+ // $("input#searchbar").blur(function(){
+ // if (!$(this).val()) {
+ // $(this).val(options.initialSearchVal);
+ // }
+ // });
+
+ };
+
+ /* Setup plugin defaults */
+ $.fn.change_list.defaults = {
+ actionCheckboxes: "input.action-select",
+ actionContainer: "div.changelist-actions",
+ counterContainer: "li.action-counter",
+ allContainer: "div.changelist-actions li.all",
+ acrossInput: "div.changelist-actions input.select-across",
+ acrossQuestions: "div.changelist-actions li.question",
+ acrossClears: "div.changelist-actions li.clear-selection",
+ allToggle: "#action-toggle",
+ selectedClass: "selected",
+ actionSelect: "div.changelist-actions select",
+ initialSearchVal: "Search"
+ };
+})(django.jQuery);
+
diff --git a/lib/grappelli/media/js/grappelli.init.js b/lib/grappelli/media/js/grappelli.init.js
new file mode 100644
index 0000000..85a7973
--- /dev/null
+++ b/lib/grappelli/media/js/grappelli.init.js
@@ -0,0 +1,4 @@
+// Puts the included jQuery into our own namespace
+var django = {
+ "jQuery": jQuery.noConflict(true)
+};
diff --git a/lib/grappelli/media/js/grappelli.js b/lib/grappelli/media/js/grappelli.js
new file mode 100644
index 0000000..f9a540a
--- /dev/null
+++ b/lib/grappelli/media/js/grappelli.js
@@ -0,0 +1,131 @@
+var grappelli = {};
+
+(function($) {
+ grappelli.collapseHandlerClass = "collapse-handler";
+ grappelli.collapsedBlockedClass = "collapse-blocked";
+ grappelli.openAllClass = "open-handler";
+ grappelli.closeAllClass = "close-handler";
+ grappelli.collapseClass = "collapse";
+ grappelli.closedClass = "closed";
+ grappelli.openClass = "open";
+
+ grappelli.collapseHandler = function() {
+ if (!$("body").hasClass(grappelli.collapsedBlockedClass)) {
+ $(this).parents("." + grappelli.collapseClass).first()
+ .toggleClass(grappelli.closedClass)
+ .toggleClass(grappelli.openClass);
+ }
+ return false;
+ };
+
+ grappelli.addCollapseHandlerClass = function() {
+ $("." + this.collapseClass).each(function() {
+ var node = $(this).children().first();
+ if (node.is("h2") || node.is("h3") || node.is("h4")) {
+ node.addClass(grappelli.collapseHandlerClass)
+ }
+ });
+ };
+
+ grappelli.registerCollapseHandler = function() {
+ $("." + this.collapseHandlerClass).click(this.collapseHandler);
+ };
+
+ grappelli.registerOpenAllHandler = function() {
+ $("." + this.openAllClass).click(this.openAllHandler);
+ };
+
+ /*
+ * Open all
+ */
+ grappelli.openAllHandler = function() {
+ // get .group and not .collapse because it doesn't necessarily have .collapse
+ $(this).parents(".group")
+ .removeClass(grappelli.closedClass)
+ .addClass(grappelli.openClass)
+ .find("." + grappelli.collapseClass)
+ .removeClass(grappelli.closedClass)
+ .addClass(grappelli.openClass);
+ };
+
+ grappelli.registerCloseAllHandler = function() {
+ $("." + this.closeAllClass).click(this.closeAllHandler);
+ };
+
+ /*
+ * Close all
+ */
+ grappelli.closeAllHandler = function() {
+ // get .group and not .collapse because it doesn't necessarily have .collapse
+ $(this).parents(".group")
+ .find("." + grappelli.collapseClass)
+ .removeClass(grappelli.openClass)
+ .addClass(grappelli.closedClass);
+ };
+
+ grappelli.initCollapsible = function() {
+ grappelli.addCollapseHandlerClass();
+ grappelli.registerCollapseHandler();
+
+ grappelli.registerOpenAllHandler();
+ grappelli.registerCloseAllHandler();
+
+ $("." + grappelli.collapseClass + " ul.errorlist").each(function() {
+ $(this).parents("." + grappelli.collapseClass)
+ .removeClass(grappelli.closedClass)
+ .addClass(grappelli.openClass);
+ });
+ };
+
+ grappelli.getFormat = function(type) {
+ if (type == "date") {
+ var format = DATE_FORMAT.toLowerCase().replace(/%\w/g, function(str) {
+ str = str.replace(/%/, '');
+ return str + str;
+ });
+ }
+ return format;
+ }
+
+ grappelli.initDateAndTimePicker = function() {
+ var options = {
+ //appendText: '(mm/dd/yyyy)',
+ showOn: 'button',
+ buttonImageOnly: false,
+ buttonText: '',
+ dateFormat: grappelli.getFormat('date'),
+ showAnim: ''
+ };
+ var dateFields = $("input[class*='vDateField']:not([id*='__prefix__'])");
+ dateFields.datepicker(options);
+
+ if (typeof IS_POPUP != "undefined" && IS_POPUP) {
+ dateFields.datepicker('disable');
+ }
+ $("input[class*='vTimeField']:not([id*='__prefix__'])").timepicker();
+ };
+
+ grappelli.initHacks = function() {
+ $('p.datetime').each(function() {
+ var text = $(this).html();
+ text = text.replace(/^\w*: /, "");
+ text = text.replace(/<br>.*: /, "<br>");
+ $(this).html(text);
+ });
+ };
+
+ // Using window.load instead of document ready for better performances
+ // It prevents lots of glitches, like divs that moves around upon loading
+ //
+ // Use $(document).ready only if you have to deal with images since it will
+ // wait for the document to be fully loaded/rendered before running the function
+ // while window.load method will run as soon as the DOM/CSS is loaded.
+
+ $(window).load(function() {
+ // we do the hacks first!
+ // because we manipulate dom via innerHTML => loose events
+ grappelli.initHacks();
+ grappelli.initCollapsible();
+ grappelli.initDateAndTimePicker();
+ });
+})(django.jQuery);
diff --git a/lib/grappelli/media/js/grappelli.min.js b/lib/grappelli/media/js/grappelli.min.js
new file mode 100644
index 0000000..e762690
--- /dev/null
+++ b/lib/grappelli/media/js/grappelli.min.js
@@ -0,0 +1,252 @@
+var grappelli = {};
+
+(function($) {
+ grappelli.collapseHandlerClass = "collapse-handler";
+ grappelli.collapsedBlockedClass = "collapse-blocked";
+ grappelli.openAllClass = "open-handler";
+ grappelli.closeAllClass = "close-handler";
+ grappelli.collapseClass = "collapse";
+ grappelli.closedClass = "closed";
+ grappelli.openClass = "open";
+
+
+ /**
+ * if collapsible in menu is opened it has to close if it "looses focus"
+ */
+ grappelli.collapseBlurHandler = function(evt) {
+ var that = $(this),
+ parents = $(evt.originalEvent.explicitOriginalTarget).parents(),
+ handler_parent = that.parents("ul.navigation-menu")[0] || that.parents("ul#user-tools")[0];
+ for (var i = 0; i < parents.length; i++) {
+ if (parents[i] == handler_parent) {
+ var target = evt.originalEvent.explicitOriginalTarget;
+ if (target.nodeType == 3) {
+ target = $(target).parent()
+ } else {
+ target = $(target);
+ }
+ target.one('blur', grappelli.collapseBlurHandler);
+ return;
+ }
+ }
+ that.parents("." + grappelli.collapseClass).first()
+ .toggleClass(grappelli.closedClass)
+ .toggleClass(grappelli.openClass);
+ };
+
+ /**
+ * handles assignment of classes (open/close) when collapse handler is clicked
+ * NOTE: special case if collapsible is within the header menu
+ */
+ grappelli.collapseHandler = function() {
+ if (!$("body").hasClass(grappelli.collapsedBlockedClass)) {
+ var that = $(this);
+ that.parents("." + grappelli.collapseClass).first()
+ .toggleClass(grappelli.closedClass)
+ .toggleClass(grappelli.openClass);
+
+ // close again on blur if it's an menu dropdown from the header (i.e. bookmarks)
+ if (that.parent(".menu-item").length || that.parent(".user-options-container").length) {
+ menu = $(this).parent('li').children('ul');
+ menumaxheight = $(window).height() - 80;
+ menuheight = menu.height();
+ if (menumaxheight < menuheight) {
+ $(menu).css({
+ "max-height": menumaxheight,
+ "overflow-y": "scroll"
+ });
+ }
+
+ that.one('blur', grappelli.collapseBlurHandler);
+ }
+ }
+ return false;
+ };
+
+ grappelli.addCollapseHandlerClass = function() {
+ $("." + this.collapseClass).each(function() {
+ var node = $(this).children().first();
+ if (node.is("h2") || node.is("h3") || node.is("h4")) {
+ node.addClass(grappelli.collapseHandlerClass)
+ }
+ });
+ };
+
+ /**
+ * add click handler to collapsibles
+ * NOTE: secial case for collapse handler in menu
+ */
+ grappelli.registerCollapseHandler = function() {
+ $("." + this.collapseHandlerClass).click(this.collapseHandler);
+
+ // if the item has children you generally open the cildren with the button next to it.
+ // open the menu anyways if it has an empty link
+ $('a.parent.item-collapse-handler-container').click(function(){
+ var that = $(this);
+
+ if (that.attr('href') == "#") {
+ that.next().click();
+ }
+ });
+
+ // do this each time a submenu is opened
+ $('ul.navigation-menu a.item-collapse-handler').click(function(){
+ // Collapse
+ $(this).closest('li.item-collapse').toggleClass("item-closed").toggleClass("item-open");
+ // Calculate Menu Height
+ menu = $(this).closest('ul.navigation-menu>li>ul');
+ $(menu).removeAttr("style");
+ menumaxheight = $(window).height() - 80;
+ menuheight = menu.height();
+ menuwidth = menu.outerWidth() + 15;
+ if (menumaxheight < menuheight) {
+ // $(menu).addClass("");
+ $(menu).css({
+ "width": menuwidth,
+ "height": menuheight,
+ "max-height": menumaxheight,
+ "overflow-y": "scroll",
+ "overflow-x": "hidden !important"
+ });
+ }
+ });
+ };
+
+ grappelli.registerOpenAllHandler = function() {
+ $("." + this.openAllClass).click(this.openAllHandler);
+ };
+
+ /*
+ * Open all
+ */
+ grappelli.openAllHandler = function() {
+ // get .group and not .collapse because it doesn't necessarily have .collapse
+ $(this).parents(".group")
+ .removeClass(grappelli.closedClass)
+ .addClass(grappelli.openClass)
+ .find("." + grappelli.collapseClass)
+ .removeClass(grappelli.closedClass)
+ .addClass(grappelli.openClass);
+ };
+
+ grappelli.registerCloseAllHandler = function() {
+ $("." + this.closeAllClass).click(this.closeAllHandler);
+ };
+
+ /*
+ * Close all
+ */
+ grappelli.closeAllHandler = function() {
+ // get .group and not .collapse because it doesn't necessarily have .collapse
+ $(this).parents(".group")
+ .find("." + grappelli.collapseClass)
+ .removeClass(grappelli.openClass)
+ .addClass(grappelli.closedClass);
+ };
+
+ grappelli.initCollapsible = function() {
+ grappelli.addCollapseHandlerClass();
+ grappelli.registerCollapseHandler();
+
+ grappelli.registerOpenAllHandler();
+ grappelli.registerCloseAllHandler();
+
+ $("." + grappelli.collapseClass + " ul.errorlist").each(function() {
+ $(this).parents("." + grappelli.collapseClass)
+ .removeClass(grappelli.closedClass)
+ .addClass(grappelli.openClass);
+ });
+ };
+
+ grappelli.getFormat = function(type) {
+ if (type == "date") {
+ var format = DATE_FORMAT.toLowerCase().replace(/%\w/g, function(str) {
+ str = str.replace(/%/, '');
+ return str + str;
+ });
+ }
+ return format;
+ }
+
+ grappelli.initDateAndTimePicker = function() {
+ var options = {
+ //appendText: '(mm/dd/yyyy)',
+ showOn: 'button',
+ buttonImageOnly: false,
+ buttonText: '',
+ dateFormat: grappelli.getFormat('date'),
+ showButtonPanel: true,
+ showAnim: '',
+ // HACK: sets the current instance to a global var.
+ // needed to actually select today if the today-button is clicked.
+ // see onClick handler for ".ui-datepicker-current"
+ beforeShow: function(year, month, inst) {
+ grappelli.datepicker_instance = this;
+ }
+ };
+ var dateFields = $("input[class*='vDateField']:not([id*='__prefix__'])");
+ dateFields.datepicker(options);
+
+ if (typeof IS_POPUP != "undefined" && IS_POPUP) {
+ dateFields.datepicker('disable');
+ }
+
+ // HACK: adds an event listener to the today button of datepicker
+ // if clicked today gets selected and datepicker hides.
+ // user live() because there is no hoock after datepicker generates it's complete dom.
+ $(".ui-datepicker-current").live('click', function() {
+ $.datepicker._selectDate(grappelli.datepicker_instance);
+ grappelli.datepicker_instance = null;
+ })
+
+ // inti timepicker
+ $("input[class*='vTimeField']:not([id*='__prefix__'])").timepicker();
+ };
+
+ grappelli.initHacks = function() {
+ // to get rid of text after DateField (hardcoded in django.admin)
+ $('p.datetime').each(function() {
+ var text = $(this).html();
+ text = text.replace(/^\w*: /, "");
+ text = text.replace(/<br>.*: /, "<br>");
+ $(this).html(text);
+ });
+ };
+
+ grappelli.initSearchbar = function() {
+ var searchbar = $("input#searchbar");
+ // var searchbar = $("input#searchbar"),
+ // searchbar_tooltip = $('div#searchbar_tooltip');
+ // if (searchbar_tooltip.length == 0) return;
+
+ searchbar.focus();
+
+ // searchbar.bind('keydown', function() {
+ // searchbar_tooltip.hide();
+ // });
+ // searchbar.bind("mouseover", function() {
+ // searchbar_tooltip.show();
+ // });
+ // searchbar.bind("mouseout", function() {
+ // searchbar_tooltip.hide();
+ // });
+ };
+
+ // Using window.load instead of document ready for better performances
+ // It prevents lots of glitches, like divs that moves around upon loading
+ //
+ // Use $(document).ready only if you have to deal with images since it will
+ // wait for the document to be fully loaded/rendered before running the function
+ // while window.load method will run as soon as the DOM/CSS is loaded.
+
+ $(window).load(function() {
+ // we do the hacks first!
+ // because we manipulate dom via innerHTML => loose events
+ grappelli.initHacks();
+ grappelli.initCollapsible();
+ grappelli.initDateAndTimePicker();
+ grappelli.initSearchbar();
+
+ // add something for clicking outside the navigation-menu
+ });
+})(django.jQuery);
diff --git a/lib/grappelli/media/js/grappelli.timepicker.js b/lib/grappelli/media/js/grappelli.timepicker.js
new file mode 100644
index 0000000..05bedcc
--- /dev/null
+++ b/lib/grappelli/media/js/grappelli.timepicker.js
@@ -0,0 +1,171 @@
+/**
+ * this is grappellis timepicker.
+ * works pretty similar to ui.datepicker:
+ * - adds a button to the element
+ * - creates a node (div) at the bottom called ui-timepicker
+ * - element.onClick fills the ui-timepicker node with the time_list (all times you can select)
+ */
+
+(function( $ ) {
+$.widget("ui.timepicker", {
+ // default options
+ options: {
+ // template for the container of the timepicker
+ template: '<div id="ui-timepicker" class="module" style="position: absolute; display: none;"></div>',
+ // selector to get the ui-timepicker once it's added to the dom
+ timepicker_selector: "#ui-timepicker",
+ // needed offset of the container from the element
+ offset: {
+ top: 0
+ },
+ // if time_list wasn't sent when calling the timepicker we use this
+ default_time_list: [
+ 'now',
+ '00:00',
+ '01:00',
+ '02:00',
+ '03:00',
+ '04:00',
+ '05:00',
+ '06:00',
+ '07:00',
+ '08:00',
+ '09:00',
+ '10:00',
+ '11:00',
+ '12:00',
+ '13:00',
+ '14:00',
+ '15:00',
+ '16:00',
+ '17:00',
+ '18:00',
+ '19:00',
+ '20:00',
+ '21:00',
+ '22:00',
+ '23:00'
+ ],
+ // leave this empty!!!
+ // NOTE: you can't set a default for time_list because if you call:
+ // $("node").timepicker({time_list: ["01:00", "02:00"]})
+ // ui.widget will extend/merge the options.time_list whith the one you sent.
+ time_list: []
+ },
+
+ /**
+ * init timepicker for a specific element
+ */
+ _create: function() {
+ // for the events
+ var self = this;
+
+ // to close timpicker if you click somewhere in the document
+ $(document).mousedown(function(evt) {
+ if (self.timepicker.is(":visible")) {
+ var $target = $(evt.target);
+ if ($target[0].id != self.timepicker[0].id && $target.parents(self.options.timepicker_selector).length == 0 && !$target.hasClass('hasTimepicker') && !$target.hasClass('ui-timepicker-trigger')) {
+ self.timepicker.hide();
+ }
+ }
+ });
+
+ // get/create timepicker's container
+ if ($(this.options.timepicker_selector).size() == 0) {
+ $(this.options.template).appendTo('body');
+ }
+ this.timepicker = $(this.options.timepicker_selector);
+ this.timepicker.hide();
+
+ // modify the element and create the button
+ this.element.addClass("hasTimepicker");
+ this.button = $('<button type="button" class="ui-timepicker-trigger"></button>');
+ this.element.after(this.button);
+
+ // disable button if element is disabled
+ if (this.element.attr("disabled")) {
+ this.button.attr("disabled", true);
+ }
+ // register event
+ else {
+ this.button.click(function() {
+ self._toggleTimepicker();
+ });
+ }
+ },
+
+ /**
+ * called when button is clicked
+ */
+ _toggleTimepicker: function() {
+ if (this.timepicker.is(":visible")) {
+ this.timepicker.hide();
+ } else {
+ this.element.focus();
+ this._generateTimepickerContents();
+ this._showTimepicker();
+ }
+ },
+
+ /**
+ * fills timepicker with time_list of element and shows it.
+ *
+ * called by _toggleTimepicker
+ */
+ _generateTimepickerContents: function() {
+ var self = this,
+ template_str = "<ul>";
+
+ // there is no time_list for this instance so use the default one
+ if (this.options.time_list.length == 0) {
+ this.options.time_list = this.options.default_time_list;
+ }
+
+ for (var i = 0; i < this.options.time_list.length; i++) {
+ if (this.options.time_list[i] == "now") {
+ var now = new Date(),
+ hours = now.getHours(),
+ minutes = now.getMinutes();
+
+ hours = ((hours < 10) ? "0" + hours : hours);
+ minutes = ((minutes < 10) ? "0" + minutes : minutes);
+
+ template_str += '<li class="ui-state-active row">' + hours + ":" + minutes + '</li>';
+ } else {
+ template_str += '<li class="ui-state-default row">' + this.options.time_list[i] + '</li>';
+ }
+ }
+ template_str += "</ul>";
+
+ // fill timepicker container
+ this.timepicker.html(template_str);
+
+ // click handler for items (times) in timepicker
+ this.timepicker.find('li').click(function() {
+ // remove active class from all items
+ $(this).parent().children('li').removeClass("ui-state-active");
+ // mark clicked item as active
+ $(this).addClass("ui-state-active");
+
+ // set the new value and hide the timepicker
+ self.element.val($(this).html());
+ self.timepicker.hide();
+ });
+ },
+
+ /**
+ * sets offset and shows timepicker containter
+ */
+ _showTimepicker: function() {
+ this.timepicker_offset = this.element.offset();
+ this.timepicker_offset.top += this.element.outerHeight() + this.options.offset.top;
+ this.timepicker.css(this.timepicker_offset);
+ this.timepicker.show();
+ },
+
+ destroy: function() {
+ $.Widget.prototype.destroy.apply(this, arguments); // default destroy
+ // now do other stuff particular to this widget
+ }
+});
+})(django.jQuery); \ No newline at end of file
diff --git a/lib/grappelli/media/js/inlines.js b/lib/grappelli/media/js/inlines.js
new file mode 100644
index 0000000..3725975
--- /dev/null
+++ b/lib/grappelli/media/js/inlines.js
@@ -0,0 +1,354 @@
+/**
+ * helper functions for sortable inlines (tabular and stacked)
+ */
+
+function reinitDateTimeFields(row) {
+ row.find(".vDateField").datepicker({
+ //appendText: '(mm/dd/yyyy)',
+ showOn: 'button',
+ buttonImageOnly: false,
+ buttonText: '',
+ dateFormat: grappelli.getFormat('date'),
+ });
+ row.find(".vTimeField").timepicker();
+}
+
+function updateSelectFilter(row) {
+ // If any SelectFilter widgets were added, instantiate a new instance.
+ if (typeof SelectFilter != "undefined"){
+ row.find(".selectfilter").each(function(index, value){
+ var namearr = value.name.split('-');
+ SelectFilter.init(value.id, namearr[namearr.length-1], false, "{% admin_media_prefix %}");
+ });
+ row.find(".selectfilterstacked").each(function(index, value){
+ var namearr = value.name.split('-');
+ SelectFilter.init(value.id, namearr[namearr.length-1], true, "{% admin_media_prefix %}");
+ });
+ }
+}
+
+/**
+ * reorder of inlines dom
+ * works pretty similar to updateFormIndex() of the inline() widget
+ * helper function for sortable
+ */
+function sortable_updateFormIndex(form, idx, prefix) {
+ var re = /-\d+-/g;
+ form.attr('id', prefix + idx);
+ form.find(':input,span,table,iframe,label,a,ul,p,img').each(function() {
+ var node = django.jQuery(this),
+ node_id = node.attr('id'),
+ node_name = node.attr('name'),
+ node_for = node.attr('for'),
+ node_href = node.attr("href");
+
+ if (node_id) node.attr('id', node_id.replace(re, "-" + idx + "-"));
+ if (node_name) node.attr('name', node_name.replace(re, "-" + idx + "-"));
+ if (node_for) node.attr('for', node_for.replace(re, "-" + idx + "-"));
+ if (node_href) node.attr('href', node_href.replace(re, "-" + idx + "-"));
+ });
+}
+
+/**
+ * checks if inline form is filled
+ * helper function for sortable
+ */
+function is_form_filled(form) {
+ var input_tags = form.find("input"),
+ input_tag;
+ for (var i = 0; i < input_tags.length; i++) {
+ input_tag = django.jQuery(input_tags[i]);
+ if (input_tag.val()) {
+ if (input_tag.attr("type") == "checkbox" || input_tag.attr("type") == "radio") {
+ if (input_tag.attr("checked")) {
+ return true;
+ }
+ } else if (input_tag.attr("type") != "hidden"){
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+// updates label of inlines
+// to keep them in sequence (#1,#2,#3,...)
+function stacked_updateInlineLabel(row) {
+ row.parent().find("div.module").find("h3:first").each(function(i) {
+ var h3_node = django.jQuery(this);
+ h3_node.html(h3_node.html().replace(/(#\d+)/g, "#" + ++i));
+ });
+}
+
+// init tinymce for new inlines
+function reinitTinyMCE(row) {
+ row.find("textarea.vLargeTextField").each(function() {
+ tinyMCE.execCommand('mceAddControl', false, this.id);
+ });
+}
+
+// need to remove tinymce form removed inline
+function deleteTinyMCE(row) {
+ row.find("textarea.vLargeTextField").each(function() {
+ if (tinyMCE.getInstanceById(this.id)) {
+ tinyMCE.execCommand('mceRemoveControl', false, this.id);
+ }
+ });
+}
+
+function tabular_onAdded(row) {
+ reinitDateTimeFields(row);
+ updateSelectFilter(row);
+}
+
+function stacked_onAdded(row) {
+ reinitTinyMCE(row);
+ reinitDateTimeFields(row);
+ updateSelectFilter(row);
+ stacked_updateInlineLabel(row);
+}
+
+function stacked_onRemoved(row) {
+ stacked_updateInlineLabel(row);
+ deleteTinyMCE(row);
+}
+
+
+(function($) {
+$.fn.inline = function(options) {
+ var defaults = {
+ prefix: "form", // The form prefix for your django formset
+ addText: "add another", // Text for the add link
+ deleteText: "remove", // Text for the delete link
+ addCssClass: "add-handler", // CSS class applied to the add link
+ deleteCssClass: "delete-handler", // CSS class applied to the delete link
+ removeCssClass: "remove-handler", // CSS class applied to the remove link
+ emptyCssClass: "empty-form", // CSS class applied to the empty row
+ formCssClass: "dynamic-form", // CSS class applied to each form in a formset
+ predeleteCssClass: "predelete",
+ onAdded: null, // Function called each time a new form is added
+ onRemoved: null // Function called each time a form is deleted
+ };
+
+ options = $.extend(defaults, options);
+
+ return this.each(function() {
+
+ var inline = $(this), // the current inline node
+ totalForms = inline.find("#id_" + options.prefix + "-TOTAL_FORMS"), // current forms total
+ maxForms = inline.find("#id_" + options.prefix + "-MAX_NUM_FORMS"), // max forms in this inline (0 if no limit)
+ addButtons = inline.find("a." + options.addCssClass),
+ template = inline.find("#" + options.prefix + "-empty"), // the hidden node we copy to create an additional form
+ template_id = template.attr('id'),
+ template_ready = false,
+ /*
+ updateElementIndex = function(el, prefix, ndx) {
+ var id_regex = new RegExp("(" + prefix + "-\\d+)");
+ var replacement = prefix + "-" + ndx;
+ if ($(el).attr("for")) {
+ $(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
+ }
+ if (el.id) {
+ el.id = el.id.replace(id_regex, replacement);
+ }
+ if (el.name) {
+ el.name = el.name.replace(id_regex, replacement);
+ }
+ },
+ */
+ initAddButtons = function() {
+ if (maxForms.val() == 0 || (maxForms.val() - totalForms.val()) > 0) {
+ addButtons.click(addButtonHandler);
+ } else {
+ // hide add-buttons
+ hideAddBottons();
+ }
+ },
+
+ initFormIndex = function(form, nextIndex) {
+ var re = /__prefix__/g;
+ form.find(':input,span,table,iframe,label,a,ul,p,img').each(function() {
+ var node = $(this),
+ node_id = node.attr('id'),
+ node_name = node.attr('name'),
+ node_for = node.attr('for'),
+ node_href = node.attr("href");
+
+ if (node_id) node.attr('id', node_id.replace(re, nextIndex));
+ if (node_name) node.attr('name', node_name.replace(re, nextIndex));
+ if (node_for) node.attr('for', node_for.replace(re, nextIndex));
+ if (node_href) node.attr('href', node_href.replace(re, nextIndex));
+ });
+ },
+
+ updateFormIndex = function(form, idx) {
+ // need to trigger onRemove and
+ // onAdded (on bottom of function) to reinint rows
+ if (options.onRemoved) {
+ options.onRemoved(form);
+ }
+ var re = /-\d+-/g;
+ form.attr('id', options.prefix + idx);
+ form.find(':input,span,table,iframe,label,a,ul,p,img').each(function() {
+ var node = $(this),
+ node_id = node.attr('id'),
+ node_name = node.attr('name'),
+ node_for = node.attr('for'),
+ node_href = node.attr("href");
+
+ if (node_id) node.attr('id', node_id.replace(re, "-" + idx + "-"));
+ if (node_name) node.attr('name', node_name.replace(re, "-" + idx + "-"));
+ if (node_for) node.attr('for', node_for.replace(re, "-" + idx + "-"));
+ if (node_href) node.attr('href', node_href.replace(re, "-" + idx + "-"));
+ });
+ if (options.onAdded) {
+ options.onAdded(form);
+ }
+ },
+
+ addButtonHandler = function() {
+ // FIXME wrong place to do this
+ // choices:
+ // 1) create a new event (beforAdded) and try to do this form outside :(
+ // 2) add the "editor_deselector" class to the templates textarea
+ // ...
+ if (!options.template_ready) {
+ if (typeof tinyMCE !== "undefined") {
+ template.find('textarea').each(function(e) {
+ if (tinyMCE.getInstanceById(this.id)) {
+ tinyMCE.execCommand('mceRemoveControl', false, this.id);
+ }
+ });
+ }
+ options.template_ready = true;
+ }
+
+ var nextIndex = parseInt(totalForms.val(), 10);
+
+ // create new from (add it as last)
+ var form = template.clone(true);
+
+ form.removeClass(options.emptyCssClass)
+ .attr("id", template_id.replace("-empty", nextIndex))
+ .insertBefore(template)
+ .addClass(options.formCssClass);
+
+ initFormIndex(form, nextIndex);
+
+ totalForms.val(nextIndex + 1);
+
+ // Hide add button in case we've hit the max, except we want to add infinitely
+ if ((maxForms.val() != 0) && (maxForms.val() - totalForms.val()) <= 0) {
+ // hide stuff
+ hideAddBottons();
+ }
+
+ // If a post-add callback was supplied, call it with the added form
+ if (options.onAdded) {
+ options.onAdded(form);
+ }
+ return false;
+ },
+
+ hideAddBottons = function() {
+ addButtons.hide().parents('div.add-item').hide();
+ },
+
+ showAddButtons = function() {
+ addButtons.show().parents('div.add-item').show();
+ },
+
+ deleteHandler = function() {
+ var deleteInput = $(this).prev(),
+ form = deleteInput.parents("." + options.formCssClass).first();
+ if (form.hasClass("has_original")) { // toggle delete checkbox and delete css class
+ form.toggleClass(options.predeleteCssClass);
+ if (deleteInput.attr("checked")) {
+ deleteInput.removeAttr("checked");
+ } else {
+ deleteInput.attr("checked", 'checked');
+ }
+ }
+ return false;
+ },
+
+ removeHandler = function() {
+ var deleteInput = $(this).prev(),
+ form = deleteInput.parents("." + options.formCssClass).first();
+ // last one stays
+ // else if (totalForms.val() == 1)
+ // alert("letztes bleibt da!");
+ // return false;
+ // remove form
+ // Remove the parent form containing this button:
+ form.remove();
+ // If a post-delete callback was provided, call it with the deleted form:
+ if (options.onRemoved) {
+ options.onRemoved(form);
+ }
+ // Update the TOTAL_FORMS form count.
+ var forms = inline.find("." + options.formCssClass);
+ totalForms.val(parseInt(totalForms.val(), 10) - 1);
+
+ // Show add button again once we drop below max
+ if ((maxForms.val() == 0) || (maxForms.val() >= forms.length)) {
+ showAddButtons();
+ }
+ // Also, update names and ids for all remaining form controls
+ // so they remain in sequence:
+ var startReplaceAt = form.attr("id");
+ startReplaceAt = parseInt(startReplaceAt.replace(options.prefix, ""), 10);
+ for (var i = startReplaceAt; i < forms.length; i++) {
+ updateFormIndex($(forms[i]), i);
+ }
+ return false;
+ },
+
+ initInlineForms = function() {
+ var hasErrors = false;
+ //inline.find("div.items div.module").each(function() {
+ inline.find("div.module").each(function() {
+ var form = $(this);
+ // add the options.formCssClass to all forms in the inline
+ if (form.attr('id') !== "") {
+ form.not("." + options.emptyCssClass).addClass(options.formCssClass);
+ }
+ // open the form if it has errors
+ if (form.find("ul.errorlist").length > 0) {
+ form.removeClass('closed').addClass('open');
+ // to open the inline
+ hasErrors = true;
+ }
+ });
+
+ // open the inline if it has forms with errors in it
+ if (hasErrors) {
+ inline.removeClass('closed').addClass('open');
+ }
+ };
+
+ // set this to prevent the browser from keeping the current value after reload
+ totalForms.attr("autocomplete", "off");
+
+ initInlineForms();
+
+ initAddButtons();
+
+ // delete button
+ // toggle the delete-checkbox and add the predelete-class to the row
+ inline.find("a." + options.deleteCssClass).click(deleteHandler);
+ inline.find("a." + options.removeCssClass).click(removeHandler);
+
+ // add options.predeleteCssClass to forms with the delete checkbox checked
+ inline.find("li.delete-handler-container input").each(function() {
+ var deleteInput = $(this);
+ if (deleteInput.attr("checked")) {
+ var form = $(deleteInput.parents("." + options.formCssClass).first());
+ if (form.hasClass("has_original")) {
+ form.toggleClass(options.predeleteCssClass);
+ }
+ }
+ });
+ });
+};
+})(django.jQuery);
diff --git a/lib/grappelli/media/js/inlines.min.js b/lib/grappelli/media/js/inlines.min.js
new file mode 100644
index 0000000..c4c9e74
--- /dev/null
+++ b/lib/grappelli/media/js/inlines.min.js
@@ -0,0 +1,380 @@
+/**
+ * helper functions for sortable inlines (tabular and stacked)
+ */
+
+function reinitDateTimeFields(row) {
+ row.find(".vDateField").datepicker({
+ //appendText: '(mm/dd/yyyy)',
+ showOn: 'button',
+ buttonImageOnly: false,
+ buttonText: '',
+ dateFormat: grappelli.getFormat('date'),
+ });
+ row.find(".vTimeField").timepicker();
+}
+
+function updateSelectFilter(row) {
+ // If any SelectFilter widgets were added, instantiate a new instance.
+ if (typeof SelectFilter != "undefined"){
+ row.find(".selectfilter").each(function(index, value){
+ var namearr = value.name.split('-');
+ SelectFilter.init(value.id, namearr[namearr.length-1], false, "{% admin_media_prefix %}");
+ });
+ row.find(".selectfilterstacked").each(function(index, value){
+ var namearr = value.name.split('-');
+ SelectFilter.init(value.id, namearr[namearr.length-1], true, "{% admin_media_prefix %}");
+ });
+ }
+}
+
+/**
+ * reorder of inlines dom
+ * works pretty similar to updateFormIndex() of the inline() widget
+ * helper function for sortable
+ */
+function sortable_updateFormIndex(form, idx, prefix) {
+ var re = /-\d+-/g;
+ form.attr('id', prefix + idx);
+ form.find(':input,span,table,iframe,label,a,ul,p,img').each(function() {
+ var node = django.jQuery(this),
+ node_id = node.attr('id'),
+ node_name = node.attr('name'),
+ node_for = node.attr('for'),
+ node_href = node.attr("href");
+
+ if (node_id) node.attr('id', node_id.replace(re, "-" + idx + "-"));
+ if (node_name) node.attr('name', node_name.replace(re, "-" + idx + "-"));
+ if (node_for) node.attr('for', node_for.replace(re, "-" + idx + "-"));
+ if (node_href) node.attr('href', node_href.replace(re, "-" + idx + "-"));
+ });
+}
+
+/**
+ * checks if inline form is filled
+ * helper function for sortable
+ */
+function is_form_filled(form) {
+ var input_tags = form.find("input"),
+ input_tag,
+ select_tags = form.find('select'),
+ select_tag,
+ select_template,
+ re = /-\d+-/g;
+
+ // flagged for deletion
+ if (form.find("input[id$='DELETE']").attr("checked")) {
+ return false;
+ }
+ // loop thru all inputs and check if anything is set
+ for (var i = 0; i < input_tags.length; i++) {
+ input_tag = django.jQuery(input_tags[i]);
+ if (input_tag.val()) {
+ // checkboxes and radio need to be checked too
+ if (input_tag.attr("type") == "checkbox" || input_tag.attr("type") == "radio") {
+ if (input_tag.attr("checked")) {
+ return true;
+ }
+ // hidden fields are ignored
+ } else if (input_tag.attr("type") != "hidden"){
+ return true;
+ }
+ }
+ }
+
+ for (var i = 0; i < select_tags.length; i++) {
+ select_tag = django.jQuery(select_tags[i]);
+ if (select_tag.val()) {
+ // get the hidden empty form and compare against it's value (which is the default/empty value)
+ select_template = form.find("#" + select_tag.attr('id').replace(re, "__prefix__"));
+ // if they aren't equal the form is filled
+ if (select_tag.val() != select_template.val()) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+// updates label of inlines
+// to keep them in sequence (#1,#2,#3,...)
+function stacked_updateInlineLabel(row) {
+ row.parent().find("div.module").find("h3:first").each(function(i) {
+ var h3_node = django.jQuery(this);
+ h3_node.html(h3_node.html().replace(/(#\d+)/g, "#" + ++i));
+ });
+}
+
+// init tinymce for new inlines
+function reinitTinyMCE(row) {
+ row.find("textarea.vLargeTextField").each(function() {
+ tinyMCE.execCommand('mceAddControl', false, this.id);
+ });
+}
+
+// need to remove tinymce form removed inline
+function deleteTinyMCE(row) {
+ row.find("textarea.vLargeTextField").each(function() {
+ if (tinyMCE.getInstanceById(this.id)) {
+ tinyMCE.execCommand('mceRemoveControl', false, this.id);
+ }
+ });
+}
+
+function tabular_onAdded(row) {
+ reinitDateTimeFields(row);
+ updateSelectFilter(row);
+}
+
+function tabular_onRemoved(row) {}
+
+function stacked_onAdded(row) {
+ reinitTinyMCE(row);
+ reinitDateTimeFields(row);
+ updateSelectFilter(row);
+ stacked_updateInlineLabel(row);
+}
+
+function stacked_onRemoved(row) {
+ stacked_updateInlineLabel(row);
+ deleteTinyMCE(row);
+}
+
+
+(function($) {
+$.fn.inline = function(options) {
+ var defaults = {
+ prefix: "form", // The form prefix for your django formset
+ addText: "add another", // Text for the add link
+ deleteText: "remove", // Text for the delete link
+ addCssClass: "add-handler", // CSS class applied to the add link
+ deleteCssClass: "delete-handler", // CSS class applied to the delete link
+ removeCssClass: "remove-handler", // CSS class applied to the remove link
+ emptyCssClass: "empty-form", // CSS class applied to the empty row
+ formCssClass: "dynamic-form", // CSS class applied to each form in a formset
+ predeleteCssClass: "predelete",
+ onAdded: null, // Function called each time a new form is added
+ onRemoved: null // Function called each time a form is deleted
+ };
+
+ options = $.extend(defaults, options);
+
+ return this.each(function() {
+
+ var inline = $(this), // the current inline node
+ totalForms = inline.find("#id_" + options.prefix + "-TOTAL_FORMS"), // current forms total
+ maxForms = inline.find("#id_" + options.prefix + "-MAX_NUM_FORMS"), // max forms in this inline (0 if no limit)
+ addButtons = inline.find("a." + options.addCssClass),
+ template = inline.find("#" + options.prefix + "-empty"), // the hidden node we copy to create an additional form
+ template_id = template.attr('id'),
+ template_ready = false,
+ /*
+ updateElementIndex = function(el, prefix, ndx) {
+ var id_regex = new RegExp("(" + prefix + "-\\d+)");
+ var replacement = prefix + "-" + ndx;
+ if ($(el).attr("for")) {
+ $(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
+ }
+ if (el.id) {
+ el.id = el.id.replace(id_regex, replacement);
+ }
+ if (el.name) {
+ el.name = el.name.replace(id_regex, replacement);
+ }
+ },
+ */
+ initAddButtons = function() {
+ addButtons.click(addButtonHandler);
+ if (maxForms.val() != 0 && (maxForms.val() - totalForms.val()) == 0) {
+ // hide add-buttons
+ hideAddBottons();
+ }
+ },
+
+ initFormIndex = function(form, nextIndex) {
+ var re = /__prefix__/g;
+ form.find(':input,span,table,iframe,label,a,ul,p,img').each(function() {
+ var node = $(this),
+ node_id = node.attr('id'),
+ node_name = node.attr('name'),
+ node_for = node.attr('for'),
+ node_href = node.attr("href");
+
+ if (node_id) node.attr('id', node_id.replace(re, nextIndex));
+ if (node_name) node.attr('name', node_name.replace(re, nextIndex));
+ if (node_for) node.attr('for', node_for.replace(re, nextIndex));
+ if (node_href) node.attr('href', node_href.replace(re, nextIndex));
+ });
+ },
+
+ updateFormIndex = function(form, idx) {
+ // need to trigger onRemove and
+ // onAdded (on bottom of function) to reinint rows
+ if (options.onRemoved) {
+ options.onRemoved(form);
+ }
+ var re = /-\d+-/g;
+ form.attr('id', options.prefix + idx);
+ form.find(':input,span,table,iframe,label,a,ul,p,img').each(function() {
+ var node = $(this),
+ node_id = node.attr('id'),
+ node_name = node.attr('name'),
+ node_for = node.attr('for'),
+ node_href = node.attr("href");
+
+ if (node_id) node.attr('id', node_id.replace(re, "-" + idx + "-"));
+ if (node_name) node.attr('name', node_name.replace(re, "-" + idx + "-"));
+ if (node_for) node.attr('for', node_for.replace(re, "-" + idx + "-"));
+ if (node_href) node.attr('href', node_href.replace(re, "-" + idx + "-"));
+ });
+ if (options.onAdded) {
+ options.onAdded(form);
+ }
+ },
+
+ addButtonHandler = function() {
+ // FIXME wrong place to do this
+ // choices:
+ // 1) create a new event (beforAdded) and try to do this form outside :(
+ // 2) add the "editor_deselector" class to the templates textarea
+ // ...
+ if (!options.template_ready) {
+ if (typeof tinyMCE !== "undefined") {
+ template.find('textarea').each(function(e) {
+ if (tinyMCE.getInstanceById(this.id)) {
+ tinyMCE.execCommand('mceRemoveControl', false, this.id);
+ }
+ });
+ }
+ options.template_ready = true;
+ }
+
+ var nextIndex = parseInt(totalForms.val(), 10);
+
+ // create new from (add it as last)
+ var form = template.clone(true);
+
+ form.removeClass(options.emptyCssClass)
+ .attr("id", template_id.replace("-empty", nextIndex))
+ .insertBefore(template)
+ .addClass(options.formCssClass);
+
+ initFormIndex(form, nextIndex);
+
+ totalForms.val(nextIndex + 1);
+
+ // Hide add button in case we've hit the max, except we want to add infinitely
+ if ((maxForms.val() != 0) && (maxForms.val() - totalForms.val()) <= 0) {
+ // hide stuff
+ hideAddBottons();
+ }
+
+ // If a post-add callback was supplied, call it with the added form
+ if (options.onAdded) {
+ options.onAdded(form);
+ }
+ return false;
+ },
+
+ hideAddBottons = function() {
+ addButtons.hide().parents('div.add-item').hide();
+ },
+
+ showAddButtons = function() {
+ addButtons.show().parents('div.add-item').show();
+ },
+
+ deleteHandler = function() {
+ var deleteInput = $(this).prev(),
+ form = deleteInput.parents("." + options.formCssClass).first();
+ if (form.hasClass("has_original")) { // toggle delete checkbox and delete css class
+ form.toggleClass(options.predeleteCssClass);
+ if (deleteInput.attr("checked")) {
+ deleteInput.removeAttr("checked");
+ } else {
+ deleteInput.attr("checked", 'checked');
+ }
+ }
+ return false;
+ },
+
+ removeHandler = function() {
+ var deleteInput = $(this).prev(),
+ form = deleteInput.parents("." + options.formCssClass).first();
+ // last one stays
+ // else if (totalForms.val() == 1)
+ // alert("letztes bleibt da!");
+ // return false;
+ // remove form
+ // Remove the parent form containing this button:
+ form.remove();
+ // If a post-delete callback was provided, call it with the deleted form:
+ if (options.onRemoved) {
+ options.onRemoved(form);
+ }
+ // Update the TOTAL_FORMS form count.
+ var forms = inline.find("." + options.formCssClass);
+ totalForms.val(parseInt(totalForms.val(), 10) - 1);
+
+ // Show add button again once we drop below max
+ if ((maxForms.val() == 0) || (maxForms.val() >= forms.length)) {
+ showAddButtons();
+ }
+ // Also, update names and ids for all remaining form controls
+ // so they remain in sequence:
+ var startReplaceAt = form.attr("id");
+ startReplaceAt = parseInt(startReplaceAt.replace(options.prefix, ""), 10);
+ for (var i = startReplaceAt; i < forms.length; i++) {
+ updateFormIndex($(forms[i]), i);
+ }
+ return false;
+ },
+
+ initInlineForms = function() {
+ var hasErrors = false;
+ //inline.find("div.items div.module").each(function() {
+ inline.find("div.module").each(function() {
+ var form = $(this);
+ // add the options.formCssClass to all forms in the inline
+ if (form.attr('id') !== "") {
+ form.not("." + options.emptyCssClass).addClass(options.formCssClass);
+ }
+ // open the form if it has errors
+ if (form.find("ul.errorlist").length > 0) {
+ form.removeClass('closed').addClass('open');
+ // to open the inline
+ hasErrors = true;
+ }
+ });
+
+ // open the inline if it has forms with errors in it
+ if (hasErrors) {
+ inline.removeClass('closed').addClass('open');
+ }
+ };
+
+ // set this to prevent the browser from keeping the current value after reload
+ totalForms.attr("autocomplete", "off");
+
+ initInlineForms();
+
+ initAddButtons();
+
+ // delete button
+ // toggle the delete-checkbox and add the predelete-class to the row
+ inline.find("a." + options.deleteCssClass).click(deleteHandler);
+ inline.find("a." + options.removeCssClass).click(removeHandler);
+
+ // add options.predeleteCssClass to forms with the delete checkbox checked
+ inline.find("li.delete-handler-container input").each(function() {
+ var deleteInput = $(this);
+ if (deleteInput.attr("checked")) {
+ var form = $(deleteInput.parents("." + options.formCssClass).first());
+ if (form.hasClass("has_original")) {
+ form.toggleClass(options.predeleteCssClass);
+ }
+ }
+ });
+ });
+};
+})(django.jQuery);
diff --git a/lib/grappelli/media/js/jquery.init.js b/lib/grappelli/media/js/jquery.init.js
new file mode 100644
index 0000000..b8a75ab
--- /dev/null
+++ b/lib/grappelli/media/js/jquery.init.js
@@ -0,0 +1,3 @@
+// dropped
+// not used in grappelli
+// kept this file to prevent 404 \ No newline at end of file
diff --git a/lib/grappelli/media/js/jquery.js b/lib/grappelli/media/js/jquery.js
new file mode 100644
index 0000000..b8a75ab
--- /dev/null
+++ b/lib/grappelli/media/js/jquery.js
@@ -0,0 +1,3 @@
+// dropped
+// not used in grappelli
+// kept this file to prevent 404 \ No newline at end of file
diff --git a/lib/grappelli/media/js/jquery.min.js b/lib/grappelli/media/js/jquery.min.js
new file mode 100644
index 0000000..b8a75ab
--- /dev/null
+++ b/lib/grappelli/media/js/jquery.min.js
@@ -0,0 +1,3 @@
+// dropped
+// not used in grappelli
+// kept this file to prevent 404 \ No newline at end of file
diff --git a/lib/grappelli/media/js/prepopulate.min.js b/lib/grappelli/media/js/prepopulate.min.js
new file mode 100644
index 0000000..b8a75ab
--- /dev/null
+++ b/lib/grappelli/media/js/prepopulate.min.js
@@ -0,0 +1,3 @@
+// dropped
+// not used in grappelli
+// kept this file to prevent 404 \ No newline at end of file
diff --git a/lib/grappelli/media/js/timeparse.js b/lib/grappelli/media/js/timeparse.js
new file mode 100644
index 0000000..882f41d
--- /dev/null
+++ b/lib/grappelli/media/js/timeparse.js
@@ -0,0 +1,94 @@
+var timeParsePatterns = [
+ // 9
+ { re: /^\d{1,2}$/i,
+ handler: function(bits) {
+ if (bits[0].length == 1) {
+ return '0' + bits[0] + ':00';
+ } else {
+ return bits[0] + ':00';
+ }
+ }
+ },
+ // 13:00
+ { re: /^\d{2}[:.]\d{2}$/i,
+ handler: function(bits) {
+ return bits[0].replace('.', ':');
+ }
+ },
+ // 9:00
+ { re: /^\d[:.]\d{2}$/i,
+ handler: function(bits) {
+ return '0' + bits[0].replace('.', ':');
+ }
+ },
+ // 3 am / 3 a.m. / 3am
+ { re: /^(\d+)\s*([ap])(?:.?m.?)?$/i,
+ handler: function(bits) {
+ var hour = parseInt(bits[1]);
+ if (hour == 12) {
+ hour = 0;
+ }
+ if (bits[2].toLowerCase() == 'p') {
+ if (hour == 12) {
+ hour = 0;
+ }
+ return (hour + 12) + ':00';
+ } else {
+ if (hour < 10) {
+ return '0' + hour + ':00';
+ } else {
+ return hour + ':00';
+ }
+ }
+ }
+ },
+ // 3.30 am / 3:15 a.m. / 3.00am
+ { re: /^(\d+)[.:](\d{2})\s*([ap]).?m.?$/i,
+ handler: function(bits) {
+ var hour = parseInt(bits[1]);
+ var mins = parseInt(bits[2]);
+ if (mins < 10) {
+ mins = '0' + mins;
+ }
+ if (hour == 12) {
+ hour = 0;
+ }
+ if (bits[3].toLowerCase() == 'p') {
+ if (hour == 12) {
+ hour = 0;
+ }
+ return (hour + 12) + ':' + mins;
+ } else {
+ if (hour < 10) {
+ return '0' + hour + ':' + mins;
+ } else {
+ return hour + ':' + mins;
+ }
+ }
+ }
+ },
+ // noon
+ { re: /^no/i,
+ handler: function(bits) {
+ return '12:00';
+ }
+ },
+ // midnight
+ { re: /^mid/i,
+ handler: function(bits) {
+ return '00:00';
+ }
+ }
+];
+
+function parseTimeString(s) {
+ for (var i = 0; i < timeParsePatterns.length; i++) {
+ var re = timeParsePatterns[i].re;
+ var handler = timeParsePatterns[i].handler;
+ var bits = re.exec(s);
+ if (bits) {
+ return handler(bits);
+ }
+ }
+ return s;
+}
diff --git a/lib/grappelli/media/js/urlify.js b/lib/grappelli/media/js/urlify.js
new file mode 100644
index 0000000..d8f2549
--- /dev/null
+++ b/lib/grappelli/media/js/urlify.js
@@ -0,0 +1,140 @@
+var LATIN_MAP = {
+ 'À': 'A', 'Á': 'A', 'Â': 'A', 'Ã': 'A', 'Ä': 'A', 'Å': 'A', 'Æ': 'AE', 'Ç':
+ 'C', 'È': 'E', 'É': 'E', 'Ê': 'E', 'Ë': 'E', 'Ì': 'I', 'Í': 'I', 'Î': 'I',
+ 'Ï': 'I', 'Ð': 'D', 'Ñ': 'N', 'Ò': 'O', 'Ó': 'O', 'Ô': 'O', 'Õ': 'O', 'Ö':
+ 'O', 'Ő': 'O', 'Ø': 'O', 'Ù': 'U', 'Ú': 'U', 'Û': 'U', 'Ü': 'U', 'Ű': 'U',
+ 'Ý': 'Y', 'Þ': 'TH', 'ß': 'ss', 'à':'a', 'á':'a', 'â': 'a', 'ã': 'a', 'ä':
+ 'a', 'å': 'a', 'æ': 'ae', 'ç': 'c', 'è': 'e', 'é': 'e', 'ê': 'e', 'ë': 'e',
+ 'ì': 'i', 'í': 'i', 'î': 'i', 'ï': 'i', 'ð': 'd', 'ñ': 'n', 'ò': 'o', 'ó':
+ 'o', 'ô': 'o', 'õ': 'o', 'ö': 'o', 'ő': 'o', 'ø': 'o', 'ù': 'u', 'ú': 'u',
+ 'û': 'u', 'ü': 'u', 'ű': 'u', 'ý': 'y', 'þ': 'th', 'ÿ': 'y'
+}
+var LATIN_SYMBOLS_MAP = {
+ '©':'(c)'
+}
+var GREEK_MAP = {
+ 'α':'a', 'β':'b', 'γ':'g', 'δ':'d', 'ε':'e', 'ζ':'z', 'η':'h', 'θ':'8',
+ 'ι':'i', 'κ':'k', 'λ':'l', 'μ':'m', 'ν':'n', 'ξ':'3', 'ο':'o', 'π':'p',
+ 'ρ':'r', 'σ':'s', 'τ':'t', 'υ':'y', 'φ':'f', 'χ':'x', 'ψ':'ps', 'ω':'w',
+ 'ά':'a', 'έ':'e', 'ί':'i', 'ό':'o', 'ύ':'y', 'ή':'h', 'ώ':'w', 'ς':'s',
+ 'ϊ':'i', 'ΰ':'y', 'ϋ':'y', 'ΐ':'i',
+ 'Α':'A', 'Β':'B', 'Γ':'G', 'Δ':'D', 'Ε':'E', 'Ζ':'Z', 'Η':'H', 'Θ':'8',
+ 'Ι':'I', 'Κ':'K', 'Λ':'L', 'Μ':'M', 'Ν':'N', 'Ξ':'3', 'Ο':'O', 'Π':'P',
+ 'Ρ':'R', 'Σ':'S', 'Τ':'T', 'Υ':'Y', 'Φ':'F', 'Χ':'X', 'Ψ':'PS', 'Ω':'W',
+ 'Ά':'A', 'Έ':'E', 'Ί':'I', 'Ό':'O', 'Ύ':'Y', 'Ή':'H', 'Ώ':'W', 'Ϊ':'I',
+ 'Ϋ':'Y'
+}
+var TURKISH_MAP = {
+ 'ş':'s', 'Ş':'S', 'ı':'i', 'İ':'I', 'ç':'c', 'Ç':'C', 'ü':'u', 'Ü':'U',
+ 'ö':'o', 'Ö':'O', 'ğ':'g', 'Ğ':'G'
+}
+var RUSSIAN_MAP = {
+ 'а':'a', 'б':'b', 'в':'v', 'г':'g', 'д':'d', 'е':'e', 'ё':'yo', 'ж':'zh',
+ 'з':'z', 'и':'i', 'й':'j', 'к':'k', 'л':'l', 'м':'m', 'н':'n', 'о':'o',
+ 'п':'p', 'р':'r', 'с':'s', 'т':'t', 'у':'u', 'ф':'f', 'х':'h', 'ц':'c',
+ 'ч':'ch', 'ш':'sh', 'щ':'sh', 'ъ':'', 'ы':'y', 'ь':'', 'э':'e', 'ю':'yu',
+ 'я':'ya',
+ 'А':'A', 'Б':'B', 'В':'V', 'Г':'G', 'Д':'D', 'Е':'E', 'Ё':'Yo', 'Ж':'Zh',
+ 'З':'Z', 'И':'I', 'Й':'J', 'К':'K', 'Л':'L', 'М':'M', 'Н':'N', 'О':'O',
+ 'П':'P', 'Р':'R', 'С':'S', 'Т':'T', 'У':'U', 'Ф':'F', 'Х':'H', 'Ц':'C',
+ 'Ч':'Ch', 'Ш':'Sh', 'Щ':'Sh', 'Ъ':'', 'Ы':'Y', 'Ь':'', 'Э':'E', 'Ю':'Yu',
+ 'Я':'Ya'
+}
+var UKRAINIAN_MAP = {
+ 'Є':'Ye', 'І':'I', 'Ї':'Yi', 'Ґ':'G', 'є':'ye', 'і':'i', 'ї':'yi', 'ґ':'g'
+}
+var CZECH_MAP = {
+ 'č':'c', 'ď':'d', 'ě':'e', 'ň': 'n', 'ř':'r', 'š':'s', 'ť':'t', 'ů':'u',
+ 'ž':'z', 'Č':'C', 'Ď':'D', 'Ě':'E', 'Ň': 'N', 'Ř':'R', 'Š':'S', 'Ť':'T',
+ 'Ů':'U', 'Ž':'Z'
+}
+
+var POLISH_MAP = {
+ 'ą':'a', 'ć':'c', 'ę':'e', 'ł':'l', 'ń':'n', 'ó':'o', 'ś':'s', 'ź':'z',
+ 'ż':'z', 'Ą':'A', 'Ć':'C', 'Ę':'e', 'Ł':'L', 'Ń':'N', 'Ó':'o', 'Ś':'S',
+ 'Ź':'Z', 'Ż':'Z'
+}
+
+var LATVIAN_MAP = {
+ 'ā':'a', 'č':'c', 'ē':'e', 'ģ':'g', 'ī':'i', 'ķ':'k', 'ļ':'l', 'ņ':'n',
+ 'š':'s', 'ū':'u', 'ž':'z', 'Ā':'A', 'Č':'C', 'Ē':'E', 'Ģ':'G', 'Ī':'i',
+ 'Ķ':'k', 'Ļ':'L', 'Ņ':'N', 'Š':'S', 'Ū':'u', 'Ž':'Z'
+}
+
+var ALL_DOWNCODE_MAPS=new Array()
+ALL_DOWNCODE_MAPS[0]=LATIN_MAP
+ALL_DOWNCODE_MAPS[1]=LATIN_SYMBOLS_MAP
+ALL_DOWNCODE_MAPS[2]=GREEK_MAP
+ALL_DOWNCODE_MAPS[3]=TURKISH_MAP
+ALL_DOWNCODE_MAPS[4]=RUSSIAN_MAP
+ALL_DOWNCODE_MAPS[5]=UKRAINIAN_MAP
+ALL_DOWNCODE_MAPS[6]=CZECH_MAP
+ALL_DOWNCODE_MAPS[7]=POLISH_MAP
+ALL_DOWNCODE_MAPS[8]=LATVIAN_MAP
+
+var Downcoder = new Object();
+Downcoder.Initialize = function()
+{
+ if (Downcoder.map) // already made
+ return ;
+ Downcoder.map ={}
+ Downcoder.chars = '' ;
+ for(var i in ALL_DOWNCODE_MAPS)
+ {
+ var lookup = ALL_DOWNCODE_MAPS[i]
+ for (var c in lookup)
+ {
+ Downcoder.map[c] = lookup[c] ;
+ Downcoder.chars += c ;
+ }
+ }
+ Downcoder.regex = new RegExp('[' + Downcoder.chars + ']|[^' + Downcoder.chars + ']+','g') ;
+}
+
+downcode= function( slug )
+{
+ Downcoder.Initialize() ;
+ var downcoded =""
+ var pieces = slug.match(Downcoder.regex);
+ if(pieces)
+ {
+ for (var i = 0 ; i < pieces.length ; i++)
+ {
+ if (pieces[i].length == 1)
+ {
+ var mapped = Downcoder.map[pieces[i]] ;
+ if (mapped != null)
+ {
+ downcoded+=mapped;
+ continue ;
+ }
+ }
+ downcoded+=pieces[i];
+ }
+ }
+ else
+ {
+ downcoded = slug;
+ }
+ return downcoded;
+}
+
+
+function URLify(s, num_chars) {
+ // changes, e.g., "Petty theft" to "petty_theft"
+ // remove all these words from the string before urlifying
+ s = downcode(s);
+ removelist = ["a", "an", "as", "at", "before", "but", "by", "for", "from",
+ "is", "in", "into", "like", "of", "off", "on", "onto", "per",
+ "since", "than", "the", "this", "that", "to", "up", "via",
+ "with"];
+ r = new RegExp('\\b(' + removelist.join('|') + ')\\b', 'gi');
+ s = s.replace(r, '');
+ // if downcode doesn't hit, the char will be stripped here
+ s = s.replace(/[^-\w\s]/g, ''); // remove unneeded chars
+ s = s.replace(/^\s+|\s+$/g, ''); // trim leading/trailing spaces
+ s = s.replace(/[-\s]+/g, '-'); // convert spaces to hyphens
+ s = s.toLowerCase(); // convert to lowercase
+ return s.substring(0, num_chars);// trim to first num_chars chars
+}
+