Dynamic inputs fixed.

see #585
This commit is contained in:
Antoine Nguyen
2014-09-28 12:46:30 +02:00
parent 12c1fca57c
commit f0fd2b166a
16 changed files with 250 additions and 102 deletions
@@ -1,8 +1,11 @@
/**
* @module admin
* @desc Utility classes for the admin application
* Creates an instance of Admin.
*
* @constructor
* @param {Object} options - instance options
* @classdesc This class contains utility methods used by the admin
* application.
*/
var Admin = function(options) {
Listing.call(this, options);
};
@@ -74,8 +77,13 @@ Admin.prototype = {
Admin.prototype = $.extend({}, Listing.prototype, Admin.prototype);
/*
* Domains
/**
* Creates an instance of Domains.
*
* @constructor
* @param {Object} options - instance options
* @classdesc This class extends the Admin one by adding methods
* specific to domains management.
*/
var Domains = function(options) {
Admin.call(this, options);
@@ -116,9 +124,22 @@ Domains.prototype = {
this.change_inputs_state(($target.val() == "yes") ? false : true);
},
/**
* Initialize the main form contained in the domain edition modal.
*
* @this Domains
*/
generalform_init: function() {
$('input:text:visible:first').focus();
$("#id_aliases").dynamic_input();
$("#original_aliases").dynamic_input({
input_added: function($row) {
$row.find("label").html("");
},
input_removed: function($input) {
$input.parents(".form-group").remove();
return true;
}
});
},
optionsform_init: function() {
@@ -181,8 +202,13 @@ Domains.prototype = {
Domains.prototype = $.extend({}, Admin.prototype, Domains.prototype);
/*
* Identities
/**
* Creates an instance of Identities.
*
* @constructor
* @param {Object} options - instance options
* @classdesc This class extends the Admin one by adding methods
* specific to identities management.
*/
var Identities = function(options) {
Admin.call(this, options);
@@ -191,6 +217,7 @@ var Identities = function(options) {
Identities.prototype = {
initialize: function(options) {
Admin.prototype.initialize.call(this, options);
this.domain_list = [];
this.register_tag_handler("idt", this.generic_tag_handler);
this.register_tag_handler("grp", this.grp_tag_handler);
},
@@ -247,10 +274,29 @@ Identities.prototype = {
return false;
},
/**
* Retrieve a list of domain from the server.
*
* @this Identities
* @return {Array} a list of domain names
*/
get_domain_list: function() {
if (!this.domain_list.length) {
$.ajax({
url: this.options.domain_list_url,
dataType: "json",
async: false
}).done($.proxy(function(data) {
this.domain_list = data;
}, this));
}
return this.domain_list;
},
simpleuser_mode: function() {
$("#id_username").autocompleter({
from_character: "@",
choices: get_domains_list
choices: $.proxy(this.get_domain_list, this)
});
$("#id_email").addClass("disabled")
.attr("readonly", "")
@@ -283,22 +329,38 @@ Identities.prototype = {
mailform_init: function() {
$("#id_aliases").autocompleter({
from_character: "@",
choices: get_domains_list
}).dynamic_input();
choices: $.proxy(this.get_domain_list, this)
});
$("#original_aliases").dynamic_input({
input_added: function($row) {
$row.find("label").html("");
},
input_removed: function($input) {
$input.parents(".form-group").remove();
return true;
}
});
$("#id_email").autocompleter({
from_character: "@",
choices: get_domains_list
choices: $.proxy(this.get_domain_list, this)
});
if ($("#id_role").length) {
$("#id_role").trigger("change");
} else {
this.simpleuser_mode();
}
$("#id_domains")
.autocompleter({
choices: get_domains_list
})
.dynamic_input();
$("#id_domains").autocompleter({
choices: $.proxy(this.get_domain_list, this)
});
$("#original_domains").dynamic_input({
input_added: function($row) {
$row.find("label").html("");
},
input_removed: function($input) {
$input.parents(".form-group").remove();
return true;
}
});
activate_widget.call($("#id_quota_act"));
},
@@ -339,9 +401,17 @@ Identities.prototype = {
aliasform_cb: function() {
$("#id_email").autocompleter({
from_character: "@",
choices: get_domains_list
choices: $.proxy(this.get_domain_list, this)
});
$("#original_recipients").dynamic_input({
input_added: function($row) {
$row.find("label").html("");
},
input_removed: function($input) {
$input.parents(".form-group").remove();
return true;
}
});
$("#id_recipients").dynamic_input();
$(".submit").on('click', $.proxy(function(e) {
simple_ajax_form_post(e, {
formid: "aliasform",
@@ -14,7 +14,7 @@
{% for da in domadmins %}<tr>
<td>{{ da.username }}</td>
<td>{{ da.fullname }}</td>
<td name="actions">{% if perms.admin.change_domain%}{% domadmin_actions da.id domain.id %}{% endif %}</td>
<td name="actions">{% if perms.admin.change_domain %}{% domadmin_actions da.id domain.id %}{% endif %}</td>
</tr>{% endfor %}
</table>{% else %}<div class="alert alert-info">{% trans "No domain administrator defined" %}</div>{% endif %}
</div>
@@ -4,20 +4,10 @@
{{ block.super }}
<script type="text/javascript" src="{{ STATIC_URL }}js/autocompleter.js"></script>
<script type="text/javascript">
function get_domains_list(container) {
$.getJSON("{% url 'admin:domain_flat_list' %}", function(data) {
container.value = data;
});
}
function get_account_list(container) {
$.getJSON("{% url 'admin:account_list' %}", function(data) {
container.value = data;
});
}
$(document).ready(function() {
admin = new Identities();
admin = new Identities({
domain_list_url: "{% url 'admin:domain_flat_list' %}"
});
});
</script>
{% extra_static_content "identities" user %}
@@ -1,5 +1,5 @@
{% load form_tags %}
{% render_field form.email %}
{% include "common/generic_field.html" with field=form.quota help_display_mode="tooltip" appended_text=_("MB") activator=True activator_value=form.quota_act.value deactivate_if_empty=False activator_label=_("Use domain default value") %}
{% include "common/generic_field.html" with field=form.quota help_display_mode="tooltip" label_width="col-sm-4" appended_text=_("MB") activator=True activator_value=form.quota_act.value deactivate_if_empty=False activator_label=_("Use domain default value") %}
{% render_extra_fields form %}
{% render_fields_group form "aliases" %}
@@ -228,7 +228,7 @@ def identity_modify_link(identity, active_tab='default'):
linkdef["url"] += "?active_tab=%s" % active_tab
linkdef["modalcb"] = "admin.editaccount_cb"
else:
linkdef["url"] = reverse("admin:alias_change", args=[identity.id]),
linkdef["url"] = reverse("admin:alias_change", args=[identity.id])
linkdef["modalcb"] = "admin.aliasform_cb"
return render_link(linkdef)
@@ -237,7 +237,8 @@ def identity_modify_link(identity, active_tab='default'):
def domadmin_actions(daid, domid):
actions = [{
"name": "removeperm",
"url": reverse("admin:permission_delete") + "?domid=%s&daid=%s" % (domid, daid),
"url": "{}?domid={}&daid={}".format(
reverse("admin:permission_delete"), domid, daid),
"img": "fa fa-trash",
"title": _("Remove this permission")
}]
@@ -59,7 +59,15 @@ RelayDomains.prototype = {
domainform_cb: function() {
$('#btnscan').click(this.launch_scan);
$('input:text:visible:first').focus();
$("#id_aliases").dynamic_input();
$("#original_aliases").dynamic_input({
input_added: function($row) {
$row.find("label").html("");
},
input_removed: function($input) {
$input.parents(".form-group").remove();
return true;
}
});
$(".submit").on('click', $.proxy(function(e) {
simple_ajax_form_post(e, {
formid: "rdomform",
+2 -4
View File
@@ -18,7 +18,6 @@ class UserCalendarForm(forms.ModelForm):
model = UserCalendar
fields = ('name', 'mailbox', )
widgets = {
'name': forms.TextInput(attrs={"class": "form-control"}),
'mailbox': forms.widgets.Select(
attrs={"class": "selectpicker", "data-live-search": "true"}
)
@@ -63,9 +62,8 @@ class RightsForm(forms.Form, DynamicForm):
"""
"""
username = forms.CharField(
required=False, widget=forms.widgets.TextInput(
attrs={"placeholder": ugettext_lazy("Username"),
"class": "form-control"}
label="", required=False, widget=forms.widgets.TextInput(
attrs={"placeholder": ugettext_lazy("Username")}
)
)
read_access = forms.BooleanField(
@@ -1,5 +1,5 @@
/**
* @module
* @module jquery.dynamicrule
*/
(function($) {
@@ -11,7 +11,12 @@
constructor: DynamicRule,
/**
* Constructor.
* Creates an instance of DynamicRule.
*
* @constructor
* @this {DynamicRule}
* @param {object} element -
* @param {dictionary} options - instance options
*/
initialize: function(element, options) {
this.$element = $(element);
@@ -49,7 +54,8 @@
var $clone = this.$element.clone();
var $btn = $clone.find("a");
$btn.find("i").removeClass("icon-plus").addClass("icon-trash");
$clone.attr("id", "");
$btn.find("span").removeClass("fa-plus").addClass("fa-trash");
$btn.removeClass("addrule").addClass("delrule");
$clone.find("#id_username")
.attr("id", "id_username_" + this.rules_counter)
@@ -67,6 +73,8 @@
/**
* Delete a given rule.
*
* @param {object} event - event object
*/
delete_rule: function(event) {
event.preventDefault();
@@ -1,6 +1,10 @@
/**
* @module radicale
* @desc Utility class for the radicale extension
* Creates an instance of Radicale.
*
* @constructor
* @param {Object} options - instance options
* @classdesc This class contains a set of methods used by the
* Radicale application.
*/
var Radicale = function(options) {
Listing.call(this, options);
@@ -32,9 +36,10 @@ Radicale.prototype = {
/**
* Refresh the listing.
*
* @this Radicale
* @param {Object} data - the ajax call response (JSON)
*/
reload_listing: function(data) {
console.log("reload");
this.navobj.update(true);
if (data) {
$("body").notify("success", data, 2000);
@@ -43,6 +48,8 @@ Radicale.prototype = {
/**
* Rights form initialization.
*
* @this Radicale
*/
rightsform_init: function() {
$("#original_row").dynamicrule();
@@ -1,7 +1,9 @@
{% load i18n %}
{% load form_tags %}
<div class="form-inline access-rule">
{{ username }}
{% render_field username %}
<label class="checkbox">{{ read_access }} {% trans "Read" %}</label>
<label class="checkbox">{{ write_access }} {% trans "Write" %}</label>
<a href="#" class="delrule"><span class="glyphicon glyphicon-trash"></span></a>
<a href="#" class="delrule"><span class="fa fa-trash"></span></a>
</div>
@@ -1,12 +1,12 @@
{% load lib_tags radicale_tags %}
{% load form_tags radicale_tags %}
{% if error %}<div class="alert alert-danger">{{ error }}</div>{% endif %}
<form action="{{ action }}" method="POST" id="{{ formid }}">
{% csrf_token %}
<div class="form-inline" id="original_row">
{{ form.username }}
<div class="form-inline access-rule" id="original_row">
{% render_field form.username %}
<label class="checkbox">{{ form.read_access }} {{ form.read_access.label }}</label>
<label class="checkbox">{{ form.write_access }} {{ form.write_access.label }}</label>
<a href="#" class="addrule"><span class="glyphicon glyphicon-plus"></span></a>
<a href="#" class="addrule"><span class="fa fa-plus"></span></a>
</div>
{% render_rule_fields form %}
</form>
@@ -80,8 +80,7 @@ def calendar_actions(calendar):
@register.simple_tag
def render_rule_fields(form):
"""Render access rules for a given calendar.
"""
"""Render access rules for a given calendar."""
from django.forms import forms
cpt = 1
+5 -3
View File
@@ -5,7 +5,6 @@ Form rendering tags.
from django import forms
from django import template
from django.template.loader import render_to_string
from django.utils.translation import ugettext_lazy
from modoboa.lib.formutils import SeparatorField
@@ -55,6 +54,7 @@ def render_field(
@register.simple_tag
def render_fields_group(form, pattern):
"""Render a group of fields."""
first = forms.forms.BoundField(form, form.fields[pattern], pattern)
configure_field_classes(first)
label = first.label
@@ -73,7 +73,8 @@ def render_fields_group(form, pattern):
cpt += 1
return render_to_string("common/generic_fields_group.html", dict(
label=label, help_text=first.help_text, group=group, haserror=haserror
label=label, help_text=first.help_text, group=group, haserror=haserror,
pattern=pattern
))
@@ -82,7 +83,8 @@ def render_extra_fields(form):
result = ''
for fname in form.extra_fields:
result += render_to_string("common/generic_field.html", {
'field': form[fname], 'help_display_mode': 'tooltip'
'field': form[fname], 'help_display_mode': 'tooltip',
'label_width': 'col-sm-4'
})
return result
+76 -21
View File
@@ -1,19 +1,31 @@
(function($) {
/**
* Creates an instance of DynamicInput.
*
* @constructor
* @this DynamicInput
* @param {Object} element -
* @param {Object} options -
*/
var DynamicInput = function(element, options) {
this.$element = $(element);
this.$input = (this.$element.is("input")) ?
this.$element : this.$element.find("input");
this.options = $.extend({}, $.fn.dynamic_input.defaults, options);
this.nextid = 1;
this.baseid = this.$element.attr("id");
this.basename = this.$element.attr("name").split("_")[0];
this.baseid = this.$input.attr("id");
this.basename = this.$input.attr("name").split("_")[0];
this.$input.keyup($.proxy(this.checknotempty, this));
this.$element.keyup($.proxy(this.checknotempty, this));
for (var cpt = 1; true; cpt++) {
var $input = $("#" + this.baseid + "_" + cpt);
if (!$input.length) {
break;
}
$input.after(this.addrmlink($input));
$input.parent().after(this.addrmlink($input));
$input.focusout(this.checkempty);
this.nextid++;
}
@@ -22,57 +34,98 @@
DynamicInput.prototype = {
constructor: DynamicInput,
/**
* Add a remove link right to an input.
*
* @this DynamicInput
* @param {Object} input - target input
* @return {Object} - created link
*/
addrmlink: function(input) {
var $rmlink = $('<a href="#"'
+ 'id="' + input.attr("id") + '_rmbtn' + '"'
+ '"><i class="fa fa-remove"></i></a>');
var $rmlink = $(
"<a href='#' id='{0}_rmbtn'>".format(input.attr("id")) +
"<span class='fa fa-remove'></span></a>"
);
$rmlink.click($.proxy(this.removeinput, $rmlink));
$rmlink.click($.proxy(this.removeinput, this));
return $rmlink;
},
/**
* Add a new input.
*
* @this DynamicInput
*/
addinput: function() {
var $clone = this.$element.clone();
var id = this.baseid + '_' + this.nextid;
var name = this.basename + '_' + this.nextid;
var $ninput = $('<input type="text"'
+ 'id="' + id + '" '
+ 'name="' + name + '"'
+ '/>');
$clone.insertAfter(this.$element);
var $ninput = $clone.find("input").attr("id", id).attr("name", name);
var $rmlink = this.addrmlink($ninput);
$clone.append($rmlink);
$ninput.focusout(this.checkempty);
$ninput.val(this.$element.val());
this.$element.val(this.options.emptylabel);
this.$element.after($ninput, $rmlink);
$ninput.val(this.$input.val());
this.$input.val(this.options.emptylabel);
this.nextid++;
if (this.options.input_added) {
this.options.input_added($clone);
}
},
/**
* Click event. Remove an input.
*
* @this DynamicInput
* @param {Object} evt - event object
* @param {boolean} force - force the removal if true
*/
removeinput: function(evt, force) {
evt.preventDefault();
var id = "#" + this.attr("id").replace("_rmbtn", "");
var $link = get_target(evt, "a");
var id = "#" + $link.attr("id").replace("_rmbtn", "");
var $input = $(id);
if ($input.val() == "" && (force == undefined || !force)) {
if ($input.val() === "" && (force === undefined || !force)) {
return;
}
if (this.options.input_removed && this.options.input_removed($input)) {
return;
}
$input.remove();
this.remove();
$link.remove();
},
/**
* Check if input's value is not empty before adding a new input.
*
* @this DynamicInput
* @param {Object} evt - event object
*/
checknotempty: function(evt) {
if (evt.keyCode != 13) {
return;
}
if (this.$element.val() == "") {
if (this.$input.val() === "") {
return;
}
this.addinput();
},
/**
* Focus out event : remove the input if it's value is empty.
*
* @param {Object} evt - event object
*/
checkempty: function(evt) {
var $input = $(evt.target);
if ($input.val() == "") {
if ($input.val() === "") {
$("#" + $input.attr("id") + "_rmbtn").trigger("click", true);
}
}
@@ -99,7 +152,9 @@
$.fn.dynamic_input.Constructor = DynamicInput;
$.fn.dynamic_input.defaults = {
emptylabel: ""
emptylabel: "",
input_added: null,
input_removed: null
};
})(jQuery);
+10 -4
View File
@@ -249,8 +249,14 @@ function get_parameter_by_name(url, name) {
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
/*
* Return the target associated to an event object.
/**
* Return the target associated to an event object. You will generally
* use this function within a click event handler configured on a <a/>
* tag containg an image. (because e.target will often point to the
* image...)
*
* @param {Object} e - event object
* @param {string} tag - desired tag
*/
function get_target(e, tag) {
var $target = $(e.target);
@@ -261,7 +267,7 @@ function get_target(e, tag) {
return $target.parent();
}
/*
/**
* Send a simple AJAX request.
*/
function simple_ajax_request(e, uoptions) {
@@ -269,7 +275,7 @@ function simple_ajax_request(e, uoptions) {
var defaults = {};
var options = $.extend({}, defaults, uoptions);
if (e != undefined) e.preventDefault();
if (e !== undefined) e.preventDefault();
$.ajax({
url: $this.attr("href"),
dataType: 'json'
@@ -1,21 +1,23 @@
{% load i18n %}
<div class="form-group {% if haserror %} error{% endif %}">
<label class="control-label col-lg-4 col-md-4 col-sm-4">
{{ label }}
{% if help_text %}
<a href="#" class="help" data-original-title="{% trans 'Help:' %} {{ label }}" data-content="{{ help_text }}" rel="popover">?</a>
{% endif %}
</label>
<div class="controls col-lg-5 col-md-5 col-sm-5">
{% for field in group %}
{{ field }}
{% if field.errors %}
<p class="help-block">
{% for error in field.errors %}
{{ error|safe }}
{% endfor %}
</p>
{% endif %}
{% endfor %}
</div>
{% for field in group %}
<div{% if forloop.first %} id="original_{{ pattern }}"{% endif %} class="form-group {% if haserror %} error{% endif %}">
<label class="control-label col-lg-4 col-md-4 col-sm-4">
{% if forloop.first %}
{{ label }}
{% if help_text %}
<a href="#" class="help" data-original-title="{% trans 'Help:' %} {{ label }}" data-content="{{ help_text }}" rel="popover">?</a>
{% endif %}
{% endif %}
</label>
<div class="controls col-lg-5 col-md-5 col-sm-5">
{{ field }}
{% if field.errors %}
<p class="help-block">
{% for error in field.errors %}
{{ error|safe }}
{% endfor %}
</p>
{% endif %}
</div>
</div>
{% endfor %}