Create your own (basic) DataEditor action

Smartsite 7.8 - ...

When you need to create an action with a simple form and render results based on this input, the easiest way to accomplish this is to create a custom class which inherits from the abstract class Smartsite.Manager.Actions.DataEditor.

Notice however, that this is a different implementation than creating a (custom) DataEditor action. In that case, the action you've created will (first) open a library containing the records for a specific table. After selecting a record and clicking the Edit button, then a new tab is opened which contains a DataEditor like implementation.

DataEditor

When directly inheriting from the abstract class Smartsite.Manager.Actions.DataEditor, you're creating an action which does use some of the same building blocks as a (custom) DataEditor action. You (also) need to load/create a DataEditor xml and you need to load/create an Instance document.

As example, the C# source code of a simplified version of the Google Pagespeed action is shown below.

C# CopyCode image Copy Code
using Smartsite.Base;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using Smartsite.XForms; 
 
namespace Smartsite.Manager.Actions
{
 internal class GooglePageSpeed : DataEditor
 {
  protected override XDocument LoadDataEditor(MgrContext context)
  {
   string form = FormParser.LoadForm("Smartsite.Manager.Source.Actions.GooglePageSpeed.DataEditor.xml");
   XDocument dataEditor = XDocument.Parse(form); 
   return dataEditor;
  }
 
  protected override XDocument LoadInstance(MgrContext context)
  {
   object obj;
   string url = "";
   if (context.Action.Parameters.TryGetValue("url", out obj) && obj != null)
    url = (string)obj;
 
   XDocument document = new XDocument();
 
   XElement root = new XElement(XNamespaces.MetaDocument + "data",
    new XElement(XNamespaces.MetaDocument + "entry",
     new XElement(XNamespaces.MetaDocument + "url", url),
     new XElement(XNamespaces.MetaDocument + "filterthirdparty", false),
     new XElement(XNamespaces.MetaDocument + "screenshot", true),
     new XElement(XNamespaces.MetaDocument + "strategy", 
      new XElement(XNamespaces.MetaDocument + "key", "desktop"))
    ));
   document.Add(root);
   return document;
  }
 
  public override ActionResult ButtonClicked(MgrContext context, XFormsSubmission submission, ref XDocument instance)
  {
   if (!submission.Id.Equals("save"))
    return null;
 
   XElement entry = instance.Root.Element(XNamespaces.MetaDocument + "entry");
   string url = (string)entry.Element(XNamespaces.MetaDocument + "url");
   url += "&screenshot=" + (string)entry.Element(XNamespaces.MetaDocument + "screenshot");
   url += "&strategy=" + (string)entry.Element(XNamespaces.MetaDocument + "strategy");
   url += "&filter_third_party_resources=" + (string)entry.Element(XNamespaces.MetaDocument + "filterthirdparty");
   url += "&locale=" + UserWorkspace.Current.UserProfile.Culture;
 
   Dictionary<string, object> parameters = new Dictionary<string, object>();
   parameters.Add("url", url);
   return ActionResult.StartSubAction(typeof(GooglePageSpeedResult), parameters);
  }
 }
}

The LoadDataEditor() function loads the DataEditor xml from an embedded resource, see example below.

XML CopyCode image Copy Code
<dataeditor xmlns="http://smartsite.nl/namespaces/dataeditor/2.0" xmlns:xf="http://www.w3.org/2002/xforms" xmlns:ev="http://www.w3.org/2001/xml-events">
 <layout title="GOOGLE_PAGESPEED">
  <tabs>
   <tab title="">
    <field name="url" caption="URL">
     <control type="textbox">
      <properties>
       <property name="maxlength">500</property>
       <property name="required">true</property>
       <property name="alert">{text:URL_REQUIRED}</property>
      </properties>
     </control>
    </field>
    <field name="filterthirdparty" caption="PAGESPEED_FILTER_THIRDPARTY">
     <control type="checkbox">
      <properties>
      </properties>
     </control>
    </field>
    <field name="screenshot" caption="PAGESPEED_INCLUDE_SCREENSHOT">
     <control type="checkbox">
      <properties>
      </properties>
     </control>
    </field>
    <field name="strategy" caption="PAGESPEED_STRATEGY">
     <control type="radiobuttonlist">
      <itemset ref="is_strategy" />
      <properties>
      </properties>
     </control>
    </field>
   </tab>
  </tabs>
  <buttons>
   <button code="save" css="icon_sys22 icon-sys22-execute" caption="EXECUTE" replace="instance" relevant="false"></button>
   <button code="close"></button>
  </buttons>
  <modellogic xmlns:xf="http://www.w3.org/2002/xforms">
   <xf:bind nodeset="cms:entry/cms:screenshot" type="xf:boolean" />
   <xf:bind nodeset="cms:entry/cms:filterthirdparty" type="xf:boolean" />
  </modellogic>
 </layout>
 <itemsets>
  <itemset id="is_strategy" key="key" value="name">
   <item key="desktop" value="{text:DESKTOP}" />
   <item key="mobile" value="{text:MOBILE}" />
  </itemset>
 </itemsets>
</dataeditor>

This xml specifies the UI (the html input form).

The LoadInstance() function creates and returns the Instance xml. Since we're not editing an existing record for a specific table, we're able to construct this instance from scratch.

When the action is started, the following input form will be displayed:

Google Pagespeed action example

The ButtonClicked() function is called when the Execute button is clicked (which in fact is the save button, but with a different caption).
This function creates an url, based on the user input, and starts a subaction of type GooglePageSpeedResult, passing this url as parameter.

GooglePageSpeedResult

For completeness' sake, the C# source code for the GooglePageSpeedResult class (a simplified version of it) is included below.

C# CopyCode image Copy Code
using Smartsite.Base;
using Smartsite.Data;
using Smartsite.Manager.Resources;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Web.Configuration;
 
namespace Smartsite.Manager.Actions
{
 internal class GooglePageSpeedResult : BaseAction
 {
  public override ActionResult Execute(MgrContext context)
  {
   if (context.Request.Form["action"] == "close")
    return ActionResult.CloseAction();
   
   string url = null;
   object obj;
 
   string apiKey = WebConfigurationManager.AppSettings["google.pagespeed.apikey"];
   if (String.IsNullOrWhiteSpace(apiKey))
    apiKey = WebConfigurationManager.AppSettings["google.apikey"];
 
   if (context.Action.Parameters.TryGetValue("url", out obj))
   {
    url = "https://www.googleapis.com/pagespeedonline/v2/runPagespeed?url=";
    url += (string)obj;
    if (!String.IsNullOrWhiteSpace(apiKey))
     url += "&key=" + apiKey;
    context.Action.State = url;
   }
   else 
   {
    url = (string)context.Action.State;
   }
 
   if (url == null)
    throw new Exception("Url parameter not specified");
 
   ButtonBar buttonBar = new ButtonBar();
   buttonBar.Buttons.Add(new Button("close", Translations.GetString("CLOSE"), "icon_sys22 icon-sys22-cancel", true));
   context.Response.Layout[ThreeColumnLayout.ButtonBar] = buttonBar.Render();
 
   StringBuilder sb = new StringBuilder();
   context.Response.IncludedCss.Add(new CssInclude(ManagerResource.CreateUrl("css/library.css")));
   context.Response.InlineJavascripts.Add("$(function () { shell.PrepareInterceptor($(\"form\")); });");
 
   HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
 
   string result = null;
   try
   {
    using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
    {
     using (StreamReader reader = new StreamReader(response.GetResponseStream()))
     {
      result = reader.ReadToEnd();
     }
    }
   }
   catch (WebException we)
   {
    // code omitted
   }
   
   sb.AppendLine("<div class=\"selectable\">");
   sb.AppendFormat("<h1>{0}</h1>", Translations.GetString("GOOGLE_PAGESPEED_RESULT"));
 
   if (String.IsNullOrWhiteSpace(apiKey))
   {
    // show warning
    sb.AppendFormat("<div><span class=\"information-message\">{0}</span></div>", Translations.GetString("PAGESPEED_NO_API_KEY"));
   }
 
   Json json = new Json(result);
   Json pageStats = json.GetProperty("pageStats");
 
   sb.AppendLine("<table class=\"librarytable\" style=\"width: 730px;\">");
   sb.AppendFormat("<tr><th colspan=\"2\">{0}</th></tr>", json.GetTypedProperty("id"));
   sb.AppendFormat("<tr><td>{0}</td><td>{1}</td></tr>", Translations.GetString("TITLE"), json.GetTypedProperty("title"));
   sb.AppendFormat("<tr><td>{0}</td><td>{1}</td></tr>", "Score", json.GetProperty("ruleGroups").GetProperty("SPEED").GetTypedProperty("score"));
   
   // page stats
   sb.AppendFormat("<tr><td>{0}</td><td>{1}</td></tr>", Translations.GetString("PAGESPEED_TOTAL_RESOURCES"), 
                    pageStats.GetTypedProperty("numberResources"));
   sb.AppendFormat("<tr><td>{0}</td><td>{1}</td></tr>", Translations.GetString("PAGESPEED_JS_RESOURCES"), 
                    pageStats.GetTypedProperty("numberJsResources"));
   sb.AppendFormat("<tr><td>{0}</td><td>{1}</td></tr>", Translations.GetString("PAGESPEED_CSS_RESOURCES"), 
                    pageStats.GetTypedProperty("numberCssResources"));
   sb.AppendFormat("<tr><td>{0}</td><td>{1}</td></tr>", Translations.GetString("PAGESPEED_STATIC_RESOURCES"), 
                    pageStats.GetTypedProperty("numberStaticResources"));
   sb.AppendFormat("<tr><td>{0}</td><td>{1}</td></tr>", Translations.GetString("PAGESPEED_NUMBEROF_HOSTS"), 
                    pageStats.GetTypedProperty("numberHosts"));
 
   sb.AppendFormat("<tr><td>{0}</td><td>{1}</td></tr>", Translations.GetString("PAGESPEED_TOTAL_BYTES"), 
                    Helpers.BytesToString(Convert.ToInt64(pageStats.GetTypedProperty("totalRequestBytes"))));
   sb.AppendFormat("<tr><td>{0}</td><td>{1}</td></tr>", Translations.GetString("PAGESPEED_HTML_BYTES"), 
                    Helpers.BytesToString(Convert.ToInt64(pageStats.GetTypedProperty("htmlResponseBytes"))));
   sb.AppendFormat("<tr><td>{0}</td><td>{1}</td></tr>", Translations.GetString("PAGESPEED_JS_BYTES"), 
                    Helpers.BytesToString(Convert.ToInt64(pageStats.GetTypedProperty("javascriptResponseBytes"))));
   sb.AppendFormat("<tr><td>{0}</td><td>{1}</td></tr>", Translations.GetString("PAGESPEED_CSS_BYTES"), 
                    Helpers.BytesToString(Convert.ToInt64(pageStats.GetTypedProperty("cssResponseBytes"))));
   sb.AppendFormat("<tr><td>{0}</td><td>{1}</td></tr>", Translations.GetString("PAGESPEED_IMG_BYTES"), 
                    Helpers.BytesToString(Convert.ToInt64(pageStats.GetTypedProperty("imageResponseBytes"))));
   sb.AppendFormat("<tr><td>{0}</td><td>{1}</td></tr>", Translations.GetString("PAGESPEED_OTHER_BYTES"), 
                    Helpers.BytesToString(Convert.ToInt64(pageStats.GetTypedProperty("otherResponseBytes"))));
   sb.AppendLine("</table>");
   
   // ruleResults skipped
 
   sb.AppendLine("</div>");
 
   context.Response.Layout[ThreeColumnLayout.CenterColumn] = sb.ToString();
   return ActionResult.ActionCompleted();
  }
 }
}

The subaction result will look like this:

Google Pagespeed result example