Anfänglicher Commit: Producion Version Stand: Oktober 2014

This commit is contained in:
Christian Berg
2014-11-25 23:43:21 +01:00
commit daa35f5913
3381 changed files with 132346 additions and 0 deletions

View File

@@ -0,0 +1,157 @@
/**
* GRAPPELLI UTILS
* functions needed for Grappelli
*/
// grp jQuery namespace
var grp = {
"jQuery": jQuery.noConflict(true)
};
// django jQuery namespace
var django = {
"jQuery": grp.jQuery.noConflict(true)
};
// var jQuery = grp.jQuery.noConflict(true);
(function($) {
// dateformat
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;
}
};
// datepicker, timepicker init
grappelli.initDateAndTimePicker = function() {
// HACK: 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>[^<]*: /g, "<br>");
$(this).html(text);
});
var options = {
//appendText: '(mm/dd/yyyy)',
constrainInput: false,
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.
// use on() because couldn't find hook after datepicker generates it's complete dom.
$(".ui-datepicker-current").on('click', function() {
$.datepicker._selectDate(grappelli.datepicker_instance);
grappelli.datepicker_instance = null;
});
// init timepicker
$("input[class*='vTimeField']:not([id*='__prefix__'])").grp_timepicker();
// now-button for both date and time
// $("<button class='ui-datetime-now' />").insertAfter("button.ui-timepicker-trigger");
// $(".ui-datetime-now").on('click', function() {
// alert("Now for date and time: grappelli.js line 68 ff.");
// return false
// });
};
// changelist: filter
grappelli.initFilter = function() {
$("a.grp-pulldown-handler").click(function() {
var pulldownContainer = $(this).closest(".grp-pulldown-container");
$(pulldownContainer).toggleClass("grp-pulldown-state-open").children(".grp-pulldown-content").toggle();
});
$("a.grp-pulldown-handler").bind('mouseout', function() {
$(this).blur();
});
$(".grp-filter-choice").change(function(){
location.href = $(this).val();
});
};
// changelist: searchbar
grappelli.initSearchbar = function() {
var searchbar = $("input.grp-search-field");
searchbar.focus();
};
grappelli.updateSelectFilter = function(form) {
if (typeof SelectFilter != "undefined"){
form.find(".selectfilter").each(function(index, value){
var namearr = value.name.split('-');
SelectFilter.init(value.id, namearr[namearr.length-1], false, "{% admin_media_prefix %}");
});
form.find(".selectfilterstacked").each(function(index, value){
var namearr = value.name.split('-');
SelectFilter.init(value.id, namearr[namearr.length-1], true, "{% admin_media_prefix %}");
});
}
};
grappelli.reinitDateTimeFields = function(form) {
form.find(".vDateField").datepicker({
constrainInput: false,
showOn: 'button',
buttonImageOnly: false,
buttonText: '',
dateFormat: grappelli.getFormat('date')
});
form.find(".vTimeField").grp_timepicker();
};
// autocomplete helpers
grappelli.get_app_label = function(elem) {
var link = elem.next("a");
if (link.length > 0) {
var url = link.attr('href').split('/');
return url[url.length-3];
}
return false;
};
grappelli.get_model_name = function(elem) {
var link = elem.next("a");
if (link.length > 0) {
var url = link.attr('href').split('/');
return url[url.length-2];
}
return false;
};
grappelli.get_query_string = function(elem) {
var link = elem.next("a");
if (link.length > 0) {
var url = link.attr('href').split('/');
pairs = url[url.length-1].replace('?', '').split("&");
return pairs.join(":");
}
return false;
};
})(grp.jQuery);

1
htdocs/grappelli/js/grappelli.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,144 @@
/**
* GRAPPELLI AUTOCOMPLETE FK
* foreign-key lookup with autocomplete
*/
(function($){
var methods = {
init: function(options) {
options = $.extend({}, $.fn.grp_autocomplete_fk.defaults, options);
return this.each(function() {
var $this = $(this);
// assign attributes
$this.attr({
"tabindex": "-1",
"readonly": "readonly"
}).addClass("grp-autocomplete-hidden-field");
// remove djangos object representation (if given)
if ($this.next().next() && $this.next().next().attr("class") != "errorlist" && $this.next().next().attr("class") != "grp-help") $this.next().next().remove();
// build autocomplete wrapper
$this.next().after(loader).after(remove_link($this.attr('id')));
$this.parent().wrapInner("<div class='grp-autocomplete-wrapper-fk'></div>");
$this.parent().prepend("<input id='" + $this.attr("id") + "-autocomplete' type='text' class='vTextField' value='' />");
// extend options
options = $.extend({
wrapper_autocomplete: $this.parent(),
input_field: $this.prev(),
remove_link: $this.next().next().hide(),
loader: $this.next().next().next().hide()
}, $.fn.grp_autocomplete_fk.defaults, options);
// lookup
lookup_id($this, options); // lookup when loading page
lookup_autocomplete($this, options); // autocomplete-handler
$this.bind("change focus keyup", function() { // id-handler
lookup_id($this, options);
});
// labels
$("label[for='"+$this.attr('id')+"']").each(function() {
$(this).attr("for", $this.attr("id")+"-autocomplete");
});
});
}
};
$.fn.grp_autocomplete_fk = function(method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || ! method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on jQuery.grp_autocomplete_fk');
}
return false;
};
var loader = function() {
var loader = $('<div class="grp-loader">loader</div>');
return loader;
};
var remove_link = function(id) {
var removelink = $('<a class="grp-related-remove"></a>');
removelink.attr('id', 'remove_'+id);
removelink.attr('href', 'javascript://');
removelink.attr('onClick', 'return removeRelatedObject(this);');
removelink.hover(function() {
$(this).parent().toggleClass("grp-autocomplete-preremove");
});
return removelink;
};
var lookup_autocomplete = function(elem, options) {
options.wrapper_autocomplete.find("input:first")
.bind("focus", function() {
options.wrapper_autocomplete.addClass("grp-state-focus");
})
.bind("blur", function() {
options.wrapper_autocomplete.removeClass("grp-state-focus");
})
.autocomplete({
minLength: 1,
delay: 1000,
source: function(request, response) {
$.ajax({
url: options.autocomplete_lookup_url,
dataType: 'json',
data: "term=" + encodeURIComponent(request.term) + "&app_label=" + grappelli.get_app_label(elem) + "&model_name=" + grappelli.get_model_name(elem) + "&query_string=" + grappelli.get_query_string(elem),
beforeSend: function (XMLHttpRequest) {
options.loader.show();
},
success: function(data){
response($.map(data, function(item) {
return {label: item.label, value: item.value};
}));
},
complete: function (XMLHttpRequest, textStatus) {
options.loader.hide();
}
});
},
focus: function() { // prevent value inserted on focus
return false;
},
select: function(event, ui) {
options.input_field.val(ui.item.label);
elem.val(ui.item.value);
elem.val() ? $(options.remove_link).show() : $(options.remove_link).hide();
return false;
}
})
.data("ui-autocomplete")._renderItem = function(ul,item) {
if (!item.value) {
return $("<li></li>")
.data( "item.autocomplete", item )
.append( "<span class='error'>" + item.label)
.appendTo(ul);
} else {
return $("<li></li>")
.data( "item.autocomplete", item )
.append( "<a>" + item.label)
.appendTo(ul);
}
};
};
var lookup_id = function(elem, options) {
$.getJSON(options.lookup_url, {
object_id: elem.val(),
app_label: grappelli.get_app_label(elem),
model_name: grappelli.get_model_name(elem)
}, function(data) {
$.each(data, function(index) {
options.input_field.val(data[index].label);
elem.val() ? $(options.remove_link).show() : $(options.remove_link).hide();
});
});
};
$.fn.grp_autocomplete_fk.defaults = {
autocomplete_lookup_url: '',
lookup_url: ''
};
})(grp.jQuery);

View File

@@ -0,0 +1,181 @@
/**
* GRAPPELLI AUTOCOMPLETE GENERIC
* generic lookup with autocomplete
*/
(function($){
var methods = {
init: function(options) {
options = $.extend({}, $.fn.grp_autocomplete_generic.defaults, options);
return this.each(function() {
var $this = $(this);
// assign attributes
$this.attr({
"tabindex": "-1",
"readonly": "readonly"
}).addClass("grp-autocomplete-hidden-field");
// build autocomplete wrapper
var val = $(options.content_type).val() || $(options.content_type).find(':checked').val();
if (val) {
$this.after(loader).after(remove_link($this.attr('id'))).after(lookup_link($this.attr("id"),val));
}
$this.parent().wrapInner("<div class='grp-autocomplete-wrapper-fk'></div>");
$this.parent().prepend("<input id='" + $this.attr("id") + "-autocomplete' type='text' class='vTextField' value='' />");
// defaults
options = $.extend({
wrapper_autocomplete: $(this).parent(),
input_field: $(this).prev(),
remove_link: $this.nextAll("a.grp-related-remove").hide(),
loader: $this.nextAll("div.grp-loader").hide()
}, $.fn.grp_autocomplete_generic.defaults, options);
// lookup
if (val) {
lookup_id($this, options); // lookup when loading page
}
lookup_autocomplete($this, options); // autocomplete-handler
$this.bind("change focus keyup", function() { // id-handler
lookup_id($this, options);
});
$(options.content_type).bind("change", function() { // content-type-handler
update_lookup($(this), options);
});
// labels
$("label[for='"+$this.attr('id')+"']").each(function() {
$(this).attr("for", $this.attr("id")+"-autocomplete");
});
});
}
};
$.fn.grp_autocomplete_generic = function(method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || ! method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on jQuery.grp_autocomplete_generic');
}
return false;
};
var loader = function() {
var loader = $('<div class="grp-loader">loader</div>');
return loader;
};
var remove_link = function(id) {
var removelink = $('<a class="grp-related-remove"></a>');
removelink.attr('id', 'remove_'+id);
removelink.attr('href', 'javascript://');
removelink.attr('onClick', 'return removeRelatedObject(this);');
removelink.hover(function() {
$(this).parent().toggleClass("grp-autocomplete-preremove");
});
return removelink;
};
var lookup_link = function(id, val) {
var lookuplink = $('<a class="related-lookup"></a>');
lookuplink.attr('id', 'lookup_'+id);
lookuplink.attr('href', "../../../" + MODEL_URL_ARRAY[val].app + "/" + MODEL_URL_ARRAY[val].model + '/?t=id');
lookuplink.attr('onClick', 'return showRelatedObjectLookupPopup(this);');
return lookuplink;
};
var update_lookup = function(elem, options) {
var obj = $(options.object_id);
obj.val('');
obj.prev().val('');
// remove loader, a-related, related-lookup
obj.nextAll("a.related-lookup").remove();
obj.nextAll("a.grp-related-remove").remove();
obj.nextAll("div.grp-loader").remove();
var val = $(elem).val() || $(elem).find(':checked').val();
if (val) {
obj.after(loader).after(remove_link(obj.attr('id'))).after(lookup_link(obj.attr('id'),val));
options.remove_link = obj.nextAll("a.grp-related-remove").hide();
options.loader = obj.nextAll("div.grp-loader").hide();
}
};
var lookup_autocomplete = function(elem, options) {
options.wrapper_autocomplete.find("input:first")
.bind("focus", function() {
options.wrapper_autocomplete.addClass("grp-state-focus");
})
.bind("blur", function() {
options.wrapper_autocomplete.removeClass("grp-state-focus");
})
.autocomplete({
minLength: 1,
delay: 1000,
source: function(request, response) {
$.ajax({
url: options.autocomplete_lookup_url,
dataType: 'json',
data: "term=" + encodeURIComponent(request.term) + "&app_label=" + grappelli.get_app_label(elem) + "&model_name=" + grappelli.get_model_name(elem) + "&query_string=" + grappelli.get_query_string(elem),
beforeSend: function (XMLHttpRequest) {
var val = $(options.content_type).val() || $(options.content_type).find(':checked').val();
if (val) {
options.loader.show();
} else {
return false;
}
},
success: function(data){
response($.map(data, function(item) {
return {label: item.label, value: item.value};
}));
},
complete: function (XMLHttpRequest, textStatus) {
options.loader.hide();
}
});
},
focus: function() { // prevent value inserted on focus
return false;
},
select: function(event, ui) {
options.input_field.val(ui.item.label);
elem.val(ui.item.value);
elem.val() ? $(options.remove_link).show() : $(options.remove_link).hide();
return false;
}
})
.data("ui-autocomplete")._renderItem = function(ul,item) {
if (!item.value) {
return $("<li></li>")
.data( "item.autocomplete", item )
.append( "<span class='error'>" + item.label)
.appendTo(ul);
} else {
return $("<li></li>")
.data( "item.autocomplete", item )
.append( "<a>" + item.label)
.appendTo(ul);
}
};
};
var lookup_id = function(elem, options) {
$.getJSON(options.lookup_url, {
object_id: elem.val(),
app_label: grappelli.get_app_label(elem),
model_name: grappelli.get_model_name(elem)
}, function(data) {
$.each(data, function(index) {
options.input_field.val(data[index].label);
elem.val() ? $(options.remove_link).show() : $(options.remove_link).hide();
});
});
};
$.fn.grp_autocomplete_generic.defaults = {
autocomplete_lookup_url: '',
lookup_url: '',
content_type: '',
object_id: ''
};
})(grp.jQuery);

View File

@@ -0,0 +1,196 @@
/**
* GRAPPELLI AUTOCOMPLETE M2M
* many-to-many lookup with autocomplete
*/
(function($){
var methods = {
init: function(options) {
options = $.extend({}, $.fn.grp_autocomplete_m2m.defaults, options);
return this.each(function() {
var $this = $(this);
// assign attributes
$this.attr({
"tabindex": "-1",
"readonly": "readonly"
}).addClass("grp-autocomplete-hidden-field");
// build autocomplete wrapper
$this.next().after(loader).after(remove_link($this.attr('id')));
$this.parent().wrapInner("<div class='grp-autocomplete-wrapper-m2m'></div>");
//$this.parent().prepend("<ul class='search'><li class='search'><input id='" + $this.attr("id") + "-autocomplete' type='text' class='vTextField' value='' /></li></ul>").prepend("<ul class='repr'></ul>");
$this.parent().prepend("<ul class='grp-repr'><li class='grp-search'><input id='" + $this.attr("id") + "-autocomplete' type='text' class='vTextField' value='' /></li></ul>");
// defaults
options = $.extend({
wrapper_autocomplete: $this.parent(),
wrapper_repr: $this.parent().find("ul.grp-repr"),
wrapper_search: $this.parent().find("li.grp-search"),
remove_link: $this.next().next().hide(),
loader: $this.next().next().next().hide()
}, $.fn.grp_autocomplete_m2m.defaults, options);
// move errorlist outside the wrapper
if ($this.parent().find("ul.errorlist")) {
$this.parent().find("ul.errorlist").detach().appendTo($this.parent().parent());
}
// lookup
lookup_id($this, options); // lookup when loading page
lookup_autocomplete($this, options); // autocomplete-handler
$this.bind("change focus keyup", function() { // id-handler
lookup_id($this, options);
});
// labels
$("label[for='"+$this.attr('id')+"']").each(function() {
$(this).attr("for", $this.attr("id")+"-autocomplete");
});
// click on div > focus input
options.wrapper_autocomplete.bind("click", function(e) {
// prevent focus when clicking on remove/select
if (!$(e.target).hasClass("related-lookup") && !$(e.target).hasClass("grp-related-remove")) {
options.wrapper_search.find("input:first").focus();
}
});
});
}
};
$.fn.grp_autocomplete_m2m = function(method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || ! method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on jQuery.grp_autocomplete_m2m');
}
return false;
};
var value_add = function(elem, value, options) {
var values = [];
if (elem.val()) values = elem.val().split(",");
values.push(value);
elem.val(values.join(","));
return values.join(",");
};
var value_remove = function(elem, position, options) {
var values = [];
if (elem.val()) values = elem.val().split(",");
values.splice(position,1);
elem.val(values.join(","));
return values.join(",");
};
var loader = function() {
var loader = $('<div class="grp-loader">loader</div>');
return loader;
};
var remove_link = function(id) {
var removelink = $('<a class="grp-related-remove"></a>');
removelink.attr('id', 'remove_'+id);
removelink.attr('href', 'javascript://');
removelink.attr('onClick', 'return removeRelatedObject(this);');
removelink.hover(function() {
$(this).parent().toggleClass("grp-autocomplete-preremove");
});
return removelink;
};
var repr_add = function(elem, label, options) {
var repr = $('<li class="grp-repr"></li>');
var removelink = $('<a class="grp-m2m-remove" href="javascript://">' + label + '</a>');
repr.append(removelink);
repr.insertBefore(options.wrapper_search);
removelink.bind("click", function(e) { // remove-handler
var pos = $(this).parent().parent().children("li").index($(this).parent());
value_remove(elem, pos, options);
$(this).parent().remove();
elem.val() ? $(options.remove_link).show() : $(options.remove_link).hide();
e.stopPropagation(); // prevent focus on input
});
removelink.hover(function() {
$(this).parent().toggleClass("grp-autocomplete-preremove");
});
};
var lookup_autocomplete = function(elem, options) {
options.wrapper_search.find("input:first")
.bind("keydown", function(event) { // don't navigate away from the field on tab when selecting an item
if (event.keyCode === $.ui.keyCode.TAB && $(this).data("autocomplete").menu.active) {
event.preventDefault();
}
})
.bind("focus", function() {
options.wrapper_autocomplete.addClass("grp-state-focus");
})
.bind("blur", function() {
options.wrapper_autocomplete.removeClass("grp-state-focus");
})
.autocomplete({
minLength: 1,
delay: 1000,
position: {my: "left top", at: "left bottom", of: options.wrapper_autocomplete},
open: function(event, ui) {
$(".ui-menu").width(options.wrapper_autocomplete.outerWidth()-6);
},
source: function(request, response) {
$.ajax({
url: options.autocomplete_lookup_url,
dataType: 'json',
data: "term=" + encodeURIComponent(request.term) + "&app_label=" + grappelli.get_app_label(elem) + "&model_name=" + grappelli.get_model_name(elem) + "&query_string=" + grappelli.get_query_string(elem),
beforeSend: function (XMLHttpRequest) {
options.loader.show();
},
success: function(data){
response($.map(data, function(item) {
return {label: item.label, value: item.value};
}));
},
complete: function (XMLHttpRequest, textStatus) {
options.loader.hide();
}
});
},
focus: function() { // prevent value inserted on focus
return false;
},
select: function(event, ui) { // add repr, add value
repr_add(elem, ui.item.label, options);
value_add(elem, ui.item.value, options);
elem.val() ? $(options.remove_link).show() : $(options.remove_link).hide();
$(this).val("").focus();
return false;
}
})
.data("ui-autocomplete")._renderItem = function(ul,item) {
if (!item.value) {
return $("<li></li>")
.data( "item.autocomplete", item )
.append( "<span class='error'>" + item.label)
.appendTo(ul);
} else {
return $("<li></li>")
.data( "item.autocomplete", item )
.append( "<a>" + item.label)
.appendTo(ul);
}
};
};
var lookup_id = function(elem, options) {
$.getJSON(options.lookup_url, {
object_id: elem.val(),
app_label: grappelli.get_app_label(elem),
model_name: grappelli.get_model_name(elem)
}, function(data) {
options.wrapper_repr.find("li.grp-repr").remove();
options.wrapper_search.find("input").val("");
$.each(data, function(index) {
if (data[index].value) repr_add(elem, data[index].label, options);
});
elem.val() ? $(options.remove_link).show() : $(options.remove_link).hide();
});
};
})(grp.jQuery);

View File

@@ -0,0 +1,35 @@
/**
* GRAPPELLI COLLAPSIBLES
* handles collapsibles,
* excluding open/closing all elements
* within a group.
*/
(function($) {
$.fn.grp_collapsible = function(options){
var defaults = {
toggle_handler_slctr: ".grp-collapse-handler:first",
closed_css: "grp-closed",
open_css: "grp-open",
on_init: function() {},
on_toggle: function() {}
};
var opts = $.extend(defaults, options);
return this.each(function() {
_initialize($(this), opts);
});
};
var _initialize = function(elem, options) {
options.on_init(elem, options);
_register_handlers(elem, options);
};
var _register_handlers = function(elem, options) {
_register_toggle_handler(elem, options);
};
var _register_toggle_handler = function(elem, options) {
elem.children(options.toggle_handler_slctr).click(function() {
elem.toggleClass(options.closed_css).toggleClass(options.open_css);
options.on_toggle(elem, options);
});
};
})(grp.jQuery);

View File

@@ -0,0 +1,54 @@
/**
* GRAPPELLI GROUP COLLAPSIBLES
* handles open/closing of all elements
* with tabular- and stacked-inlines.
*/
(function($) {
$.fn.grp_collapsible_group = function(options){
var defaults = {
open_handler_slctr: ".grp-open-handler",
close_handler_slctr: ".grp-close-handler",
collapsible_container_slctr: ".grp-collapse",
closed_css: "grp-closed",
open_css: "grp-open",
on_init: function() {},
on_open: function() {},
on_close: function() {}
};
options = $.extend(defaults, options);
return this.each(function() {
_initialize($(this), options);
});
};
var _initialize = function(elem, options) {
options.on_init(elem, options);
_register_handlers(elem, options);
};
var _register_handlers = function(elem, options) {
_register_open_handler(elem, options);
_register_close_handler(elem, options);
};
var _register_open_handler = function(elem, options) {
elem.find(options.open_handler_slctr).each(function() {
$(this).click(function() {
options.on_open(elem, options);
elem.find(options.collapsible_container_slctr)
.removeClass(options.closed_css)
.addClass(options.open_css);
elem.removeClass(options.closed_css)
.addClass(options.open_css);
});
});
};
var _register_close_handler = function(elem, options) {
elem.find(options.close_handler_slctr).each(function() {
$(this).click(function() {
options.on_close(elem, options);
elem.find(options.collapsible_container_slctr)
.removeClass(options.open_css)
.addClass(options.closed_css);
});
});
};
})(grp.jQuery);

View File

@@ -0,0 +1,195 @@
/**
* GRAPPELLI INLINES
* jquery-plugin for inlines (stacked and tabular)
*/
(function($) {
$.fn.grp_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: "grp-add-handler", // CSS class applied to the add link
removeCssClass: "grp-remove-handler", // CSS class applied to the remove link
deleteCssClass: "grp-delete-handler", // CSS class applied to the delete link
emptyCssClass: "grp-empty-form", // CSS class applied to the empty row
formCssClass: "grp-dynamic-form", // CSS class applied to each form in a formset
predeleteCssClass: "grp-predelete",
onBeforeInit: function(form) {}, // Function called before a form is initialized
onBeforeAdded: function(inline) {}, // Function called before a form is added
onBeforeRemoved: function(form) {}, // Function called before a form is removed
onBeforeDeleted: function(form) {}, // Function called before a form is deleted
onAfterInit: function(form) {}, // Function called after a form has been initialized
onAfterAdded: function(form) {}, // Function called after a form has been added
onAfterRemoved: function(inline) {}, // Function called after a form has been removed
onAfterDeleted: function(form) {} // Function called after a form has been deleted
};
options = $.extend(defaults, options);
return this.each(function() {
var inline = $(this); // the current inline node
var totalForms = inline.find("#id_" + options.prefix + "-TOTAL_FORMS");
// set autocomplete to off in order to prevent the browser from keeping the current value after reload
totalForms.attr("autocomplete", "off");
// init inline and add-buttons
initInlineForms(inline, options);
initAddButtons(inline, options);
// button handlers
addButtonHandler(inline.find("a." + options.addCssClass), options);
removeButtonHandler(inline.find("a." + options.removeCssClass), options);
deleteButtonHandler(inline.find("a." + options.deleteCssClass), options);
});
};
getFormIndex = function(elem, options, regex) {
var formIndex = elem.find("[id^='id_" + options.prefix + "']").attr('id');
if (!formIndex) { return -1; }
return parseInt(regex.exec(formIndex)[1], 10);
}
updateFormIndex = function(elem, options, replace_regex, replace_with) {
elem.find(':input,span,table,iframe,label,a,ul,p,img,div').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"),
node_class = node.attr("class"),
node_onclick = node.attr("onclick");
if (node_id) { node.attr('id', node_id.replace(replace_regex, replace_with)); }
if (node_name) { node.attr('name', node_name.replace(replace_regex, replace_with)); }
if (node_for) { node.attr('for', node_for.replace(replace_regex, replace_with)); }
if (node_href) { node.attr('href', node_href.replace(replace_regex, replace_with)); }
if (node_class) { node.attr('class', node_class.replace(replace_regex, replace_with)); }
if (node_onclick) { node.attr('onclick', node_onclick.replace(replace_regex, replace_with)); }
});
};
initInlineForms = function(elem, options) {
elem.find("div.grp-module").each(function() {
var form = $(this);
// callback
options.onBeforeInit(form);
// add options.formCssClass to all forms in the inline
// except table/theader/add-item
if (form.attr('id') !== "") {
form.not("." + options.emptyCssClass).not(".grp-table").not(".grp-thead").not(".add-item").addClass(options.formCssClass);
}
// add options.predeleteCssClass to forms with the delete checkbox checked
form.find("li.grp-delete-handler-container input").each(function() {
if ($(this).is(":checked") && form.hasClass("has_original")) {
form.toggleClass(options.predeleteCssClass);
}
});
// callback
options.onAfterInit(form);
});
};
initAddButtons = function(elem, options) {
var totalForms = elem.find("#id_" + options.prefix + "-TOTAL_FORMS");
var maxForms = elem.find("#id_" + options.prefix + "-MAX_NUM_FORMS");
var addButtons = elem.find("a." + options.addCssClass);
// hide add button in case we've hit the max, except we want to add infinitely
if ((maxForms.val() !== '') && (maxForms.val()-totalForms.val()) <= 0) {
hideAddButtons(elem, options);
}
};
addButtonHandler = function(elem, options) {
elem.bind("click", function() {
var inline = elem.parents(".grp-group"),
totalForms = inline.find("#id_" + options.prefix + "-TOTAL_FORMS"),
maxForms = inline.find("#id_" + options.prefix + "-MAX_NUM_FORMS"),
addButtons = inline.find("a." + options.addCssClass),
empty_template = inline.find("#" + options.prefix + "-empty");
// callback
options.onBeforeAdded(inline);
// create new form
var index = parseInt(totalForms.val(), 10),
form = empty_template.clone(true);
form.removeClass(options.emptyCssClass)
.attr("id", empty_template.attr('id').replace("-empty", index));
// update form index
var re = /__prefix__/g;
updateFormIndex(form, options, re, index);
// after "__prefix__" strings has been substituted with the number
// of the inline, we can add the form to DOM, not earlier.
// This way we can support handlers that track live element
// adding/removing, like those used in django-autocomplete-light
form.insertBefore(empty_template)
.addClass(options.formCssClass);
// update total forms
totalForms.val(index + 1);
// hide add button in case we've hit the max, except we want to add infinitely
if ((maxForms.val() !== 0) && (maxForms.val() !== "") && (maxForms.val() - totalForms.val()) <= 0) {
hideAddButtons(inline, options);
}
// callback
options.onAfterAdded(form);
});
};
removeButtonHandler = function(elem, options) {
elem.bind("click", function() {
var inline = elem.parents(".grp-group"),
form = $(this).parents("." + options.formCssClass).first(),
totalForms = inline.find("#id_" + options.prefix + "-TOTAL_FORMS"),
maxForms = inline.find("#id_" + options.prefix + "-MAX_NUM_FORMS"),
re = /-(\d+)-/,
removedFormIndex = getFormIndex(form, options, re);
// callback
options.onBeforeRemoved(form);
// remove form
form.remove();
// update total forms
totalForms.val(parseInt(totalForms.val(), 10) - 1);
// show add button in case we've dropped below max
if ((maxForms.val() !== 0) && (maxForms.val() - totalForms.val()) > 0) {
showAddButtons(inline, options);
}
// update form index (only forms with a higher index than the removed form)
inline.find("." + options.formCssClass).each(function() {
var form = $(this),
formIndex = getFormIndex(form, options, re);
if (formIndex > removedFormIndex) {
updateFormIndex(form, options, re, "-" + (formIndex - 1) + "-");
}
});
// callback
options.onAfterRemoved(inline);
});
};
deleteButtonHandler = function(elem, options) {
elem.bind("click", function() {
var deleteInput = $(this).prev(),
form = $(this).parents("." + options.formCssClass).first();
// callback
options.onBeforeDeleted(form);
// toggle options.predeleteCssClass and toggle checkbox
if (form.hasClass("has_original")) {
form.toggleClass(options.predeleteCssClass);
if (deleteInput.prop("checked")) {
deleteInput.removeAttr("checked");
} else {
deleteInput.prop("checked", true);
}
}
// callback
options.onAfterDeleted(form);
});
};
hideAddButtons = function(elem, options) {
var addButtons = elem.find("a." + options.addCssClass);
addButtons.hide().parents('.grp-add-item').hide();
};
showAddButtons = function(elem, options) {
var addButtons = elem.find("a." + options.addCssClass);
addButtons.show().parents('.grp-add-item').show();
};
})(grp.jQuery);

View File

@@ -0,0 +1,66 @@
/**
* GRAPPELLI RELATED FK
* foreign-key lookup
*/
(function($){
var methods = {
init: function(options) {
options = $.extend({}, $.fn.grp_related_fk.defaults, options);
return this.each(function() {
var $this = $(this);
var $parent = $this.parent();
// remove djangos object representation
if ($parent.find('a.related-lookup').next().is('strong')) {
$parent.find('a.related-lookup').get(0).nextSibling.nodeValue="";
$parent.find('a.related-lookup').next('strong').remove();
}
// add placeholder
$parent.find('a.related-lookup').after(options.placeholder);
// add related class
$this.addClass('grp-has-related-lookup');
// lookup
lookup_id($this, options); // lookup when loading page
$this.bind("change focus keyup", function() { // id-handler
lookup_id($this, options);
});
});
}
};
$.fn.grp_related_fk = function(method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || ! method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on jQuery.grp_related_fk');
}
return false;
};
var lookup_id = function(elem, options) {
var text = elem.parent().find('.grp-placeholder-related-fk');
$.getJSON(options.lookup_url, {
object_id: elem.val(),
app_label: grappelli.get_app_label(elem),
model_name: grappelli.get_model_name(elem),
query_string: grappelli.get_query_string(elem)
}, function(data) {
if (data[0].label === "") {
text.hide();
} else {
text.show();
}
text.html('<span class="grp-placeholder-label">' + data[0].label + '</span>');
});
};
$.fn.grp_related_fk.defaults = {
placeholder: '<div class="grp-placeholder-related-fk"></div>',
repr_max_length: 30,
lookup_url: ''
};
})(grp.jQuery);

View File

@@ -0,0 +1,89 @@
/**
* GRAPPELLI RELATED FK
* generic lookup
*/
(function($){
var methods = {
init: function(options) {
options = $.extend({}, $.fn.grp_related_generic.defaults, options);
return this.each(function() {
var $this = $(this);
// add placeholder
var val = $(options.content_type).val() || $(options.content_type).find(':checked').val();
if (val) {
$this.after(options.placeholder).after(lookup_link($this.attr('id'),val));
}
// add related class
$this.addClass('grp-has-related-lookup');
// lookup
if (val) {
lookup_id($this, options); // lookup when loading page
}
$this.bind("change focus keyup", function() { // id-handler
lookup_id($this, options);
});
$(options.content_type).bind("change", function() { // content-type-handler
update_lookup($(this), options);
});
});
}
};
$.fn.grp_related_generic = function(method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || ! method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on jQuery.grp_related_generic');
}
return false;
};
var lookup_link = function(id, val) {
var lookuplink = $('<a class="related-lookup"></a>');
lookuplink.attr('id', 'lookup_'+id);
lookuplink.attr('href', "../../../" + MODEL_URL_ARRAY[val].app + "/" + MODEL_URL_ARRAY[val].model + '/?t=id');
lookuplink.attr('onClick', 'return showRelatedObjectLookupPopup(this);');
return lookuplink;
};
var update_lookup = function(elem, options) {
var obj = $(options.object_id);
obj.val('');
obj.parent().find('a.related-lookup').remove();
obj.parent().find('.grp-placeholder-related-generic').remove();
var val = $(elem).val() || $(elem).find(':checked').val();
if (val) {
obj.after(options.placeholder).after(lookup_link(obj.attr('id'),val));
}
};
var lookup_id = function(elem, options) {
var text = elem.next().next();
$.getJSON(options.lookup_url, {
object_id: elem.val(),
app_label: grappelli.get_app_label(elem),
model_name: grappelli.get_model_name(elem),
query_string: grappelli.get_query_string(elem)
}, function(data) {
if (data[0].label === "") {
text.hide();
} else {
text.show();
}
text.html('<span class="grp-placeholder-label">' + data[0].label + '</span>');
});
};
$.fn.grp_related_generic.defaults = {
placeholder: '<div class="grp-placeholder-related-generic" style="display:none"></div>',
repr_max_length: 30,
lookup_url: '',
content_type: '',
object_id: ''
};
})(grp.jQuery);

View File

@@ -0,0 +1,62 @@
/**
* GRAPPELLI RELATED M2M
* m2m lookup
*/
(function($){
var methods = {
init: function(options) {
options = $.extend({}, $.fn.grp_related_m2m.defaults, options);
return this.each(function() {
var $this = $(this);
// add placeholder
$this.parent().find('a.related-lookup').after(options.placeholder);
// change lookup class
$this.next().addClass("grp-m2m");
// add related class
$this.addClass('grp-has-related-lookup');
// lookup
lookup_id($this, options); // lookup when loading page
$this.bind("change focus keyup", function() { // id-handler
lookup_id($this, options);
});
});
}
};
$.fn.grp_related_m2m = function(method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || ! method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on jQuery.grp_related_m2m');
}
return false;
};
var lookup_id = function(elem, options) {
$.getJSON(options.lookup_url, {
object_id: elem.val(),
app_label: grappelli.get_app_label(elem),
model_name: grappelli.get_model_name(elem),
query_string: grappelli.get_query_string(elem)
}, function(data) {
values = $.map(data, function (a) { return '<span class="grp-placeholder-label">' + a.label + '</span>'; });
if (values === "") {
elem.parent().find('.grp-placeholder-related-m2m').hide();
} else {
elem.parent().find('.grp-placeholder-related-m2m').show();
}
elem.parent().find('.grp-placeholder-related-m2m').html(values.join('<span class="grp-separator"></span>'));
});
};
$.fn.grp_related_m2m.defaults = {
placeholder: '<div class="grp-placeholder-related-m2m"></div>',
repr_max_length: 30,
lookup_url: ''
};
})(grp.jQuery);

View File

@@ -0,0 +1,186 @@
/**
* GRAPPELLI 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.grp_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 with 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();
}
}
});
// close on esc
$(document).keyup(function(e) {
if (e.keyCode == 27) {
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.prop("disabled")) {
this.button.prop("disabled", true);
} else {
// register event
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 container
_showTimepicker: function() {
var browserHeight = document.documentElement.clientHeight;
var scrollY = document.documentElement.scrollTop || document.body.scrollTop;
var tpInputHeight = this.element.outerHeight();
var tpDialogHeight = this.timepicker.outerHeight() + tpInputHeight;
var offsetTop = this.element.offset().top;
var offsetLeft = this.element.offset().left;
// check the remaining space within the viewport
// 60 counts for fixed header/footer
var checkSpace = offsetTop - scrollY + tpDialogHeight + 60;
// position timepicker below or above the input
if (checkSpace < browserHeight) {
// place the timepicker below input
var below = offsetTop + tpInputHeight;
this.timepicker.css('left', offsetLeft + 'px').css('top', below + 'px');
} else {
// place timepicker above input
var above = offsetTop - tpDialogHeight + tpInputHeight;
this.timepicker.css('left', offsetLeft + 'px').css('top', above + 'px');
}
// show timepicker
this.timepicker.show();
},
destroy: function() {
$.Widget.prototype.destroy.apply(this, arguments); // default destroy
// now do other stuff particular to this widget
}
});
})(grp.jQuery);