Overview
The FogBugz case and list pages are composed of a single-page app. When you go to a case or filter/search URL on your FogBugz site, your browser is sent a mostly-blank HTML document and a JavaScript package. If you want the JavaScript code to execute when the case content is actually on the screen, you need to hook into the correct navigation events and look for certain parameters. This article provides a case page customization template based on a custom JavaScript and CSS code.
Prerequisites
Read first this article: Enabling, Configuring, and Creating New Customizations for FogBugz
Please also note the disclaimer mentioned in the article.
Solution
You may use the following template as a jump-start in customizing your case page:
name: Case Page Things
description: Do things on the case page in ocelot and oldbugz
author: You
version: 1.0.0.0
js:
$(function(){
var isOcelot = function() {
return (typeof fb.config != 'undefined');
};
// if this isn't Ocelot, and we're not on the oldbugz case page, don't run
if (!isOcelot() && !$('#bugviewContainer').length) {
return;
}
// this needs to be idempotent. in ocelot, we don't know on a full page
// load if it was run on the /nav/end event so we run it again after subscribing
var runThisWhenCasePageModeChanges = function(sCommand, fIsOcelot) {
// the bug JS object is goBug in the old UI and fb.cases.current.bug in Ocelot
var bug;
// tags differ in the two UIs: bug.tags in Ocelot and goBug.ListTagsAsArray() in the old UI
var rgTags;
// when using url parameters to populate case form fields on page-load, Ocelot and the
// old UI differ in one or more. In this instance, the tag list is "tags" in Ocelot
// and "sTags" in the old UI
if (fIsOcelot) {
bug = fb.cases.current.bug;
rgTags = bug.tags;
}
else {
bug = goBug;
rgTags = goBug.ListTagsAsArray();
}
if (sCommand == 'load' || sCommand == 'view') {
// do something on case view
}
else {
// do something on case edit in one of the various modes: edit,
// assign, resolve, close, reactivate, reopen, open, email, reply,
// forward, and new
}
};
// ------ set up to call your code when the case view changes -------------------
// Customizations are run only on full page-load. In ocelot, this only
// happens when you go directly to a URL or when you refresh the whole page.
// Therefore, we just want to subscribe to the navigation event that fires whenever
// the view is changed, as well as subscribe to any other events we care about
// and run our code one initial time.
if (isOcelot()) {
// /nav/end fires at the end of every single-page-app navigation, e.g. when
// the list page is done displaying or when the case page is done changing from
// view to edit mode. the event param contains some useful info e.g.
// event.route is something like '/cases/234/case-title-here' and event.url
// has the entire url of the page
// on the case page, fb.cases.current.sAction is the mode of the page just like
// in the old UI: view, edit, assign, resolve, close, reactivate, reopen,
// open, email, reply, forward and new
fb.pubsub.subscribe({
'/nav/end': function(event) {
//console.log("navigated to url " + event.url);
//console.log("route: " + event.route);
// if it's the case page, add the onkeyup for the title field and
// run an initial check of the fields
if (typeof fb.cases.current.sAction != 'undefined') {
runThisWhenCasePageModeChanges(fb.cases.current.sAction, true);
}
else {
//console.log('ocelot page but not the case page');
}
}
});
// depending on timing, if you go directly to a case page, the pubsub might not
// finish before /nav/end is called the first time, so run the function once
// after a delay. remember your code should be idempotent
setTimeout(function() { runThisWhenCasePageModeChanges(fb.cases.current.sAction, true) }, 100);
}
else {
// the old UI case page is a full page load in many situations, but does use ajax
// transitions for certain things, like clicking edit while viewing a case or
// canceling an edit in progress.
// this runs on full page load and determines which action is occurring based on
// what case-page html elements are present
if ($('#bugviewContainerEdit textarea').length > 0) {
if ($('div.ixBug a').length > 0) {
if ($('#sTo').length > 0) {
// warning: not localized:
if ($('#bugviewContainerEdit div.bugevent div.summary span.action:contains("Replied by")').length > 0) {
runThisWhenCasePageModeChanges('reply', false);
}
// warning: not localized:
else if ($('#bugviewContainerEdit div.bugevent div.summary span.action:contains("Forwarded by")').length > 0) {
runThisWhenCasePageModeChanges('forward', false);
}
else {
runThisWhenCasePageModeChanges('email', false);
}
}
// warning: not localized:
else if ($('#bugviewContainerEdit div.bugevent div.summary span.action:contains("Assigned by")').length > 0) {
runThisWhenCasePageModeChanges('assign', false);
}
// warning: not localized:
else if ($('#bugviewContainerEdit div.bugevent div.summary span.action:contains("Closed by")').length > 0) {
runThisWhenCasePageModeChanges('close', false);
}
// warning: not localized:
else if ($('#bugviewContainerEdit div.bugevent div.summary span.action:contains("Reactivated by")').length > 0) {
runThisWhenCasePageModeChanges('reactivate');
}
else if ($('#Button_Resolve').length > 0) {
runThisWhenCasePageModeChanges('resolve', false);
}
else {
runThisWhenCasePageModeChanges('edit', false);
}
// add to this: direct link could be to edit or reply or...
}
else if ($('#bulkBugItems').length > 0) {
// bulk action
runThisWhenCasePageModeChanges('edit', false);
// add to this: direct link could be to edit or reply or...
}
else {
runThisWhenCasePageModeChanges('new', false);
}
}
else {
// this is the case view page on a full page-load. The sCommand value
// on an ajax load is 'view' If you do not need to distinguish
// the two, change the string 'load' here to 'view'.
// Note that in Ocelot, the sAction is always 'view' and never 'load'
runThisWhenCasePageModeChanges('load', false);
}
// full page loads are handled above. To handle the ajax
// transitions, hook into the BugViewChange event:
$(window).on('BugViewChange', function(e, data) {
runThisWhenCasePageModeChanges(data.sCommand, false);
});
}
});
css:
/ * your css here */