Skip to content
nairdo edited this page Sep 14, 2013 · 43 revisions

Blocks are instances of BlockTypes that can have configurable properties (aka "Attributes"). Changing the values of those properties will typically change the behavior or default functionality of the Block. For simplicity we'll refer to BlockTypes as "Blocks".

Besides using the REST API, Blocks are the primary mechanism for accessing and changing all the data in Rock. They are the primary building blocks, and by creating and combining Blocks together on one or more pages, you can do just about anything you can imagine.

You already created [an ultra-simple Hello World block][Building-your-first-custom-block], but let's continue with more details on how to really tap into the power of the RockBlock.

Developing Custom Blocks

As mentioned in the Basic Rock Concepts section, Blocks should inherit from Rock.Web.UI.RockBlock.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.UI;
using Rock;
using Rock.Attribute;
using Rock.Web.UI;
using Rock.Web.UI.Controls;

namespace com.mychurch.Blocks
{
    public partial class PotluckDinnerList : RockBlock
    {

RockBlock Class Methods and Properties

Inheriting from RockBlock provides you with access to several methods and properties. You'll use these in your Block to do things like check security (to decide what the person can do and see) and get the configurable bits of your generic block to make super specific decisions.

Properties

  • CurrentPage - The page the block is currently on.
  • CurrentPageReference - URL for the page where the current block is located.
  • CurrentPerson - The currently authenticated (logged in) person.
  • CurrentPersonId - The currently authenticated (logged in) person's ID.
  • CurrentTheme - The relative path to the current theme and layout folder.
  • CurrentUser - The currently authenticated (logged in) user.
  • RootPath - The root URL path to the Rock application.

Methods

  • GetAttributeValue( string name ) - Gets the value for the given attribute name. This is covered in greater detail in the next section called Block Field Attributes.
  • IsUserAuthorized( string action ) - Checks if the CurrentPerson is authorized to perform the requested action name. See Securing Access below for details.
  • NavigateToParentPage() - will redirect the user to the "parent" page of the current block.

Block Field Attributes

The Rock framework gives you an amazingly easy way to have configuration settings for each Block you develop with minimal coding. This powerful feature lets you develop Blocks that are flexible, generic and configurable. We call these Block Attributes.

When a Block class is decorated with one or more Rock.Attribute field attributes, administrators can set values for each instance of the Block. (The framework automatically builds the UI that's needed for setting the values.)

For example, let's say you wanted to let the administrator decide how many minutes to keep something cached. Just add an IntegerField attribute:

    using Rock.Attribute;

    [IntegerField( "Cache Duration", "Number of seconds to cache the content.", false, 0 )]
    public partial class HtmlContent : RockBlock
    {
        // ...

Rock's Framework UI allows admins to configure block attributes

Then to retrieve the value for that attribute, use the GetAttributeValue( attributeName ) method.

    int duration = Convert.ToInt32( GetAttributeValue( "CacheDuration") );

Each field type will generally have a name, description, default value, required boolean, category, and order, but some types will have additional properties you can control. You can provide these as named arguments which may make it a bit easier to read in your code. Here are the parameters that are common to all field attribute types:

  • name - (string) This is the title of the field which is shown to the administrator user. This is different than the programmatic key you use when you call GetAttributeValue( string ) described earlier. If you don't specify a key argument, by convention, the name (stripped of all spaces) is used as the key.
  • description - (string) This is the text that is shown in the help bubble.
  • required - (boolean) Indicates whether or not a value must be provided/configured.
  • defaultValue - (mixed) Holds a default value which is appropriate for the field type.
  • category - (string) Used to group field attribute types into logical sets. This is useful when you have many fields which are related.
  • order - (int) Used to order the field attribute types. The order is used first to determine the ordering for the category groupings and then it's used to order fields within the group. As such, you may wish to adopt the "tens" strategy whereby your first field category group uses the orders 10, 11, 12, etc. and your next field category uses 20, 21, etc.
  • key - (string) As mentioned earlier, the key is used to access the stored values for the attributes via the GetAttributeValue( string ) method.

NOTE: There is a different kind of configurable property, called Global Attributes, which are not block instance specific but instead are used to store configurable, system-wide values that can be shared and used anywhere in Rock. See the Global Attributes section for more information about these settings.

Types of Block Attributes

The following are the types of attributes you can use on your block.

NOTE: This section below is meant for reference. You can quickly scan through it to familiarize yourself with all the different kinds of block attributes and refer back to it later.

AccountField

AccountField creates an FinancialAccount picker which allows only a single item to be selected.

    [AccountField( "Account", "The account that new pledges will be allocated toward", true, "", "", 0, "DefaultAccount" )]

You can get the selected account's guid like this:

   var accountGuid = GetAttributeValue( "DefaultAccount" );

AccountsField

AccountsField is similar to the previous except it allows multiple accounts to be selected and saved. You will access the stored values slightly differently when using this attribute field type.

    [AccountsField( "Accounts", "The accounts that new pledges will be allocated toward", true, "", "", 0, "DefaultAccounts" )]

You can get all the guids (strings) of the selected accounts using the GetAttributeValues() method like this:

   List<string> accountGuids = GetAttributeValues( "DefaultAccount" );

  // or if you prefer a list of Guids use this:
   List<Guid> accountGuids = GetAttributeValues( "DefaultAccounts" ).Select( Guid.Parse ).ToList();

AttributeCategoryField

TODO: This section needs some actual content.

BinaryFileTypeField

TODO: This section needs some actual content.

BooleanField

The BooleanField will render a checkbox.

Example 1 - a simple checkbox.

    [BooleanField( "Show Notification" )]

Example 2 - This will render a checkbox, which defaults to true (checked), within a grouping category called "Applies To" and with a helpful description.

    [BooleanField( "Global Tags", "Edit global tags (vs. personal tags)?", true, "Applies To" )]

To get the value of the boolean use the GetAttributeValue( string key ) passing in the attribute's "key". Unless you specifically defined a key, it will be the name of your attribute with the spaces removed.

   // using extension method to get the boolean value
   bool useGlobalTags = GetAttributeValue( "Global Tags" ).FromTrueFalse();

   // using standard ToBoolean conversion
   bool showNotifications = Convert.ToBoolean( GetAttributeValue( "ShowNotification" ) );

NOTE: We've created some useful Extensions Methods, like the FromTrueFalse() shown above.

CampusField

TODO: This section needs some actual content.

CampusesField

Renders a set of checkboxes of each campus allowing for multiple selection.

Example 1 - This will render the campus checkboxes with the required value set to false allowing the administrator to select/check none of them if desired.

    [CampusesField( "Campuses", "Display Ads for selected campus" )]

CategoryField

TODO: This section needs some actual content.

ComponentField

TODO: This section needs some actual content.

ComponentsField

TODO: This section needs some actual content.

CustomCheckboxListField

This field type lets you define a custom list of values from which the administrator can pick. It will render the list as a group of checkboxes allowing one or more items to be selected.

The format of the listSource (values) field is a comma delimited set of [value]s or [value]:[text] pairs or a SQL Select statement that returns result set with a Value and Text column. The [text] part will be shown to the user while the value will be used internally and stored/saved. In the defaultValue parameter you can default select one or more items by using a comma delimited set of [value]s as shown below.

Example 1 - an Audience to Target option will be shown with two checkboxes, "Primary" and a "Secondary", and by default they will both be selected/checked.

   [CustomCheckboxListField( "Audience to Target", "The audience to display marketing items for.",
       "1:Primary,2:Secondary", false, "1,2" )]

In order to make things clearer to you and other developers, you can also use named parameters as shown here with the required and defaultValue parameters:

   [CustomCheckboxListField( "Audience to Target", "The audience to display marketing items for.",
       "1:Primary,2:Secondary", required: false, defaultValue:"1,2" )]

CustomDropdownListField

Similar to the previous field, this field type lets you define a custom list of values from which the administrator can pick. It will render the list as a dropdown list allowing an item to be selected.

The format of the listSource (values) field is a comma delimited set of [value]s or [value]:[text] pairs or a SQL Select statement that returns result set with a Value and Text column. The [text] part will be shown to the user while the value will be used internally and stored/saved. In the defaultValue parameter you can default select and item by providing the value as shown below.

    [CustomDropdownListField( "Start Time", "the time at which the notice will be processed",
        "1:6 AM,2:7 AM,3:8 AM", false, "2" )]

Example 2 - Using a SQL select statement for the listSource.

    [CustomDropdownListField( "Primary Role", "the main role for this request",
        listSource:"SELECT [Id] as 'Value', [Name] as 'Text' FROM [GroupRole] ORDER BY [SortOrder]")]

CustomRadioListField

Similar to the Custom Checkbox and Dropdown attribute fields, this field type lets you define a custom list of values from which the administrator can select one. It will render the list as a radio button group allowing a single item to be selected.

The format of the listSource (values) field is a comma delimited set of [value]s or [value]:[text] pairs or a SQL Select statement that returns result set with a Value and Text column. The [text] part will be shown to the user while the value will be used internally and stored/saved. In the defaultValue parameter you can default select and item by providing the value as shown below.

Example 1 - A static list of radio buttons for Yes and No that defaults to No and is required.

    [CustomRadioListField( "Radio Options", "a list of options", "1:Yes,2:No", required: true,
        defaultValue: "2" )]

Example 2 - Using a SQL select statement for the listSource to get a list of Rock Campuses.

    [CustomRadioListField( "Choose Campus", "a list of campuses",
        listSource: "SELECT [Id] as 'Value', [Name] as 'Text' FROM [Campus] ORDER BY [Name]" )]

DateField

This will create a calendar date picker for quickly selecting a particular date.

    [DateField( "Start Date", "The beginning date for the range.", key: "DefaultStartDate" )]

    // Get the admin configured value
    var start = GetAttributeValue( "DefaultStartDate" );

DateRangeField

TODO: This section needs some actual content.

DecimalField

TODO: This section needs some actual content.

DecimalRangeField

TODO: This section needs some actual content.

DefinedValueField

This field will build a drop down selector listing all the values for the given DefinedType. Whether your in need of a well known value or a custom value from a custom DefinedType you added into the system, this attribute field simplifies getting & setting the administrator configured value.

    [DefinedValueField( Rock.SystemGuid.DefinedType.MARKETING_CAMPAIGN_AUDIENCE_TYPE, "Audience",
        "The audience you wish to target with ads." )]

DetailPage

Use this Block attribute when you have a grid that needs to link to a page that handles the details for the grid's item as seen in the Grid -> Item Details UX pattern. It currently renders as a dropdown list selector of pages.

Example:

    [DetailPage]

EmailTemplateField

TODO: This section needs some actual content.

EntityTypeField

This will render a drop down list of all known Rock Entities. It's useful if your block has some general purpose functionality which can be applied to specific entities.

    [EntityType( "Selected Type", "Select the entity type you wish to bind to the tree view." )]

Field

TODO: This section needs some actual content.

GroupField

Renders a flat list of all groups in the form of a dropdown list.

    [GroupField( "Group", "Select the root group to show in this block." )]

GroupRoleField

TODO: This section needs some actual content.

GroupTypeField

TODO: This section needs some actual content.

GroupTypesField

Renders all known group types in the form of a set of checkboxes.

    [GroupTypesField( "Group Types", "Select group types to show in this block." )]

IntegerField

An integer field renders as a textbox that accepts only whole number (integer) values as input.

Example 1 - An empty field that accepts a whole number but does not require a setting.

    [IntegerField( "Max Items", "Maximum number of items to display; otherwise shows all." )]

Example 2 - Defaults to the value of 0 and requires a number to be set.

    [IntegerField( "Cache Duration", "Number of seconds to cache the content.",
        required: true, defaultValue: 60)]

IntegerRangeField

TODO: This section needs some actual content.

LinkedPage

Similar to the DetailPage described above, this attribute renders dropdown list of pages. It lets the administrator wire up pages to your block that may be unknown to you (the developer) and is handy when your block has a relationship to something but does not handle the functionality for that thing.

Note: You can only use one LinkedPage attribute per block.

    [LinkedPage( "Calendar Page", "The page that holds the calendar for viewing upcoming events." )]

LocationField

TODO: This section needs some actual content.

MemoField

TODO: This section needs some actual content.

TextField

Renders a simple textbox useful for collecting a miscellaneous value from the administrator.

Example 1 - a textbox with no default and requiring no value.

    [TextField( "License Key", "The Service Objects License Key" )]

Example 1 - a textbox with a default and also requires a value.

    [TextField( "XSLT File", "The path to the XSLT File ", required: true,
        defaultValue: "~/Assets/XSLT/PageList.xslt" )]

WorkflowTypeField

Renders field for selecting a single defined workflow.

     [WorkflowTypeField("Workflow", "An optional workflow to activate for any new file uploaded")]

Your Own Custom Field Attributes

Note: Creating custom field attributes is considered an advanced topic.

By extending Rock.Attribute.FieldAttribute, custom field types can be created (that exist in other assemblies) and utilized in block properties. See the Custom Field Attributes and FieldTypes for the details.

Saving Attribute Values

Although it's a pretty rare need, sometimes you may be in a situation where your block is capturing some data and needs to store it in the block attribute value. In these cases, after setting all your values using the SetAttributeValue( string key, string value ) method, you will need to call the SaveAttributeValues( int? personId ) as seen in this example:

    SetAttributeValue( "XsltFilePath", tbXslt.Text );
    SetAttributeValue( "PersonReport", cbPersonReport.Checked.ToString());
    SetAttributeValue( "ShowColumns", ddlHideShow.SelectedValue );
    SaveAttributeValues( CurrentPersonId );

Other UI Components To Make Your Life Easier

Rock has tons of other UI components you can use in your blocks. Whereas the Block Attributes are primarily used by the administrators, these other UI components are intended to be used by the regular users of your blocks. Be sure to read the UI Toolkit section for all the details.

User Preferences

The Rock framework has a mechanism for storing and retrieving preferences (settings) for the current user. For example, let's say your block has options for sorting or filtering, and you'd like to "remember" how the user sets them each time they use the block. This is an ideal case for using this mechanism. See the user's preferences document for more details.

Fetching Values from "QueryString"

We're only guessing, but your custom block is probably going to need some data from the QueryString. Use the PageParameter( string ) method when fetching values from the QueryString or route. This method hides the complexity of having to find the value in either the QueryString or the URL route.

            if ( !Page.IsPostBack )
            {
                string itemId = PageParameter( "categoryId" );
                if ( !string.IsNullOrWhiteSpace( itemId ) )
                {
                    ShowDetail( "categoryId", int.Parse( itemId ) );
                }
                else
                {
                    pnlDetails.Visible = false;
                }
            }

Securing Access

Securing functionality access within your block is easy to do. To test whether the current user (if there is one) is allowed to perform the requested action just use the IsUserAuthorized (string action) method where action is one of "View", "Edit", or "Administrate" as seen here:

    if ( ! IsUserAuthorized( "View" ) )
    {
        message = "You are not allowed to see this content.";
        ...

    if ( IsUserAuthorized( "Edit" ) || IsUserAuthorized( "Administrate" ) )
    {
        rGrid.Actions.IsAddEnabled = true;
        ...

If you need to define additional custom action names to control your custom functionality, you can simply decorate your block with [AdditionalActions()] like this:

    [AdditionalActions( new string[] { "Approve" } )]

NOTE: You will need to include using Rock.Security; in your block. Once you do this, you can then use the IsUserAuthorized(string action) method to verify user authorization.

Standard Security Action Meanings

  • "View" - grants the ability to view the item's public properties
  • "Edit" - includes view access and the ability to change the item's name and other properties
  • "Administrate" - means the block's security and block's settings can be changed.

Validation

When validating a user's input, you'll need to provide some feedback to let them know when they've entered something incorrectly. Use a ValidationSummary control at the top of an edit panel with the Bootstrap standard class:

    <asp:ValidationSummary ID="ValidationSummary1" runat="server" CssClass="alert alert-error" />

It will look something like this when the user runs into trouble with their input:

a standard validation summary error

The standard data field controls (DataTextBox, DataDropDownList, etc.) in your UI Toolkit will also render appropriate error conditions with the proper Bootstrap validation states as seen here:

a standard error validation state

If using a custom or regular input control, be sure to follow the Bootstrap documentation on Form control Validation states.

Implementing IDetailBlock

If your custom block is a "Detail" block then you should also implement the IDetailBlock interface.

    public partial class PledgeDetail : RockBlock, IDetailBlock
    {

As seen in the above code example, we're encouraging the use of a ShowDetail( string parameter, int value ) method when appropriate to to handle the displaying of the detail of your block's 'item'. This method is required if your block implements the IDetailBlock interface. See the List Detail Pattern in the UI Guidelines section for more details about this pattern.

Relative Paths

Both the BlockType and the Page entities have a public CurrentTheme property that can be used in either a block or template file to get the resolved path to the current theme folder. Here's an example of how to use this property:

Markup:

 <img src="<%= CurrentTheme %>/Images/avatar.gif">

Code Behind:

 myImg.ImageUrl = CurrentTheme + "/Images/avatar.gif";

If trying to reference a resource that is not in the theme folder, you can use the ResolveUrl() method of the System.Web.UI.Control object. For example:

 <link type="text/css" rel="stylesheet" href='<%# ResolveUrl("~/CSS/reset-core.css") %>' />

Adding References to the HTML Document Head

When a block needs to add a reference into the page Head for another asset (JavaScript, CSS, etc.) it should use one of these methods (AddCSSLink, AddScriptLink, or AddHtmlLink) from the RockPage class. The path should be relative to the layout template.

    protected override void OnInit( EventArgs e )
    {
        base.OnInit( e );

        RockPage.AddCSSLink( this.Page, "~/css/bootstrap-switch.css" );
        RockPage.AddScriptLink( this.Page, "~/scripts/jquery.switch.js" );

Example AddHtmlLink Usage:

 System.Web.UI.HtmlControls.HtmlLink rssLink = new System.Web.UI.HtmlControls.HtmlLink();
 rssLink.Attributes.Add( "type", "application/rss+xml");
 rssLink.Attributes.Add( "rel", "alternate" );
 rssLink.Attributes.Add( "href", blog.PublicFeedAddress );
 rssLink.Attributes.Add( "title", "RSS" );
 CurrentPage.AddHtmlLink( this.Page, rssLink );

Sharing Objects Between Blocks

Blocks can communicate with each other through the sharing of objects. The base Block class has a CurrentPage object that is a reference to the current Cms Page object. This object has two methods for saving and retrieving shared objects specific to current page request. Within your block, you can call:

   CurrentPage.SaveSharedItem( string key, object item )`
   CurrentPage.GetSharedItem( string key )

Example Usage:

TODO: The following example refers to an old class name Rock.Models.Cms.Blog - need to update

 // try loading the blog object from the page cache
 Rock.Models.Cms.Blog blog = CurrentPage.GetSharedItem( "blog" ) as Rock.Models.Cms.Blog;
 
 if ( blog == null )
 {
     blog = blogService.GetBlog( blogId );
     CurrentPage.SaveSharedItem( "blog", blog );
 }

It's worth noting that the order in which loaded blocks modify these shared objects cannot be guaranteed without further preparation and coordination.

Caution When Saving Then Attempting to View Data

When you save a new entity using the service layer, be aware that Entity Framework will not automatically hydrate any related entities unless you use a new service. For example, a PrayerRequest has a relationship to a Category entity, and when we save a new PrayerRequest after setting its CategoryId property as shown below, the Category property is not automatically populated/hydrated -- even if you try to Get it using the same service after saving it:

    prayerRequest.CategoryId = 9;
    prayerRequestService.Save( prayerRequest, CurrentPersonId );
    prayerRequest = prayerRequestService.Get( prayerRequest.Id ); // Warning!
    var category = prayerRequest.Category; // THIS IS NULL

Instead, you need to use a new service object as shown here:

    // prayerRequest = prayerRequestService.Get( prayerRequest.Id );
    prayerRequest = new PrayerRequestService().Get( prayerRequest.Id ); // Good.
    var category = prayerRequest.Category; // Now it's there.

Liquid Markup and Text/Report Templating

Rock includes DotLiquid which makes it easy to merge field values into string templates. That means you can store a template-string and Rock (using DotLiquid) can replace things like {{ person.FullName }}) with actual values of your object (such as "John Smith").

To tap into this feature, Rock includes an string extension method called ResolveMergeFields() so any string can easily be merged with a collection of objects.

Let's look at a simple example where we have the body text of an email and a person that we're sending it to. Here a mergeObjects dictionary is passed into ResolveMergeFields and the tokens are replaced with the person's given name.

Example 1

    string emailBody = "{{person.GivenName}}, pretend this msgBody was stored somewhere.";
    Person person = new PersonService().Get( 1 );

    var mergeObjects = new Dictionary<string, object>();

    // Add a person to the mergeObjects dictionary
    mergeOjects.Add( "person", person );

    // output will be "Admin, pretend this msgBody was stored somewhere."
    var output = emailBody.ResolveMergeFields( mergeObjects );

You can read more about DotLiquid Templates on the web, but simply put, there are two types of Liquid markup called "Output" and "Tags". Output is surrounded by {{ two curly brackets }} and Tags are surrounded by {% a curly bracket and a percent %}. The example above shows only Output markup. Tags are where you can put code logic to control aspects of the template. Using this you can do some pretty complex things -- including looping.

Here is an example string template from the check-in system that will be merged to become a list of location names.

Example 2

    <ul>
        {% for location in groupType.Locations -%}
        <li>{{ location.Name }}</li>
        {% endfor -%}
    </ul>

See http://wiki.shopify.com/UsingLiquid and http://dotliquidmarkup.org/ for additional reference information.

Filesystem Location

The standard location for all custom blocks is in the Plugins folder. Create your own folder under the Plugins folder to hold any custom blocks created by your team:

+---Plugins
    \---FakeCompany.com
        \---TimeClock
            \---Blocks
                    ExampleTimeBlock.ascx
                    ExampleTimeBlock.ascx.cs

Performance Considerations

Page_Init vs. OnInit

There's not really any big difference besides preference. Overriding the base method (OnInit) may be slightly faster than invoking an event delegate (Page_Init), and it also doesn't require using the AutoEventWireup feature, but essentially it comes down to preference. My preference is to override the event. (I.e. use OnInit or OnLoad instead of Page_Init or Page_Load). This article discusses this in detail.

OnInit vs. OnLoad

There's a significant difference between putting code into the OnInit (Page_Init) method compared to the OnLoad (Page_Load) method, specifically in how it affects ViewState. Any change you make to a control in the Init portion of the page life cycle does not need to be added to ViewState, however, if changed in the Load portion it does. Consider a dropdown box of all the states. If you load the dropdown in the OnLoad method, all of the 50 items of the dropdown box will be added to the ViewState collection, but if you load it in the OnInit method, they will not. For performance sake, we want to keep ViewState as small as possible. So whenever possible set the properties of controls in the OnInit method. Please read this article on Understanding ViewState.

Caching

To cache methods (AddCacheItem(), GetCacheItem(), FlushCacheItem()) can be used to cache custom data across requests. By default the item's cache key will be unique to the block but if caching several items in your block you can specify your own custom keys for each item.

    // Store into cache
    int cacheDuration = 0;
    if ( Int32.TryParse( GetAttributeValue( "CacheDuration" ), out cacheDuration ) && cacheDuration > 0 )
    {
        AddCacheItem( entityValue, html, cacheDuration );
    ...

    // Elsewhere, pull from cache
    string entityValue = EntityValue();
    string cachedContent = GetCacheItem( entityValue ) as string;


    // When finished, flush the cache
    FlushCacheItem( entityValue );

How to Add Blocks To Pages?

Blocks are added to a page by adding them a zone on a page or by adding them to a zone in a layout. Adding a block to a zone in a layout will cause all pages which use that layout to automatically include that block instance.

TODO: This should be moved into an Administrative section/guide.

Clone this wiki locally