This post is a work in progress

The Manuscript user interface, including global JavaScript objects and DOM elements, can and does change without warning. If you have trouble with Manuscript or your customization after applying a customization or a new release, please confirm that your customization is working as expected.

The customizations feature is currently in Beta. Contact us if you would like to have customizations enabled on your Manuscript account. If you used the Bug Monkey plugin in the past, your customizations will need to be updated. This article will become your guide to migrating your scripts and for making new changes to the case and list pages.

Configuring customizations

The customizations feature in Manuscript allows you to add client-side JavaScript and CSS to the application. Use code and stylesheets to tweak the UI to your liking and support your own specific workflow and needs. This feature was inspired by the “GreaseMonkey” browser extension and operates in a similar fashion. The customizations feature is currently in Beta. Contact us if you would like to have customizations enabled on your Manuscript account.

To use customizations, go to the avatar menu in the bottom-left of Manuscript and choose Customizations. The main view shows a list of all customizations currently active.

  • Click on the title of a script to edit it.
  • Click On or Off in the Enabled column to enable or disable it for your user.
  • Click Edit Rules… to set whether the customization is available for others to enable, if it defaults to on or off for them, and if it’s required.
  • Click New Customization… to create a new script. Give it a name and description, and fill in the JavaScript and CSS sections. Make sure you save it, and enable it for your user. You can also enable scripts that others have shared in your Manuscript instance under the Your Available Customizations list.

Site admins are offered a Site Configuration button on that page to allow them to set which users or groups are allowed to create customizations.

Writing Customizations

Structure

Each customization has a header where you set the name, description, author and version:

name:        A Quick Thing
description: This script does X, Y and Z
author:      Ada Lovelace
version:     1.1.0.0

The name and description must be changed from the default Customization and Doesn’t do anything which Manuscript fills in for you. The heading and later sections must be properly formatted in order to save the customization.

After the header, there is a section for javascript and one for css. Within them, make sure you use valid syntax for each type. For example, the js section allows comments using // this formatting, but make sure to wrap your css comments in /* the correct characters */

js:

  // re-name the "Wiki" drop-down to "Wikis"
  $(function() {
    var isOcelot = function() {
      return (typeof fb.config != 'undefined');
    };
    var changeIt = function() {
      $('li.wiki.dropdown a.section-link').text("Wikis");
    }
    if (isOcelot()) {
      fb.pubsub.subscribe({
        '/nav/end': function(event) {
          changeIt();
        }
      });
    }
    else {
      changeIt();
    }
  });

css:

  li.wiki.dropdown a.section-link {
    font-weight: bold !important;
  }

Details

Using the example above, some details and guidelines for customizations:

Stylesheets

The CSS you enter is pulled into Manuscript on page load in both UIs (see below). If it is shorter than 1024 characters, it is added inline on the page after Manuscript’s main CSS. If it is larger, it is pulled into your browser using an @import rule.

Javascript Code

1. Wrap your code in $(function() { ... }); so that it runs once the page is ready

2. There are two types of pages in Manuscript: “Ocelot” and “old UI”. Over the last few years, we have transitioned the core portions of the app from our old UI to a new single-page-app we call Ocelot. The old UI pages generally are old-fashioned full-pageloads. Ocelot pages load a mostly blank framework when you first hit enter in the URL, but every transition after that is done AJAXily. (note, if you go from Ocelot to an old page and back, that “back” will be a full pageload)

  • The case list page, case page (view and edit), the iteration planner, kanban board, user options page, and activity feed / notification center are Ocelot pages. Generally, any page with /f/ in it is an Ocelot page
  • The wiki, discussion groups, all configuration pages (other than user options), evidence-based scheduling and anything else not listed above are the old UI

The jQuery we’re running in this example is $('li.wiki.dropdown a.section-link').text("Wikis"). If we just put that in our script by itself, old UI pages would run it after the whole page was in the user’s browser and it would work fine. On Ocelot pages like the list (search and filter results) and case page, nothing would happen. The code would run in the user’s browser when all they had was our header and footer. We need to run it after all of the single-page-app magic is done. To do that, use pubsub to subscribe to the '/nav/end' event. That event fires at the end of every page in Ocelot.

The simplest way to detect if the code is running in Ocelot is to look for fb.config, as shown in the sample code above.

3. DRY (don’t repeat yourself). In our example, we are working with the main header. Since it is shown on pages in both UIs, we use a function to avoid having to write the same code in two places. As of this writing, the bulk edit page can also be a reason to write DRY code. If you want to make changes to the case edit page, the main one is in Manuscript, but bulk edit is still running in the old UI. It will be migrated soon, but for now you will need to handle both situations. (see the script archive for some example scripts which do this)

Templates and Sample Code

Before you start hacking on Manuscript to implement a client-side change, you might want to look at our list of useful and popular customizations to see if someone has already addressed your need. Even if you don’t find exactly what you are looking for, something there may serve as a good starting point.

Case Page

You can also use this template to create a new customization which runs on the case page. It has all the structure you need to detect which version of the page you are on and what case editing or viewing mode is active. If you need to work with the drop-down fields (e.g. Project and Area), see the next section, “Drop-Down Menus”.

Drop-Down Menus

Drop-down menus in the old UI use the Manuscript “droplist” library. On the page, there is a <select> tag with <option> elements like a normal HTML menu, but it is hidden and the library uses them to manage a custom element. If you want to change the value of one, for example on the edit page for a project, ignore the <input type=text> tag and work with the <select>:

// for the primary contact field on the edit project page, the id is 'ixPersonOwner'. change the selection:
$('#ixPersonOwner').selectedIndex = -1;
$('#ixPersonOwner').find('option[value=3]').attr('selected','selected');
// tell Manuscript to re-generate the text input based on the modified <select>
// (make sure to use the actual element, not the jQuery array
DropListControl.refresh($('#ixPersonOwner')[0]);

On the case page in Manuscript, there is a droplist library. Like the old library, there is a text-type <input> tag. Unlike the old library, there is no <select> element and the value is stored in a hidden input. The element to work with is the <div> which has its ID set to the internal name of the case field you want, e.g. the project id, ‘ixProject’. When you select the field with jQuery, the droplist object for use in your code is accessible by calling .droplist() on that jQuery object. Here is the whole progression:

var theField = $('#ixProject');
// get the actual droplist object:
var theDropList = theField.droplist();
// get the currently selected value:
var selectedIxValue = theDropList.val();
// if you want to apply styles and classes to the field, you don't need to use
// jQuery to go find it. it's right on the field:
var theInputTag = theField.input;
// we have the 'ix' value of the field above. Use the droplist library to get
// the display name for it:
var selectedText = theDropList.getChoiceFromValue(selectedIxValue).text;
// enable or disable the drop-down:
theDropList.enable(); theDropList.disable();
// change the value:
// if you know the 'ix' value you want change to, set it with .val(ix):
// var ixProject = 15;
theDropList.val(ixProject);
// the available choices for the droplist are in config.choices:
console.table(theDropList.config.choices);
// loop the choices, e.g. if you want to find the ix for a given text
for (i=0; i<theDropList.config.choices.length; i++) {
  var choice = theDropList.config.choices[i];
  console.log("ix: " + choice.value + ", text: '" + foo.text + "'");
}

In the old library, you could hook into changes on a droplist via the onchange event:

$('#ixPersonOwner').change(function() { console.log("primary contact drop-down changed"); });

Like most of the new Ocelot UI, the droplist library uses events fired on the <body>:

function handleChange(event) {
  var theField = event.target;
  var theDropList = theField.droplist();
  console.log('droplist with id ' + theField.id + ' changed to ix ' + theDropList.val() +
              ' / text "' + theDropList.getChoiceFromValue(theDropList.val()).text + '"');
}
$('body').on("droplistUserInput", function(event) {
  handleChange(event);
});

Manuscript Entities

Manuscript has many entities which you are familiar with from working with cases: Bugs (cases), Projects, Areas, Categories, etc. Internally, these all have a set of properties which follow naming conventions. For example, each entity has a numeric ID, ‘ix’ followed by the entity name, and a display name which is called ‘s’ followed by the entity name: Project has ixProject as its ID and sProject as its name; Category has ixCategory and sCategory. Cases opened via email in your Manuscript account probably have their category set as ixCategory = 3 and sCategory = "Inquiry". Information about all of the entities is available in javascriptland in the fb.db object. Here is a sample function to demonstrate working with the lists. Here we look up an ixProject from an sProject:

function getIxProjectFromSProject (sProject) {
  var ixProject = -1;
  for (i=0; i<fb.db.Project.length; i++) {
    var project = fb.db.Project[i];
    if (project.sProject == sProject) {
      ixProject = project.ixProject;
      break;
    }
  }
  return ixProject;
}

Here is a sample script which sets the category of the case being edited / created to “Inquiry” if the project is “Inbox”. You can modify it using code like getIxProjectFromSProject above to look up the ix’s by name if you wish. Remember to use the basic pubsub method in “Structure” above so that your $('body').on code is run after the case page is fully rendered. You might want to use the larger template script in “Case Page” above.

$(function() {
  var ixProjectInbox = 2;
  var ixCategoryInquiry = 3;
  var checkForProjectInbox = function(event) {
    if (event.target.id == "ixProject") {
      var dropList = event.droplist;
      if (dropList.val() == ixProjectInbox) {
        var categoryDroplist = $('#ixCategory').droplist();
  categoryDroplist.val(ixCategoryInquiry);
      }
    }
  }

  fb.pubsub.subscribe({
    '/nav/end': function(event) {
      $('body').on("droplistUserInput", function(event) { checkForProjectInbox(event); });
    }
  });
});

Migrating from Bug Monkey

Before the Ocelot UI, FogBugz included an optional plugin called Bug Monkey which worked the same way customizations do now. When you switch to Manuscript, your FogBugz customizations come along, but they may need changes; you will need to update your customizations to look for the correct DOM elements and use new JS events and APIs.

A migration guide is coming soon, so watch this space. For now, you can find some explanation of the changes and how some basic things work in the rest of this article. If you only need to run code on the case page, take a look at our template. If you want to run code on the case list page, you can just pull out the “pubsub” portion of that template to run your code on ‘nav/end’. You can also wait for a complete guide here, or drop us a line for personalized help migrating your scripts.

If you just moved over to Manuscript from the old UI and your critical customizations aren’t running as expected, you may want to email us. We can help answer any questions on the changes you need to make, or put you in touch with our Professional Services team to handle the transition for you.

Caveats

The DOM, JavaScript events (such as '/nav/end') and JavaScript objects and APIs (like fb.config and fb.api) are not public, stable APIs. Expect them to change without notice.