Creating widgets

This article describes how to create your own lemoon widgets for the administrative dashboard. All widgets are defined in an xml file. The xml contains widget preferences and the widget code itself.

 

The bascis

We'll start of with the simplest example of a widget. The widget contains the minimum requreiments for a widget to run and doesn't really do anything. Take a look at the example below. 

<?xml version="1.0" encoding="utf-8" ?>
<Module>
  <ModulePrefs title="My widget" />  
  <Content type="html">
    <![CDATA[ 
      
      <script type="text/javascript">
        
        var MyWidget__MODULE_ID__ = {
          jQuery: $,          
          init: function() {
            var widget = new LemoonWidget.init(__MODULE_ID__);           
          }
        };  
        
        MyWidget__MODULE_ID__.init();
        
        </script>
     ]]>
  </Content>
</Module>

The xml file has a Module root element. Allowed elements to Module are ModulePrefs, UserPref and Content. ModulePrefs contains properties for the widget, UserPref (not in the example above) contains unique settings for the widget and the Content element contains a CData section with the widget code including html and script.

The minimum requirement for a widget to run is the init() function. All widgets should be encapsulated in a var [Widget ID]__MODULE_ID__ to make sure multiple widgets of the same type on the Dashboard doesn't collide regarding functions and variables. To initialize the widget, call the var widget = new LemoonWidget.init(__MODULE_ID__);. If the widget in the example above would be added to the Dashboard, it would only show an empty widget with a header saying "My widget". Se picture below.

 


Image 1. A simple widget doing nothing.

 

Adding content

A widget is nothing without content. The content, or html, should be placed in the CData Section of the Content element. Look att the example below. 

<?xml version="1.0" encoding="utf-8" ?>
<Module>
  <ModulePrefs title="My widget" />
  <Content type="html">
    <![CDATA[ 
      
      <div style="margin:10px">This is some content!</div>
      
      <script type="text/javascript">
        
        var MyWidget__MODULE_ID__ = {
          jQuery: $,          
          init: function() {
            var widget = new LemoonWidget.init(__MODULE_ID__);          
          }
        };  
        
        MyWidget__MODULE_ID__.init();
        
        </script>
     ]]>
  </Content>
</Module>

 

This example outputs a text in the widget. Look at the result in the screenshow below.


Image 2. Some content in the widget.

 

Adding dynamic settings

Sometimes you would like to expose some sort of setting in the widget to the end user. This could be to set the number of feed items to show in a rss feed widget, login credentials in a google analytics widget and so on. Lemoon widgets gives you the possibility to add settings to the widget by using one or more UserPref elements. The UserPref elements should be a child element to the Module root element.

There are different types of UserPref datatype depending on what you would like to store. The available datatypes are:

Data type Description
bool This data type will display a checkbox
string This data type will display a textbox exprecting a string. This is the default data type if nothing is set in a UserPref element
enum This data type will display a drop down listbox.
hidden A hidden string data type that can be used for settings that shouldn't be visible to the user.

If you use the enum data type, you have to add EnumValue elements as child elements to the UserPref element for each list item you need. Look at the example below with all data types available.

 

<?xml version="1.0" encoding="utf-8" ?>
<Module>
  <ModulePrefs title="My widget" />
  <UserPref name="url" display_name="Url" default_value="" />
  <UserPref name="showall" display_name="Show all" default_value="" datatype="bool"/>
  <UserPref name="type" display_name="Select type" default_value="" datatype="enum">
    <EnumValue value="1" display_value="Type 1"></EnumValue>
    <EnumValue value="2" display_value="Type 2"></EnumValue>
    <EnumValue value="3" display_value="Type 3"></EnumValue>
  </UserPref>
  
  <Content type="html">
    <![CDATA[ 
      
      <div style="margin:10px">This is some content!</div>
      
      <script type="text/javascript">
        
        var MyWidget__MODULE_ID__ = {
          jQuery: $,          
          init: function() {
            var widget = new LemoonWidget.init(__MODULE_ID__);          
          }
        };  
        
        MyWidget__MODULE_ID__.init();
        
        </script>
     ]]>
  </Content>
</Module>

 

And this is how it looks like in the widget.





image 3. Custom settings on a widget.

So, how do you get the settings stored in a widget. There are three different functions for this. Which one you should use depends on the data type you would like to get. The functions available are:

Function Description
getString(key) Returns the setting value as a string
getInt(key) Returns the setting value as a int
getBool(key) Returns the setting value as a bool


Look at the example below where the settings stored in the widget is used in the script. 

<?xml version="1.0" encoding="utf-8" ?>
<Module>
  <ModulePrefs title="My widget" />
  <UserPref name="url" display_name="Url" default_value="" />
  <UserPref name="showall" display_name="Show all" default_value="" datatype="bool"/>
  <UserPref name="type" display_name="Select type" default_value="" datatype="enum">
    <EnumValue value="1" display_value="Type 1"></EnumValue>
    <EnumValue value="2" display_value="Type 2"></EnumValue>
    <EnumValue value="3" display_value="Type 3"></EnumValue>
  </UserPref>
  
  <Content type="html">
    <![CDATA[ 
      
      <div style="margin:10px" id="mytext"></div>
      
      <script type="text/javascript">
        
        var MyWidget__MODULE_ID__ = {
          jQuery: $,          
          init: function() {
            var widget = new LemoonWidget.init(__MODULE_ID__);          
            
            if (widget.getBool("showall")) {  
              $("#mytext", widget.handle).text("You entered the value " + widget.getString("url"));
            }
          }
        };  
        
        MyWidget__MODULE_ID__.init();
        
        </script>
     ]]>
  </Content>
</Module>

 

The example above checks the boolean setting showall first and then gets the string setting url and uses this as the output to the div-tag. Notice the $("#mytext", widget.handle). The reason for using widget.handle here is to get the mytext div-tag relative to the current widget. This is to avoid collisions among widgets of the same type on the dashboard. You should always use this type of jQuery selector format to get html elements in your widget.

 

Using Ajax to extend the functionality

Using html and client script only might not be very exiting in the long term. The real power in the widget lies in the possibility to make ajax calls to services and take advantage of server side code. Since the browser security model only accepts calls to the same domain, you either have to create your service and make it accessible within the domain or create proxys to services outside the domain.

Lemoon lets you create MVC controllers that inherit from System.Web.Mvc.Controller class. Create the class in your project or in a other assembly. Please take a look at a simple Controller class below. The class contains one method that only sets the Status to "OK".

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using Model = Mindroute.Lemoon.Model;

namespace Mindroute.MyControllerTest
{
    public class TestController : Controller
    {
        public JsonResult TestMethod()
        {
            return Json(new Model.JsonResponse() { Status = "OK" });
          }
    }
}

 

Below is an example on how to call the method from javascript and ajax using JQuery. 

<?xml version="1.0" encoding="utf-8" ?>
<Module>
  <ModulePrefs title="My widget" />
  <UserPref name="url" display_name="Url" default_value="" />
  <UserPref name="showall" display_name="Show all" default_value="" datatype="bool"/>
  <UserPref name="type" display_name="Select type" default_value="" datatype="enum">
    <EnumValue value="1" display_value="Type 1"></EnumValue>
    <EnumValue value="2" display_value="Type 2"></EnumValue>
    <EnumValue value="3" display_value="Type 3"></EnumValue>
  </UserPref>
  
  <Content type="html">
    <![CDATA[ 
      
      <div style="margin:10px" id="mytext"></div>
      
      <script type="text/javascript">
        
        var MyWidget__MODULE_ID__ = {
          jQuery: $,          
          init: function() {
            var widget = new LemoonWidget.init(__MODULE_ID__);          
            
            widget.startLoading($(".list", widget.handle));
             $.ajax({
                type: "POST",
                contentType: "application/json; charset=utf-8",
                url: widget.baseUrl() + "admin/api/test/testmethod",
                dataType: "json",
                cache: false,
                error: function(XMLHttpRequest, textStatus, errorThrown) {
                  widget.endLoading($(".list", widget.handle));
                  
                  alert("Error!");
                  
                  if (XMLHttpRequest.statusText.length > 0) {                    
                    
                  }
                },
                success: function(data, textStatus) {
                  if (data.Status == "error") {               
                    widget.endLoading($(".list", widget.handle));                   
                    
                    alert("Error!");
                    
                  } else {
                    widget.endLoading($(".list", widget.handle));                   
                    
                    alert(data.Status);
                    
                  }
                }
              });  
          }
        };  
        
        MyWidget__MODULE_ID__.init();
        
        </script>
     ]]>
  </Content>
</Module>

 

Note how the call to the method is made. The url looks like this: 

  url: widget.baseUrl() + "admin/api/test/testmethod"

where test is the controller class name TestController without the Controller suffix and testmethod is the name of the method you would like to call.

If the call to the method is successful, the string OK is return in the Status property.

The startLoading and endLoading functions is used to display the animated progress wheel. Se below.

//.list is the element where to display the animation
widget.startLoading($(".list", widget.handle));

widget.endLoading($(".list", widget.handle));

 

Localize the widget

If you would like to localize the widget you do so by creating a resource xml file with all the phrases to be localized. The resource xml file should have the same name as the widget followed by the prefix _ALL_ALL.xml. To localize for a specific language, replace the first ALL with the language code, for example _sv.ALL.xml. All resource files should be places in the App_Data\widgets\localization\ folder.

The ALL_ALL represents [language]_[country]. The country option is rarely used, but is sometimes set to specify for example "British english" or "USA english" as in en_GB or en_US. When using the ALL prefix, you can specify resources for all english languages, as in en_ALL.

 

Example

Suppose you have a widget called MyWidget.xml located in the App_Data\widgets folder, you should create a resource file with the name MyWidget_ALL_ALL.xml with the default phrases and additional resource files for each of the languages you would like to translate to. If you would like to have a swedish resource file, name the file MyWidget_sv_ALL.xml.

An example of a resource file is shown below. 

<?xml version="1.0" encoding="utf-8" ?>
<messagebundle>
  <msg name="title">My drafts</msg>
  <msg name="records">Items per page</msg>
  <msg name="name">Title</msg>
  <msg name="created">Created</msg>
  <msg name="next">Next</msg>
  <msg name="previous">Previous</msg>
</messagebundle>

 

To have the widget localized, replace you phrases in your widget with the macro __msg_[phrase name]__. In the code example above we have the phrase "My drafts". To use that phrase in you widget you should enter __msg_title__. Look at the example below. 

<?xml version="1.0" encoding="utf-8" ?>
<Module>
  <ModulePrefs title="__msg_title__" />
  <UserPref name="records" display_name="__msg_records__" default_value="10" />
  <Content type="html">
    <![CDATA[ 
      <table class="list">
        <tbody />     
      </table>  
      <div class="paging">
        <a href="javascript:void(0);" class="previous">__msg_previous__</a>, <a href="javascript:void(0);" class="next">__msg_next__</a>
      </div>
      <script type="text/javascript">
        var Recent__MODULE_ID__ = {
          jQuery: $,
          
          init: function() {
            var widget = new LemoonWidget.init(__MODULE_ID__);
            this.listPages(0, widget.getInt("records"));            
             
          },
                    
          listPages: function(pageIndex, pageSize) {
              var widget = new LemoonWidget.init(__MODULE_ID__);
              var module = this;
         
              
              $.ajax({
                type: "GET",
                contentType: "application/json; charset=utf-8",
                url: widget.baseUrl() + "admin/api/content/getdrafts/?query.PageIndex=" + pageIndex + "&query.PageSize=" + pageSize,
                dataType: "json",
                cache: false,
                error: function(XMLHttpRequest, textStatus) {
            //...code is left out               },
                success: function(data, textStatus) {
                  if (typeof(data.Status) != "undefined" && data.Status == "error") {
                    //...code is left out
                  } else {

                     //... code is left out...
                }
              });             
            }
        };  
        Recent__MODULE_ID__.init();
        </script>
     ]]>
  </Content>
</Module>