diff --git a/modoboa/extensions/admin/static/admin/js/admin.js b/modoboa/extensions/admin/static/admin/js/admin.js index 33162d160..9c596b78d 100644 --- a/modoboa/extensions/admin/static/admin/js/admin.js +++ b/modoboa/extensions/admin/static/admin/js/admin.js @@ -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", diff --git a/modoboa/extensions/admin/templates/admin/editdomainform.html b/modoboa/extensions/admin/templates/admin/editdomainform.html index 0850f4f45..09d19065f 100644 --- a/modoboa/extensions/admin/templates/admin/editdomainform.html +++ b/modoboa/extensions/admin/templates/admin/editdomainform.html @@ -14,7 +14,7 @@ {% for da in domadmins %} {{ da.username }} {{ da.fullname }} - {% if perms.admin.change_domain%}{% domadmin_actions da.id domain.id %}{% endif %} + {% if perms.admin.change_domain %}{% domadmin_actions da.id domain.id %}{% endif %} {% endfor %} {% else %}
{% trans "No domain administrator defined" %}
{% endif %} diff --git a/modoboa/extensions/admin/templates/admin/identities.html b/modoboa/extensions/admin/templates/admin/identities.html index 202142ee3..8655ce8ef 100644 --- a/modoboa/extensions/admin/templates/admin/identities.html +++ b/modoboa/extensions/admin/templates/admin/identities.html @@ -4,20 +4,10 @@ {{ block.super }} {% extra_static_content "identities" user %} diff --git a/modoboa/extensions/admin/templates/admin/mailform.html b/modoboa/extensions/admin/templates/admin/mailform.html index 2d0d48c28..d00bf3359 100644 --- a/modoboa/extensions/admin/templates/admin/mailform.html +++ b/modoboa/extensions/admin/templates/admin/mailform.html @@ -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" %} diff --git a/modoboa/extensions/admin/templatetags/admin_tags.py b/modoboa/extensions/admin/templatetags/admin_tags.py index 826dc99dc..2be237969 100644 --- a/modoboa/extensions/admin/templatetags/admin_tags.py +++ b/modoboa/extensions/admin/templatetags/admin_tags.py @@ -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") }] diff --git a/modoboa/extensions/postfix_relay_domains/static/postfix_relay_domains/js/relay_domains.js b/modoboa/extensions/postfix_relay_domains/static/postfix_relay_domains/js/relay_domains.js index e2e91c5a5..3b6a36386 100644 --- a/modoboa/extensions/postfix_relay_domains/static/postfix_relay_domains/js/relay_domains.js +++ b/modoboa/extensions/postfix_relay_domains/static/postfix_relay_domains/js/relay_domains.js @@ -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", diff --git a/modoboa/extensions/radicale/forms.py b/modoboa/extensions/radicale/forms.py index 04578be0d..6188306c5 100644 --- a/modoboa/extensions/radicale/forms.py +++ b/modoboa/extensions/radicale/forms.py @@ -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( diff --git a/modoboa/extensions/radicale/static/radicale/js/jquery.dynamicrule.js b/modoboa/extensions/radicale/static/radicale/js/jquery.dynamicrule.js index af7eb82df..cf94de18c 100644 --- a/modoboa/extensions/radicale/static/radicale/js/jquery.dynamicrule.js +++ b/modoboa/extensions/radicale/static/radicale/js/jquery.dynamicrule.js @@ -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(); diff --git a/modoboa/extensions/radicale/static/radicale/js/radicale.js b/modoboa/extensions/radicale/static/radicale/js/radicale.js index 49952cc94..9a6552ba7 100644 --- a/modoboa/extensions/radicale/static/radicale/js/radicale.js +++ b/modoboa/extensions/radicale/static/radicale/js/radicale.js @@ -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(); diff --git a/modoboa/extensions/radicale/templates/radicale/accessrule.html b/modoboa/extensions/radicale/templates/radicale/accessrule.html index 895618f65..5ac132826 100644 --- a/modoboa/extensions/radicale/templates/radicale/accessrule.html +++ b/modoboa/extensions/radicale/templates/radicale/accessrule.html @@ -1,7 +1,9 @@ {% load i18n %} +{% load form_tags %} +
- {{ username }} + {% render_field username %} - +
diff --git a/modoboa/extensions/radicale/templates/radicale/rightsform.html b/modoboa/extensions/radicale/templates/radicale/rightsform.html index 8f9e4e750..e2982f5b8 100644 --- a/modoboa/extensions/radicale/templates/radicale/rightsform.html +++ b/modoboa/extensions/radicale/templates/radicale/rightsform.html @@ -1,12 +1,12 @@ -{% load lib_tags radicale_tags %} +{% load form_tags radicale_tags %} {% if error %}
{{ error }}
{% endif %}
{% csrf_token %} -
- {{ form.username }} +
+ {% render_field form.username %} - +
{% render_rule_fields form %} diff --git a/modoboa/extensions/radicale/templatetags/radicale_tags.py b/modoboa/extensions/radicale/templatetags/radicale_tags.py index 20fc74f7c..f9992e354 100644 --- a/modoboa/extensions/radicale/templatetags/radicale_tags.py +++ b/modoboa/extensions/radicale/templatetags/radicale_tags.py @@ -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 diff --git a/modoboa/lib/templatetags/form_tags.py b/modoboa/lib/templatetags/form_tags.py index 5d4e01a42..a53826d07 100644 --- a/modoboa/lib/templatetags/form_tags.py +++ b/modoboa/lib/templatetags/form_tags.py @@ -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 diff --git a/modoboa/static/js/dynamic_input.js b/modoboa/static/js/dynamic_input.js index 509c29c24..da5a62f9b 100644 --- a/modoboa/static/js/dynamic_input.js +++ b/modoboa/static/js/dynamic_input.js @@ -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 = $(''); + var $rmlink = $( + "".format(input.attr("id")) + + "" + ); - $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 = $(''); + + $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); diff --git a/modoboa/static/js/global.js b/modoboa/static/js/global.js index 599498644..30a49dba9 100644 --- a/modoboa/static/js/global.js +++ b/modoboa/static/js/global.js @@ -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 + * 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' diff --git a/modoboa/templates/common/generic_fields_group.html b/modoboa/templates/common/generic_fields_group.html index 6ab5f3433..ab83b3fb2 100644 --- a/modoboa/templates/common/generic_fields_group.html +++ b/modoboa/templates/common/generic_fields_group.html @@ -1,21 +1,23 @@ {% load i18n %} -
- ? - {% endif %} - -
- {% for field in group %} - {{ field }} - {% if field.errors %} -

- {% for error in field.errors %} - {{ error|safe }} - {% endfor %} -

- {% endif %} - {% endfor %} -
+{% for field in group %} + + +
+ {{ field }} + {% if field.errors %} +

+ {% for error in field.errors %} + {{ error|safe }} + {% endfor %} +

+ {% endif %} +
+{% endfor %}