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:
- Implement the
BLACKBAUD.api.customPartEditor.onSave
method - In the editor view, save settings to the
BLACKBAUD.api.customPartEditor.settings
object - 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>
Link Picker
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
initLinkPickerExample
<p>
<strong>Selected Link Name:</strong>
<span class="selected-link-name"></span>
</p>
<p>
<strong>Selected Link URL:</strong>
<span class="selected-link-url"></span>
</p>
<p>
<button class="btn-select">Select Link</button>
</p>
<script>
!function($) {
$(function() {
// Read our previously saved name (if it exists)
if (typeof BLACKBAUD.api.customPartEditor.settings.selectedName != '') {
$('.selected-link-name').text(BLACKBAUD.api.customPartEditor.settings.selectedName);
}
// Read our previously saved url (if it exists)
if (typeof BLACKBAUD.api.customPartEditor.settings.selectedUrl != '') {
$('.selected-link-url').text(BLACKBAUD.api.customPartEditor.settings.selectedUrl);
}
// 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-link-name').text(),
selectedUrl: $('.selected-link-url').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();
// Verify the user has permission to launch the link picker
if (!BLACKBAUD.api.customPartEditor.links.canLaunchLinkPicker) {
alert('You do not have permission to launch the link picker.');
} else {
// Launch the link picker, passing in our callback
BLACKBAUD.api.customPartEditor.links.launchLinkPicker({
callback: function(selectedLink) {
$('.selected-link-name').text(selectedLink.name);
$('.selected-link-url').text(selectedLink.url);
}
});
}
})
});
}(jQuery);
</script>
<p>
See my link: <span class="selected-link"></span>
</p>
<script>
function initLinkPickerExample(instance) {
var link = '<a href="' + instance.settings.selectedUrl + '">' + instance.settings.selectedName + '</a>';
$('#' + instance.elementId + ' .selected-link').append(link);
}
</script>
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>