Monday, 21 July 2014

k2 Custom SmartControl: Client Side

This is a continuation of the previous three posts:

To set up a k2 Custom Control Visual Studio project click here.
To set up the xml definition click here.
To set up the server side class click here.

if you have these three complete continue on.

Picking up where we left off we have to make one last modification, recall that we added a reference to our javascript file in the AssemblyInfromation.cs class, but we still have to add our JavaScript to the rendered page in the Constructor of our Custom Control. so lets do that:

public MySmartControl()
    : base()
{
    //Define the javascript key and path
    string jsKey = "MyCustomControls_MySmartControl_MySmartControl_Script";
    string jsUrl = "MyCustomControls.MySmartControl.MySmartControl_Script.js";

    // add the script to the designer environment
    this.DesignCodePaths.Add(jsKey, jsUrl);

    // add the script to the runtime environment
    this.CodePaths.Add(jsKey, jsUrl);
}

now we can focus on JavaScript, so lets add functions to get our control, set and get both the value and properties. First we'll start with an empty self calling function that builds our javascript object

// self calling function, will fire itself when rendered
(function ($, undefined) {

    //check if MyCustomControls object exists, if not create it
    if (typeof MyCustomControls === "undefined" || MyCustomControls === null)
        MyCustomControls = {};

        //add MySmartControl object to MyCustomControls object
        MyCustomControls.MySmartControl = {
        
        //TODO add interaction logic between control and xml definition 
      
        };
}(jQuery));


with this ready now we have to implement all the endpoints we defined in our XML Definition, namely

  • get/setValue
  • get/setProperty
  • events

// self calling function, will fire itself when rendered
(function ($, undefined) {

    //check if MyCustomControls object exists, if not create it
    if (typeof MyCustomControls === "undefined" || MyCustomControls === null)
        MyCustomControls = {};

        //add MySmartControl object to MyCustomControls object
        MyCustomControls.MySmartControl = {

            // Custom function to retrieve html rendering of control, it is marked
            // with an underscore to emphesize that it is a private function that is
            // not declared in the xml definition
            _getControl: function (objInfo)
            {
                return document.getElementById(objInfo.CurrentControlId);
            },

            // getValue function defined in xml Definition
            getValue: function (objInfo) {
                var control = MyCustomControls.MySmartControl._getControl(objInfo);
                return control.innerHTML;
            },

            // setValue method defined in xml Definition
            setValue: function (objInfo) {
                var control = MyCustomControls.MySmartControl._getControl(objInfo);
                control.innerHTML = objInfo.Value;
            },

            // getProperty method defined in xml Definition, retrieves property
            // that is saved as attribute on the root container of the control
            getProperty: function (objInfo) {
                var control = MyCustomControls.MySmartControl._getControl(objInfo);
         
                if (control.hasAttribute(objInfo.property))
                    return control.getAttribute(objInfo.property);
                return control.getAttribute("data-{0}".format(objInfo.property));
            },

            // setProperty method defined in xml Definition, sets an attribute on
            // the root element of the control to the property value
            setProperty: function (objInfo) {
                var control = MyCustomControls.MySmartControl._getControl(objInfo);

                if (control.hasAttribute(objInfo.property))
                    return control.setAttribute(objInfo.property, objInfo.Value);
                control.setAttribute("data-{0}".format(objInfo.property), objInfo.Value);
            },

            // excute is defined in the xml Definition, it is responsible for handling
            // all incomeing method calls that are defined in the xml definition and called
            // from the k2 rules.
            execute: function (objInfo) {
                var parameters = objInfo.methodParameters;
                var method = objInfo.methodName;

                switch (method) {
                    case "ShowAlert":
                        alert(parameters.message);
                        return;
                    case "Add":
                        return (parameters.double ? 2 : 1) *
                               (parseInt(parameters.addendone) + parseInt(parameters.addendtwo));
                }
            }
        };

}(jQuery));

now with that complete we have to add the logic to pass on the events from the control to k2

$().ready(function () {
    $(document).delegate('.MyCustomControls-MySmartControl', 'click.Control', function (e)
    {
        console.log(this.id); //Notice that this.id = CurrentControlId

        // raiseEvent is an internal k2 fucntion in the events.js file that will pass the
        // event to the k2 rules and fire the event on the appropriate instance of the control
        raiseEvent(this.id, 'Control', 'OnClick');
    });

});

To recap: Think of your basic control as a three file beast:

  • Xml Definition: used to define all the properties, methods, events and supporting functions your control will have. The properties are mapped to the server side .cs file for serialization, but the methods, events and supporting functions are defined in the client side .js file.
  • Server side: used to de/serialize all the properties defined in your control, also to render the control initially on the page in the RenderControl method.
  • Client side: the meat of the control, this is where the interaction between the user and k2 environment occurs, events let users execute k2 rules and methods let k2 rules execute functions defined in the client side of your control.



Tuesday, 15 July 2014

k2 Custom SmartControl: XML Definition

This is an extension of the previous post, so if you haven't set up a Visual Studio k2 CustomControl project go back and do so here.

Picking up where we left off, add a folder to your project and call it MySmartControl, as the name insinuates this will be where all your smart control assets will live. to get started, lets add the following to our custom control
  • an icon that will only be visible in design time, use the one above
  • an xml definition which describes our control and how it will interact in the k2 designer
  • a .cs file that is the server representation 
  • a .js file that is the client representation 
Now remember that everything other then your .cs file has to have it's build action set to Embedded Resource, you accomplish this by right clicking on the asset and hitting properties or the F4 key.

with that complete your solution explorer should look something like


Now lets get started with the xml definition file.
Think of this as the description of the endpoints that the k2 designer will use to interact with your smart control through the forms and rules. there really is no logic here, just definitions of things like:
  • Properties that can be set at design time
  • A value that your control can have
  • Events that your control can react to through the rules
  • Methods that can be fired from the rules
  • Styling
along with these interaction definitions there are also passive definitions, such as what will the controls category be, display name, system name, the full name etc.

So to make a very simple xml definition lets take a look at the following.

<?xml version="1.0" encoding="utf-8" ?>

<ControlType>
  <!--the control type, options are Display, Input, Listing and Action.
      If you want to be able to set and get a value on your control it
      can’t be of type Display.-->
  <Category>Input</Category>

  <!--The grouping category where the control will be listed inside
      the Designer-->
  <Group>Custom</Group>

  <!--the Name that will show up in the Designer-->
  <DisplayName>My Smart Control</DisplayName>
 
  <!--This is the short name for the control type that is platform independent.
      It is the name of the control type stored in Form and View definitions. -->
  <Name>MySmartControl</Name>

  <!--The full name of the control used to load and instantiate the control.
      Format: {TypeFullName},{AssemblyName}-->
  <FullName>MyCustomControls.MySmartControl, MyCustomControls</FullName>

  <!--***********************************************************************-->
 
  <!--JavaScript Functions responsible for
      getting and setting the value of our Control-->
  <GetValueMethod>MyCustomControls.MySmartControl.getValue</GetValueMethod>
  <SetValueMethod>MyCustomControls.MySmartControl.setValue</SetValueMethod>

  <!--The typd of data that your control's value can be set to
      The Following are options:
      AudoGuid    AutoNumber    DateTime  Decimal   File  Guid  Text     
      Hyperlink   MultiValue    Image     YesNo     Xml   Memo  Number -->
  <DataTypes>
    <DataType>Text</DataType>
    <DataType>Memo</DataType>
  </DataTypes>

  <!--***********************************************************************-->

  <!--Events lists all the events that the control exposes. These events are listed in the Rules editor.
      Event handlers for non-standard JQuery events must be implemented in the control's .js file -->
  <Events>
    <Event>OnClick</Event>
    <Event>OnChange</Event>
  </Events>

  <!--DefaultEventName is the default event that the Rules composer will select when the designer creates
      a rule for the control-->
  <DefaultEventName>OnClick</DefaultEventName>

  <!--***********************************************************************-->
 
  <!--JavaScript Functions responsible for
      getting and setting properties of our Control-->
  <SetPropertyMethod>MyCustomControls.MySmartControl.setProperty</SetPropertyMethod>
  <GetPropertyMethod>MyCustomControls.MySmartControl.getProperty</GetPropertyMethod>

  <!--A Collection of properties that can be set at Design time-->
  <Properties>
    <!--Control Name is a property on the k2 Base Control,
        Thus it doesn't neeed a backing Property explicitly defined in
        our .cs file-->
    <Prop ID="ControlName" mappable="false" refreshdisplay="true"
          ValidationPattern="\S"  ValidationMessage="InvalidName"         
          friendlyname="Name" type="string"
          category="Detail" inputlength="255" />
   
    <!--ControlProperty01 is our own custom property so it does have
          to be explicitly defined in our .cs file-->
    <Prop ID ="ControlProperty01" mappable="true" refreshdisplay="true" ReadOnly="false"
          friendlyname="Control Property01" type="string"  category="Detail"
          inputlength="255">
      <Value>prop01</Value>
    </Prop>
   
  </Properties>

  <!--Prop Attributes-->
 
  <!--ID: the Name of the property that has to be mapped to a
          corrisponding server side Property-->
 
  <!--mappable: a boolean attribute that specifies if the property can be set
                or retrieved from within the form rules-->
 
  <!--refreshDispaly: if set to true will reload the control, using the
                      Server Side RenderControl(HtmlTextWriter) Method-->
 
  <!--freindlyName: the display name that is shown in the k2 designer-->
 
  <!--type: the type that the property can be, options are string, int, -->
 
  <!--ReadOnly: if the value can be set through the properties pane in the
                k2 designer or not-->

  <!--category: the grouping in the properties pane of the k2 designer-->
 
  <!--ValidationPattern: a regex that validates property input-->
 
  <!--ValidationMessage: a magic string for a message that will pop up
                         should the validation pattern fail-->

  <!--***********************************************************************-->

  <!--Javascript function responsible for handling all defined methods-->
  <ExecuteMethod>MyCustomControls.MySmartControl.execute</ExecuteMethod>
 
  <!--Methods are mappings from the k2 rules to methods set up in
      the client side javascript file, this allows you execute javascript
      code from the k2 rules-->
  <Methods>
   
    <Method ResultType="None">
      <!--Name: is the internal name used by k2, it's also the name that the
          client side .js file will use to switch on-->
      <Name>ShowAlert</Name>
     
      <!--DisplayName is the nice name that will show up in the control
          settings when the method is being set up in the k2 rules-->
      <DisplayName>Show Alert</DisplayName>

      <Description>
        This method takes in a string and displays it in a standared JavaScript alert
      </Description>

      <!--Parameters is a collection of input parameters that can be passed into a method-->
      <Parameters>
        <!--The Datatype will be displayed to the user in the k2 form, but will be passed to
            the .js file as a string, so you'll have to parse numbers booleans seem ok-->
        <Parameter DataType="Text">
          <!--Name is the internal identifier of the parameter being passed in, it is a
          best practice to always use lower case names, this is because no matter what that's
          how they can be used in the javascript-->
          <Name>message</Name>
          <DisplayName>Message</DisplayName>
          <Description>The message to be displayed</Description>
          <IsRequired>true</IsRequired>
        </Parameter>
      </Parameters>
    </Method>

    <!--by setting the result type to something other then "None" we can do an output mapping
        int the k2 form rules-->
    <Method ResultType="Number">
      <Name>Add</Name>
      <DisplayName>Add</DisplayName>
      <Description>Adds two numbers</Description>
      <Parameters>
        <Parameter DataType="Number">
          <Name>addendone</Name>
          <DisplayName>Addend One</DisplayName>
          <Description>The first number to be added</Description>
          <IsRequired>true</IsRequired>
        </Parameter>
        <Parameter DataType="Number">
          <Name>addendtwo</Name>
          <DisplayName>Addend Two</DisplayName>
          <Description>The second number to be added</Description>
          <IsRequired>true</IsRequired>
        </Parameter>
        <Parameter DataType="Boolean">
          <Name>double</Name>
          <DisplayName>Double Sum</DisplayName>
          <Description>Multiply the sum by two</Description>
          <!--keep in mind that if it's not specified the value doesn't 
              default to false, but is undefined-->
          <IsRequired>false</IsRequired>
        </Parameter>
      </Parameters>
    </Method>
  </Methods>

</ControlType>

The XML Definition file, defines the endpoints that are used to communicate between the K2 environment and the actual control. These endpoints are exposed in the K2 designer as properties, events and methods:

  • Properties: are fields that store values, they are set during design time and used to set things control name, is enabled or visible or any number of custom properties that we implement. To get an idea of properties that are available inspect the K2 BaseControl class. If a property is not available on the base control it can defined in our custom control server class.
  • Events: are interactions that the control can initiate within the rules, basically a way for the control to fire logic defined in the K2 Form, for example on clicking the control display a K2 modal window.
  • Methods: are a way for the K2 Form rules to execute logic that is contained within the custom control.
  • Interaction JS Methods: these are the getters and setters defined in your Client Side .js file for interaction with the control value as well as the control properties, we defined them get/setValueMethod and get/setPropertyMethod xml elements.

Now it's important to differentiate between the server (.cs ) and client (.js) parts of our control. the server is what renders the control initially, whereas the client part is responsible for the on form interactions. this means that things like properties have to be maintained in both instances.

Next let's look at the Server side part of our control.