Arild’s SuperBlog

08/03/2009

My own client side messagebox

Filed under: 6.web, Uncategorized — eikari @ 11:23
Tags:

As you may already know, there are a few JavaScript methods available to the SIX Web developers. Most, if not all of them, are placed in the Scripts folder, and there are methods for almost every need. In this post I’m going to look at the messagebox functions and round it all off making my own messagebox version.

In the Scripts/Dialog.js file you have several messagebox functions available. Three of the most common are:

YesNo (title, text, dialogType, checkBoxText, callBack, context)
YesNoCancel (title, text, dialogType, callBack, context)
ThreeButton (title, text, buttonText1, buttonText2, dialogType, context, callBack)

These methods are what you can call self explaining, but if you need to read more about them, open the Web GUI Api help file (SO.WebGUI.chm). Anyway, here is an example:

Dialog.YesNo('My Yes/No Message Box', 'Are you sure you want to do the stuff you are doing now?', 'question', '', 'MyCallBack', '');

function MyCallBack(res)
{
  alert(res);
}

Running this will display this messagebox:
My Yes/No MessageBox

The problem occurs when you want to show a messagebox with two customizable buttons only… and off curse that was what I needed.

I you use the Reflector tool to dig into the SIX Web code, you will find out that the javascripts function above actually are Ajax call to methods on the server. So, using this as a base, I created my own messagebox method using the SoDialogBase class:

Imports SuperOffice.CRM.Web.UI.Controls
Imports SuperOffice.Globalization
Imports System.Web.UI

Namespace Omere.GUI
  Public Class TwoButtonDialog
    Inherits SoDialogBase

    Private _Button1 As String = ""
    Private _Button2 As String = ""
    Private _Text As String = ""
    Private _Title As String = ""

    Public Sub New(ByVal title As String, ByVal [text] As String, ByVal buttonText1 As String, ByVal buttonText2 As String)
      _Text = [text]
      _Title = title
      _Button1 = buttonText1
      _Button2 = buttonText2
      MyBase.ID = ("twobuttondialog" & DateTime.Now.Ticks.ToString)
    End Sub

    Public Sub AddDialogButtons()
      Dim callBackFunction As String = MyBase.CallBackFunction
      callBackFunction = (("Dialog.inlineDialogCallback(this, '" & MyBase.CallBackFunction & "',") & "{0}, checkBoxValues, radioValues);")
      MyBase.AddButtons(New ButtonDefinition() {New ButtonDefinition(_Button1, String.Format(callBackFunction, 1), True), New ButtonDefinition(_Button2, String.Format(callBackFunction, 2), True)})
    End Sub

    Protected Overrides Sub Render(ByVal writer As HtmlTextWriter)
      If _Text.StartsWith("[") Then
        _Text = ResourceManager.GetString(_Text)
      End If

      If _Title.StartsWith("[") Then
        MyBase.Title = ResourceManager.GetString(_Title).ToUpperInvariant
      Else
        MyBase.Title = _Title
      End If

      MyBase.AddText(_Text)
      Me.AddDialogButtons()
      MyBase.Render(writer)
    End Sub
  End Class
End Namespace

Since this method you be available on the client side, I need to pack it into to an "Ajax" method. So, I'm creating a new class:

Imports SuperOffice.CRM.Web.UI
Imports SuperOffice.CRM.Web.UI.Controls.SoDialogBase

Namespace Omere.GUI
  Public Class AjaxMethods

    Public Function TwoButtonDialog(ByVal title As String, ByVal [text] As String, ByVal buttonText1 As String, ByVal buttonText2 As String, ByVal dialogType As String, ByVal context As String, ByVal callBack As String) As String
      dialogType = dialogType.ToLowerInvariant

      Dim none As DialogTypeEnum = DialogTypeEnum.None
      Dim str3 As String = dialogType

      If (str3 IsNot Nothing) Then
        If Not (str3 = "warning") Then
          If (str3 = "error") Then
            none = DialogTypeEnum.Error
          ElseIf (str3 = "question") Then
            none = DialogTypeEnum.Question
          End If
        Else
          none = DialogTypeEnum.Warning
        End If
      End If

      Dim control As New TwoButtonDialog(title, [text], buttonText1, buttonText2)
      control.DialogType = none
      control.CallBackFunction = callBack

      Try
        Return RenderHelper.RenderControlToString(control).Replace(ChrW(10), "")
      Catch exception As Exception
        Return ""
      End Try
    End Function
  End Class
End Namespace

As always when you are working with Ajax methods, we need to add a line in the SoObjectMapping.config:

<object type="AjaxMethod" mappingname="AjaxMethodsGUI" assemblyname="Omere.GUI" objectname="Omere.GUI.AjaxMethods" xusing_ajaxnet="true"/>

At the client side I added this script:

function OmereTwoButton(title, text, buttonText1, buttonText2, dialogType, context, callBack)
{
    var src = AjaxMethodDispatcher.CallSync("Omere.GUI.AjaxMethodsGUI.TwoButtonDialog", "", title, text, buttonText1, buttonText2, dialogType, context, callBack);
    Dialog.displayInlineModalDialog(src, context);
}

Calling the OmereTwoButton method with this code

OmereTwoButton("My Own Messagebox", "Yes! This is my own MessageBox","Button1","Button2",'info', '', 'MyCallBack');

result in:
My Own MessageBox

There is only one catch! The button text is handled as resource tags, so you have to use a resource file to specify the text.

31/01/2009

NetServer and hardcoded values in Select statements

Filed under: 6.web — eikari @ 16:47
Tags:

Have you ever needed to add a hardcoded value into one of your Select statements? To make it easy, let us say we have the following more-or-less lame SQL Union statement

SELECT id, name FROM crm5.contact WHERE name LIKE 'a%'
UNION
SELECT id, lastname FROM crm5.person WHERE lastname LIKE 'a%'

Re-creating this in NetServer should be pretty straight forward

Dim ContactSelect As SQL.Select = SuperOffice.Data.S.NewSelect
Dim ContactTI As ContactTableInfo = TablesInfo.GetContactTableInfo

ContactSelect.ReturnFields.Add(ContactTI.ContactId, ContactTI.Name)
ContactSelect.Restriction = ContactTI.Name.Like(S.Parameter("a" & "%"))

Dim PersonSelect As SQL.Select = SuperOffice.Data.S.NewSelect
Dim PersonTI As PersonTableInfo = TablesInfo.GetPersonTableInfo

PersonSelect.ReturnFields.Add(PersonTI.PersonId, PersonTI.LastName)
PersonSelect.Restriction = PersonTI.LastName.Like(S.Parameter("a" & "%"))

Dim MyUnion As SQL.Union = SuperOffice.Data.S.TableExpression.NewUnion
MyUnion.Add(ContactSelect)
MyUnion.Add(PersonSelect)

Using DBReader As New QueryExecutionHelper(MyUnion)
   ...
End Using

So far, so good… The problem occurs when you want to diverse the two SQL statements. For example if you want to make sure that all persons are shown in red, while contacts stay in black, you need a way to know if the returning row is a contact or a person. My way to solve this has always been to add a new column where I hardcode a unique number, like this:

SELECT 1, id, name FROM crm5.contact WHERE name LIKE 'a%'
UNION
SELECT 2, id, lastname FROM crm5.person WHERE lastname LIKE 'a%'

If you try to do this in NetServer, you will find out that there is not a good way to add a hardcoded value like I did in the example above. Why? Well, I actually don’t know. Anyway, after digging around I found out that we had a S.Math class where we are able to add two values. Since I’m using integer value to diverse my two SQL statements, I’m able to use this class to add my much needed column

ContactSelect.ReturnFields.Add(S.Math.NewAdd(S.Parameter(0), S.Parameter(1)))
...
PersonSelect.ReturnFields.Add(S.Math.NewAdd(S.Parameter(0), S.Parameter(2)))

It is maybe not the “correct” way of doing it, but at least it solved my problem. My follow-up question is what do we do if we needed to add a hardcoded string value? Something is telling me that S.Math.NewAdd() doesn’t get very satisfied for two string parameters…

24/01/2009

The not so buggy SoButton control

Filed under: 6.web — eikari @ 11:16
Tags:

Yesterday, I had one of those “is it possible!?” moments followed by “how stupid can I be?”

It all started when I wanted to test out the ImageSrc property on the SoButton control. I needed a search button and instead of setting caption to Search or something similar, I visioned that a button with a magnifier would make my day.

In WinForms this is pretty easy – set the image property to point to your image and run the program. Should it be more difficult in 6.web? Well, it depends on the developer…

First you need to set the ImageSrc property and make sure that the path starts with a / – which is opposite of what you have to do if you are using a <img>. If you stop here (like I did), you get a nice button that looks like this: SoButton.ImageSrc='/Images/toolicons/btn-find-passive.png'

Hmm… Something was wrong. Was it something with the path, or could it be that I needed to set the ImageWidth and ImageHeight properties to “20px”? Well, to make it short. After an hour with a lot of #@$&%!, I gave up.

Later that day I talked to Tony from SuperOffice DevNet and asked him about this buggy SoButton control. Maybee I had found a bug in the control? Let us say that the only thing I didn’t try, was to specify the ImageWidth and ImageHeight without using px.

So, to summarize it all

The not so correct way, a.k.a my way
<six:SoButton ID="btnSearch" ImageHeght="15px" ImageWidth="15px" ImageSrc="Images/toolicons/btn-find-passive.png" runat="server" />

A better way
<six:SoButton ID="btnSearch" ImageHeght="15" ImageWidth="15" ImageSrc="/Images/toolicons/btn-find-passive.png" runat="server" />

I should mention that when I finally got it correct I didn’t like it, so I replaced the button with a <img> :)

21/01/2009

Creating an ArchiveProvider from scratch, Part I

Filed under: 6.web — eikari @ 11:13
Tags:

For the last couple of weeks I have been trying to figure out how to create an ArchiveProvider from scratch, implementing the IArchiveProvider interface. The difficult part was actually not coding the archive provider itself, but to get the combination of my archive provider and a SoArchiveControl to agree that the functionality and data I wanted to present to the user was valuable enough for the system to bother (at least that was my feeling working with this stuff). So, as a part of an ongoing healing process, I will try to write myself to a better understanding of the ArchiveProvider concept (… and also avoid throwing my laptop out the window).

First some words about why I need this good looking provider functionality. One of the projects that I’m working on these days is going to retrieve data from an external Web Service. When you get the opportunity to combine this external data source with a grid kind of view, you have the perfect case for an Archive Provider + SoArchiveControl affair… at least in theory.

Since I’m working with an external data source I have two possibilities. One, I can implement the IArchiveProvider interface or two, inherit from the InMemoryProviderBase class. Both are found in the SuperOffice.CRM.ArchiveList namespace which is part of the SoDatabase assembly. There is also a base class called TypedInMemoryProviderBase, but I think this is only a typed version of the InMemoryProviderBase.

Enough chit-chat, it’s time for some code…

First I create a public class where I implement the IArchiveProvider interface and specify a class attribute named ArchiveProvider. To follow the “SuperOffice Standard”, I set the priority to int.MaxValue/2. In this first part I will keep my provider down to a minimum.

namespace Omere.Providers
{
  [ArchiveProvider("MyProvider", int.MaxValue / 2)]
  public class MyProvider : IArchiveProvider
  {
  }
}

The next step would be to declare some helping objects and define columns my provider supports. There is one object you should bear in mind, the ColumnHelper. This object simplifies column declarations by automatically scanning you provider for defined columns, and is saving you for writing a lot of code. The second helper object is the EntityHelper. As the name implies it handles entities for you. Let us see

// Helper objects
private ColumnHelper _columnHelper;
private EntityHelper _entityHelper;

// Columns used by my provider
protected ArchiveColumnInfo _colExtId = new ArchiveColumnInfo("extid", "Ext Id", "Tooltip", Constants.DisplayTypes.String, true, true, "5c");
protected ArchiveColumnInfo _colExtName = new ArchiveColumnInfo("extname", "Name", "Tooltip", Constants.DisplayTypes.String, true, true, "20%");

public MyProvider()
{
  // This line is enough for the columnhelper to scan your provider for ArchiveColumnInfo objects
  _columnHelper = new ColumnHelper(this);
  _entityHelper = new EntityHelper();

  // I have only one entity which I'm naming ExtSource
  _entityHelper.AddAvailableEntities(new ArchiveEntityInfo("ExtEntity", "Ext Entity", false));
}

To make it easier, I’m going to ignore paging and sorting in this provider, but despite this I still have to override several methods to please the interface. Luckily I can use “copy-paste” on some of them

public List GetAvailableColumns()
{
  return _columnHelper.AvailableColumns;
}

public void SetDesiredColumns(params string[] columnIds)
{
  _columnHelper.SetDesiredColumns(columnIds);
}

public List GetAvailableEntities()
{
  return _entityHelper.GetAvailableEntities();
}

public void SetDesiredEntities(params string[] entities)
{
  _entityHelper.SetDesiredEntities(entities);
}

public bool SetRestriction(params ArchiveRestrictionInfo[] restrictions)
{
  // Ignoring restrictions here to simplify the provider
  return true;
}

Last I have to code the GetRows method

public IEnumerable GetRows()
{
  List resultList = GetDataFromWebService();

  // read and format data, until we have completed one page
  while (index  0)
  {
    // create a row and mark it with our entity and primary key
    ArchiveRow row = new ArchiveRow(EntityName, 1);
    WebServiceData entity = resultList[index];
    ++index;

    // add columns, if they have been requested
    if (_columnHelper.WantAnyColumn(_colExtId))
      row.AddOverlappingColumn(_colExtId.Name, result.Id.ToString());

    if (_columnHelper.WantAnyColumn(_colExtName))
      row.AddOverlappingColumn(_colExtName.Name, result.Name);

    yield return row;
  }

  yield break;
}

Despite that I still have methods I should implement; I’m able to compile my provider and install and use it in 6.web.

In Part II, I will see how we should inform 6.web about my new fabiolus provider.

For those of you who don’t like to write code :-) , you can download it here: MyProvider.cs

Sources: Tony Yates, Dr. Marek Vocak, SuperOffice DevNet

19/01/2009

Welcome

Filed under: Uncategorized — eikari @ 22:31

Hi, and welcome to my SuperBlog!

This blog is mainly going to be about my experience developing in .Net for SuperOffice Web and Windows. Since most of the project I’m working with are involving SuperOffice 6.web, I assume most of my posts are going to be about the web platform. Anyway, tips & tricks I’m picking up in my daily work are always going to find the way to site, regardless which platform it is about.  

When it comes to maintaining my own blog, I’m a newbie. I have for a long time been a blog reader only, searching to find solutions for specific problems. So, bear with me in the beginning.

Blog at WordPress.com.