Custom Content Parts

You can create part types which use your custom JavaScript and HTML. Parts based on these types are called content parts.

Content parts are one of two types of custom parts in Blackbaud Internet Solutions. To create custom content parts, you need access to Custom parts in Blackbaud Internet Solutions, the ability to create part types, parts, and to add parts to a page. Knowledge of HTML and JavaScript is required.

Parts have a display component which appears to Blackbaud Internet Solutions users who view a part on a page. Parts also have an editor component which is accessible to administrators. The editor component is used to configure a part. For information about Parts, see the guides for Blackbaud Internet Solutions: Blackbaud Internet Solutions User Guides at a Glance.

When you create a custom content part type, you can define the behavior for the display and the editor using HTML and JavaScript. You also specify a JavaScript to run when the part is loaded onto a page. What the JavaScript function does is up to you. For example, you can use the Initialize JavaScript function to initialize forms on either the display or the editor, display messages, and execute other functions. You can include other JavaScript libraries or make use of the existing reference to the jQuery library on the Blackbaud Internet Solutions page.

To create a content part type, from Custom parts, you click New content part

The New content part page appears.

Using the text from any of the examples below, enter the appropriate information into the "Initialize JavaScript function," "Editor," and "Web page display" text fields. Click the save button. The procedure for creating an instance of your Custom Content Part is now handled the same as creating any other type of part. Your Custom Content Part shows up as an available part type.

Barebones Example

Let's start with the most basic example of a custom content part. In this example we highlight two important steps:

  1. Implement the BLACKBAUD.api.customPartEditor.onSave method
  2. In the editor view, save settings to the BLACKBAUD.api.customPartEditor.settings object
  3. In the display view, read settings passed to our initialize function
initBarebonesExample
<p>
  <label for="greeting">Greeting:</label>
  <input type="text" id="greeting" />
</p>
      
<script>
!function($) {
  
  $(function() {
    
    // Read our previously saved greeting (if it exists)
    if (typeof BLACKBAUD.api.customPartEditor.settings.greeting != '') {
      $('#greeting').val(BLACKBAUD.api.customPartEditor.settings.greeting);
    }
    
    // We must implement the onSave method
    // Returning true or false controls whether the save is successful
    BLACKBAUD.api.customPartEditor.onSave = function() {
      
      // Save our greeting
      BLACKBAUD.api.customPartEditor.settings = {
        greeting: $('#greeting').val()
      };
      
      // Required to allow the settings to save and the part to close
      return true;
    };
    
  });
  
}(jQuery);     
</script>
<p>Hello, <span class="greeting"></span></p>
<script>
function initBarebonesExample(instance) {
  $('#' + instance.elementId + ' .greeting').text(instance.settings.greeting);
}
</script>

In addition to saving settings and building a custom inferface, the Custom Content Part also the following mechanism for interacting with the link picker:

  • BLACKBAUD.api.customPartEditor.links.canLaunchLinkPicker
  • BLACKBAUD.api.customPartEditor.links.launchLinkPicker

Image Picker

Very similar to our link picker example, the Custom Content Part also exposes mechanisms to launch the image picker via the following:

  • BLACKBAUD.api.customPartEditor.canLaunchImagePicker
  • BLACKBAUD.api.customPartEditor.launchImagePicker()

In this example, we've also introduced some more robust styling an error handling, in an effort to provide you with a more real-world example. As you can see, the Custom Content Part allows you to develop very polished and custom administrative user experiences.

imageHighlighterRender
<script>
!function($) {
  
  function setupSettings() {
    if (typeof BLACKBAUD.api.customPartEditor.settings.text == 'undefined' || 
        typeof BLACKBAUD.api.customPartEditor.settings.image == 'undefined') {
      BLACKBAUD.api.customPartEditor.settings = {
        text: '',
        image: {
          url: ''
        }
      }
    }
  }
  
  function bindImagePicker() {
    if (!BLACKBAUD.api.customPartEditor.images.canLaunchImagePicker) {
      $('.error-permission').show();
    } else {
      $('a.ImagePicker').click(function(e) {
        e.preventDefault();
        BLACKBAUD.api.customPartEditor.images.launchImagePicker({
          callback: renderImage
        });
      });
    }
  }
  
  function bindSave() {
    BLACKBAUD.api.customPartEditor.onSave = function() {
      var success = true;
      
      $('.error-validation').hide();
      $('[data-validate]').each(function() {
        var element = $(this);
        if (element.val() == '') {
          $(element.data('validate')).show();
          success = false;
        }
      });
      
      // Save text
      BLACKBAUD.api.customPartEditor.settings.text = $('#field-highlighter-text').val();
            
      return success;
    }    
  }
    
  function renderText() {
    $('#field-highlighter-text').val(BLACKBAUD.api.customPartEditor.settings.text); 
  }
  
  function renderImage(o) {
    if (typeof o.name != 'undefined' && o.url != 'undefined') {
      $('.ImagePicker .SearchFieldText').text(o.name);
      $('.ImagePickerRender').html('<img alt="" src="' + o.url + '" />');
      $('#field-highlighter-image-name').val(o.name);
      $('#field-highlighter-image-url').val(o.url);
    }
    BLACKBAUD.api.customPartEditor.settings.image = o;
  }
  
  $(function() {
    setupSettings();
    bindImagePicker();
    bindSave();
    renderText();
    renderImage(BLACKBAUD.api.customPartEditor.settings.image);
  });
  
}(jQuery);
</script>

<style>
.error { display: none; color: #FF0000; font-weight: bold; }
</style>

<div>
  <ul>
    <li class="error error-permission">You do not have permission to launch the image picker.</li>
    <li class="error error-validation error-text">Text is required.</li>
    <li class="error error-validation error-image">Image is required.</li>
  </ul>
</div>

<div class="TabContainer">
  <div class="FieldSetRow">
    <div class="FieldSetColumn FieldSetColumnFirst">
      <div class="Field">
        <div class="FieldHeading">
          <label for="field-highlighter-text" class="FieldLabel">Text:</label>
          <span class="FieldRequired">*</span>
          <span class="clear"></span>
        </div>
        <div class="FieldContentText">
          <input type="text" id="field-highlighter-text" class="FieldSelect FieldInput" data-validate=".error-text" />
          <input type="hidden" id="field-highlighter-image-name" data-validate=".error-image" />
          <input type="hidden" id="field-highlighter-image-url" data-validate=".error-image" />
        </div>
      </div>
      <div class="Field">
        <div class="FieldHeading">
          <label for="field-highlighter-image" class="FieldLabel">Image:</label>
          <span class="FieldRequired">*</span>
          <span class="clear"></span>
        </div>
        <div class="FieldContent">
          <div class="SearchFieldContainer">
            <div class="SearchFieldBox">
              <a href="#" class="SearchField ImagePicker">
                <span class="SearchFieldIcon">
                  <img src="/images/pencil_16.gif" alt="" />
                </span>
                <span class="SearchFieldText"></span>
                <span class="SearchFieldButton">
                  <img src="/images/Admin/Buttons/search.gif" alt="" />
                </span>
              </a>
            </div>
          </div>
        </div>
      </div>
      <div class="Field ImagePickerRender"></div>
    </div>
  </div>
</div>
<script>
function imageHighlighterRender(o) {
  var html = [
    '<p class="circle" style="background-image: url(' + o.settings.image.url + ')">',
    '<span class="text">' + o.settings.text + '</span>',
    '</p>'
  ];
  $('#' + o.elementId).html(html.join(''));
}
</script>

Image Folder Picker

Again, the Custom Content Part is also exposing mechanisms to launch the image folder picker.

  • BLACKBAUD.api.customPartEditor.canLaunchImageFolderPicker
  • BLACKBAUD.api.customPartEditor.launchImageFolderPicker

In this example, we're also introducing the use of the Images endpoint of our REST API, but you could also easily use the ImageService. In an effort to make the end result more visually appealing to the end user, we've also chosen to use the OWL Carousel.

imageFolderPickerRender
<p>
  <strong>Selected Folder Name:</strong>
  <span class="selected-folder-name"></span>
</p>
    
<p>
  <strong>Selected Folder GUID:</strong>
  <span class="selected-folder-guid"></span>
</p>
  
<p>
  <button class="btn-select">Select Folder</button>
</p>
      
<script>
!function($) {
  
  $(function() {
    
    // Read our previously saved name (if it exists)
    if (typeof BLACKBAUD.api.customPartEditor.settings.selectedName != '') {
      $('.selected-folder-name').text(BLACKBAUD.api.customPartEditor.settings.selectedName);
    }
    
    // Read our previously saved url (if it exists)
    if (typeof BLACKBAUD.api.customPartEditor.settings.selectedGuid != '') {
      $('.selected-folder-guid').text(BLACKBAUD.api.customPartEditor.settings.selectedGuid);
    }
    
    // We must implement the onSave method
    // Returning true or false controls whether the save is successful
    BLACKBAUD.api.customPartEditor.onSave = function() {
      
      // Save our settings
      BLACKBAUD.api.customPartEditor.settings = {
        selectedName: $('.selected-folder-name').text(),
        selectedGuid: $('.selected-folder-guid').text()
      };
      
      // Required to allow the settings to save and the part to close
      return true;
    };
    
    // Bind to the link launcher button
    $('.btn-select').click(function(e) {
      
      // Stop the button from submitting our form
      e.preventDefault();
              
      // Launch the link picker, passing in our callback
      BLACKBAUD.api.customPartEditor.folders.launchImageFolderPicker({
        callback: function(selectedFolder) {
          $('.selected-folder-name').text(selectedFolder.folderName);
          $('.selected-folder-guid').text(selectedFolder.folderGUID);
        }
      });
        
    })
    
  });
  
}(jQuery);     
</script>
<div class="carousel">Loading...</div>

<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/owl-carousel/1.3.2/owl.carousel.min.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/owl-carousel/1.3.2/owl.theme.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/owl-carousel/1.3.2/owl.carousel.min.js"></script>
    
<script>
function imageFolderPickerRender(instance) {
  
  var placeholder = $('#' + instance.elementId + ' .carousel');
  
  placeholder.owlCarousel({
    singleItem: true,
    jsonPath: BLACKBAUD.api.pageInformation.rootPath + '/WebApi/Images?FolderGUID=' + instance.settings.selectedGuid,
    jsonSuccess: function(images) {
      var content = '';
      for (var i = 0, j = images.length; i < j; i++) {
        content += '<img src="' + images[i].Url + '" alt="' + images[i].Caption + '" />';
      }
      placeholder.html(content);    
    }
  }); 
}
</script>