Skip to content

Instantly share code, notes, and snippets.

@shesek
Created March 1, 2012 21:22
Show Gist options
  • Save shesek/1953329 to your computer and use it in GitHub Desktop.
Save shesek/1953329 to your computer and use it in GitHub Desktop.
Trac multi project UI enhancement
/*
* Just some small UI enhancement for multi-projects. In my setup, I have an `project` custom field and
* "project/" prefixes for the component, version and milestones fields (e.g. "AwesomeProject/1.2" as a,
* version, "SomeProject/User" as a component, etc). This JavaScript hides the irrelevant components/versions/
* milestones on the ticket creation/edit forms according to the selected project, adds some navigation
* to the roadmap page to only show milestones of a specific project and creates links for the project-specific
* roadmap page (using the hash).
*/
(function($){
var project={
init: function(){
// Identify current page
if (window.location.pathname.indexOf('/newticket') !== -1 || window.location.pathname.indexOf('/ticket/') !== -1) {
this.ticket.init();
}
else if (window.location.pathname.indexOf('/roadmap') !== -1) {
this.roadmap.init();
}
},
// # Ticket creation/updating handling
ticket:{
// In order to hide/show <option>s, I'm moving them back and forward between the 'real' select element and an anonymous/hidden. Why can't we just apply display:none to <option>s? :(
init:function(){
// I'm not using [value*=/] as project selector, because it causes all values that contains "/" in them without a valid project before it to be not accesible at all.
var self=this, projectSelector = $('#field-project option').map(function(){return 'option[value^='+this.value+'/]';}).get().join(',');
this.opt=$('#field-version, #field-component, #field-milestone').each(function(){
// For each field, create an anonymous select element and move all project-specific options to it (which also makes them invisible)
var $t=$(this);
$t.data('load_val', $t.val()).data('opt', $(document.createElement('select')).append($t.find(projectSelector).each(function(){$(this).data('parent', $(this).parent());}).attr('isproject',1)));
});
// Bind setProject() to the change event + call setProject() with current val
this.setProject($('#field-project').change(function(){self.setProject(this.value);}).val());
},
setProject: function(name) {
this.opt.each(function(){
// Move all project-specific options from 'real' select to anonymous select, than find options related to current project on the anonymous select and move to 'real' select
var $t=$(this);
$t.data('opt').append($('option[isproject=1]',this)).find('option[value^='+name+'/]').each(function(){$(this).appendTo($(this).data('parent'));});
// $t.append($t.data('opt').append($('option[isproject=1]',this)).find('option[value^='+name+'/]'));
$t.data('load_val') && $t.val($t.data('load_val')).data('load_val',null); // Re-set value that was there during page load and reset load_val
});
}
},
// # Roadmap handling
roadmap: {
init: function() {
var added={};
// Create a <ul> for list of projects
var list = $(document.createElement('ul')).css({listStyleType:'none'}).
// Add a 'Display:' label
append($(document.createElement('li')).text('Display:').css({fontWeight:'bold',display:'inline',marginRight:25})).
// Add an 'All' option
append($(document.createElement('li')).addClass('link').text('All').click(function(){project.roadmap.setProject(null)}))
// Add a clear:both element after the list
//.after($(document.createElement('div')).css({clear:'both'}))
;
// Iterate over all milestones, add a 'project' attribute and add an <li> with all projects to the list
$('li.milestone').each(function(){
var t=$(this),text=t.find('h2 a em').text(), index=text.lastIndexOf('/'); // lastIndexOf is used to support 'subprojects' - foo/bar/baz is considered milestone 'baz' under project 'foo/bar'
if (index !== -1){
var name = text.substr(0, index);
t.attr('project', name);
if (!added[name]) {
added[name]=1;
$(document.createElement('li')).addClass('link').text(name).click(function(){project.roadmap.setProject(name)}).appendTo(list);
}
}
});
list.find('li.link').css({color:'#BB0000',display:'inline',marginRight:15,cursor:'pointer'});
// Add our <ul> after the 'Roadmap' title
$('.roadmap h1').eq(0).after(list);
if (document.location.hash.indexOf('#project=') === 0) {
this.setProject(document.location.hash.substr(9));
}
},
setProject: function(project){
if (project !== null) {
$('li.milestone').hide().filter('[project='+project+']').show();
document.location.hash = '#project=' + project;
}
else {
$('li.milestone').show();
document.location.hash = '';
}
}
}
};
$(function(){project.init();});
})(jQuery);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment