Permalink
Browse files

Added a new plugin: ConditionalFieldsPlugin.

  • Loading branch information...
1 parent 5c60e01 commit 7e9c6b93b5c47b99981d895ac82f2699a4b53e01 @daltonmatos committed Nov 26, 2009
@@ -0,0 +1,5 @@
+*~
+*.swp
+ConditionalFields.egg-info/*
+build/*
+dist/*
@@ -0,0 +1,5 @@
+
+# 0.1
+ * Basic functionality
+ * Can hide both the field (from the HTML form) and the field from the ticket header
+ * Can choose which of the visible fields are required
@@ -0,0 +1,6 @@
+To install just run:
+
+$ python setup.py bdist_egg
+
+and then copy *.egg from the dist folder to the trac-env/plugins folder.
+Restart Trac.
@@ -0,0 +1,7 @@
+
+
+# 0.2
+ * Add the option to make the description field *always* required (add validation)
+
+# 0.3
+ * Add an admin panel to configure the plugin
@@ -0,0 +1,2 @@
+*~
+*.swp
@@ -0,0 +1,68 @@
+
+from trac.core import *
+from trac.config import Option, ListOption
+from trac.ticket import TicketSystem
+from trac.web.api import ITemplateStreamFilter, IRequestFilter
+from trac.web.chrome import add_script, ITemplateProvider
+from genshi.filters.transform import Transformer, StreamBuffer
+from genshi.builder import tag
+from genshi.template import MarkupTemplate
+
+class ConditionalFields(Component):
+ implements(ITemplateStreamFilter, IRequestFilter, ITemplateProvider)
+
+ base_url = 'cf'
+ config_section = 'conditional-fields'
+
+ default_fields = ['priority', 'milestone', 'component', 'version',\
+ 'keywords', 'cc'];
+
+ # ITemplateStreamFilter
+ def filter_stream(self, req, method, filename, stream, data):
+ logger = self.env.log
+ if filename == 'ticket.html':
+ ticket_sys = TicketSystem(self.env)
+ custom_fields = [field.get('name') for field in ticket_sys.get_ticket_fields()\
+ if field.get('custom') is True]
+
+ all_fields = self.default_fields + custom_fields
+
+ # Populate de all_fields array so the javascript code can
+ # hide/show them.
+ script = "<script>\n"
+ for field_name in all_fields:
+ script += ("all_fields.push('%s');\n" % field_name)
+
+
+ i = 1
+ while len(self.config.getlist(self.config_section, ('ticket_type_%d' % i))) > 0 :
+ ticket_type = self.config.getlist(self.config_section, ('ticket_type_%d' % i))
+ visible_fields = self.config.getlist(self.config_section, ('visible_fields_%d' % i))
+ required_fields = self.config.getlist(self.config_section, ('required_fields_%d' % i))
+
+ if len(visible_fields) > 0 :
+ script += ("types['%s'] = ['%s'];\n" % ("".join(ticket_type), "','".join(visible_fields)))
+ script += ("types_required['%s'] = ['%s'];\n" % ("".join(ticket_type), "','".join(required_fields)))
+ i += 1
+
+ script += "</script>\n"
+ stream |= Transformer("//div[@id='footer']").after(MarkupTemplate(script).generate())
+
+ return stream
+
+ #IRequestFilter
+ def pre_process_request(self, req, handler):
+ return handler
+
+ def post_process_request(self, req, template, data, content_type):
+ if req.path_info.startswith("/newticket") or req.path_info.startswith("/ticket"):
+ add_script(req, ('%s/conditional-fields.js' % self.base_url))
+ return template, data, content_type
+
+ #ITemplateProvider
+ def get_templates_dirs(self):
+ return []
+
+ def get_htdocs_dirs(self):
+ from pkg_resources import resource_filename
+ return [(self.base_url, resource_filename(__name__, 'htdocs'))]
@@ -0,0 +1,6 @@
+
+
+.cf-required-field{
+ color: #b00;
+ font-weight: bold;
+}
@@ -0,0 +1,251 @@
+
+
+/*
+ * All basic trac tricket fields
+ * The custom field names will be added by the
+ * ITemplateStreamFilter
+ */
+all_fields = [];
+
+
+/*
+ * This hash stores the fields that will be visible for
+ * each ticket type.
+ *
+ * The "key" is the ticket type name, eg. "task", "defect" or
+ * any other type added by the user.
+ *
+ * The "value" will be an array of String. Each elemnt of this
+ * array represents tha name of the field. If you refer to any
+ * custom field tou should use de "name" and *not* the "Label"
+ * of the field.
+ *
+ * This hash will be populated by the ITemplateStreamFilter
+ *
+ */
+types = {};
+
+
+/*
+ *
+ * This hash stores the fileds that are required for each ticket type.
+ *
+ */
+types_required = {}
+
+
+
+var css_required_class_name = 'cf-required-field';
+
+
+function change_event_handler(){
+ campos = types[this.value];
+
+ if (campos){
+ hide_fields(all_fields);
+ show_fields(campos);
+ }else{
+ hide_fields(all_fields);
+ show_fields(all_fields);
+ }
+
+ required = types_required[this.value];
+ if (required){
+ $(required).each(function(idx, field){
+ add_css_class(css_required_class_name, field);
+ });
+ }
+
+}
+
+
+/*
+ * Hide *one* field
+ *
+ */
+function hide_field(value){
+
+ $(get_field(value)).each(function(idx, value){
+ value.hide();
+ });
+
+ $(get_header_field(value)).each(function (idx, value){
+ value.hide();
+ });
+
+ $(all_fields).each(function(idx, field){
+ remove_css_class(css_required_class_name, field);
+ });
+
+}
+
+/*
+ * Hide a list of fields
+ *
+ */
+function hide_fields(field_name_array){
+ if (!field_name_array)
+ return;
+
+ $(field_name_array).each(function (idx, field){
+ hide_field(field);
+ });
+}
+
+
+/*
+ * Show *one* field
+ *
+ */
+function show_field(field_name){
+ $(get_field(field_name)).each(function(idx, value){
+ value.show();
+ });
+
+ $(get_header_field(field_name)).each(function (idx, value){
+ value.show();
+ });
+
+}
+
+
+/*
+ * Show a list of fields.
+ *
+ */
+function show_fields(field_name_array){
+ if (!field_name_array)
+ return;
+
+ $(field_name_array).each(function(idx, field){
+ show_field(field);
+ });
+}
+
+
+/*
+ * Return all the HTML Objetcs that refers to the chosen field
+ *
+ * These are, for exmaple:
+ * * The HTML form field
+ * * The HTML elemente <label for=""> of this field
+ *
+ * The tickets page is like this: The tickets fields (including custom fields)
+ * are displayed in a two-column table. Where every TD element has a TH elemento
+ * with a <label for> inside.
+ *
+ * <tr>
+ * <th>
+ * <label for="field-cc">Cc:</label>
+ * </th>
+ * <td>
+ * <input type .... id="field-cc"/>
+ * </td>
+ * </tr>
+ *
+ * So we need to hide two html elements: TH and TD for each field.
+ *
+ */
+function get_field(field_name){
+ var html_objects = [];
+ input_tag = $("#field-" + field_name);
+
+ if (!input_tag.found()){
+ return [];
+ }
+
+ tag_td = $(input_tag.parents("td").get(0));
+
+ html_objects.push(tag_td);
+
+ tag_td_class = tag_td.attr('class');
+
+ tag_th = $(tag_td.siblings("th"));
+
+ /* We get the right TH by comparing the class attribute
+ * Id the chosen fiels has class="col1" we can't hide de TH
+ * with class="col2". If we do so we will hide *another* field's label
+ */
+ tag_th.each(function(idx, value){
+ if ($(value).attr('class') == tag_td_class){
+ html_objects.push($(value));
+ }
+ });
+ return html_objects;
+
+}
+
+/*
+ * Return the HTML objects of this field that are part of
+ * the ticket header
+ *
+ */
+function get_header_field(field_name){
+ var html_objects = [];
+
+ th_tag = $("#h_" + field_name);
+ td_tag = $("td[headers='h_" + field_name + "']");
+
+ html_objects.push(th_tag);
+ html_objects.push(td_tag);
+
+ return $(html_objects);
+}
+
+
+/*
+ *
+ * Remove a CSS class from a HTML Object
+ *
+ */
+function remove_css_class(css_class, field_id){
+ $("label[for='field-" + field_id + "']").removeClass(css_class);
+}
+
+
+/*
+ *
+ * Add a CSS class to a HTML Object
+ */
+
+function add_css_class(css_class, field_id){
+ xpath = "label[@for='field-" + field_id + "']";
+ label = $(xpath);
+ label.addClass(css_class);
+}
+
+
+/*
+ * Sets the change envent of the
+ * ticket type drop down.
+ *
+ */
+$(document).ready(function(){
+ /*
+ * summary, type, and description are always highlithed
+ */
+ $(['summary', 'type', 'description']).each(function(idx, field){
+ add_css_class(css_required_class_name, field);
+ });
+
+
+ $("#field-type").change(change_event_handler);
+
+ /* Call the change event, so the fields of the type already selected
+ * can be hidden */
+ $("#field-type").change();
+});
+
+
+/*
+ *
+ * Helper method so we can know if an element
+ * was found inside the HTML code.
+ *
+ */
+$.prototype.found = function (){
+ return (this.length > 0);
+};
+
+
+
Oops, something went wrong.

0 comments on commit 7e9c6b9

Please sign in to comment.