Taoffi's blog

prisonniers du temps

doc5ync – word index web page presentation!

Objectives:

  • Display a cloud of index words, each dimensioned relative to its occurrences in e-book information (e-book title, description, author, editor… etc.)
  • On selection of a given word: display the list of e-book references related to the selected word
  • On selection of an e-book: display its information details and all words linked to it

Context:

doc5ync web interface is based on a meta-model engine (simpleSite, currently being renamed to web5ync!).

I talked about meta-models in a past post Here, with some posts about its potential applications here.

The basic concept of meta-models is to describe an object by its set of properties and enable the user to act on these properties by modifying their values in the meta-model database. On runtime, those property values are assigned to each defined object.

In our case, for instance, we have a meta-model describing web page elements, and a meta-model describing the dataset of word index and their related e-books.

For web page elements, the approach considers a web page as a set of html tags (i.e. <div>, <table><tr><td>…, <img… etc.). Where each tag has a set of properties (style, and other attributes) for which you can define the desired values. On runtime, your meta-model-defined web page comes to life by loading its html tags, assigning to each the defined values and injecting the output of the process to the web response.

A dataset is similarly considered as a set of rows (obtained through a data source), each composed of data cells containing values. Data cells can then be either presented and manipulated through web elements (html tags, above) or otherwise manipulated through web services.

Data storage and relationships

As we mentioned in the previous post, index words and their related e-books are stored in database tables as illustrated in the following figure:

Each word of the index provides us with its number of occurrences in e-book text sequences (known on Trie scan).

Html formatting using a SQL view

To reflect this information into a presentation, we used a view to format a html div element for each word relative to its number of occurrences. The query looks like the following code

select
  w.id            as word_id
 , w.n_occurs
 , N'<div style="BASIC STYLE STRING HERE…; display:inline;'

-- add the font-size style relative to number of occurrences
+
case
    when w.n_occurs between 0 and 2    then N' font-size:10pt;'
    when w.n_occurs between 3 and 8    then N' font-size:14pt;'
    when w.n_occurs between 9 and 15    then N' font-size:16pt;'
    when w.n_occurs between 16 and 24    then N' font-size:22pt;'
    when w.n_occurs between 25 and 2147483647    then N' font-size:26pt; '
end

+ N'"'
-- add whatever html attributes we need (hover/click…)
+
N' id="div' + convert(nvarchar(32), w.id)
+ N'" onclick="select_data_cell(''' + convert(nvarchar(32), w.id) + N''');" '
     as word_string_html

-- add other columns if needed
from dbo.doc5_trie_words w
order by w.word_string

 

The above view code provides us with html-preformatted string for each word index in the data row.

Tweaking the data rows into a cloud of words

On a web page, a dataset is commonly displayed as a grid (table / columns / rows), and web5ync knows how to read a data source, and output its rows into that form. But that did not seem to be convenient in our case, because it simply displays index words each on a row which is not really the presentation we are looking for!

 

To resolve this, we simply need to change the dataset web container from a <table> (and its containing rows / cells) into <div> tags (with style=display: inline).

Here a sample of html code of the above presentation:

<table>
  <tr>
    <td>
    <div style="font-size:16pt;" onclick="select_data_cell('29008');">After</div>
  </td>
</tr>
<tr>
  <td>
    <div style="font-size:26pt; onclick="select_data_cell('28526');">after</div>
  </td>
</tr>

<!-- the table rows go on... --> 


And here is a sample html code of the presentation we are looking for:

<div style="display:inline;">
    <div id="td_word_string_html82" style="display:inline;">
       <div style="font-size:26pt;" onclick="select_data_cell('28526');">after</div>
      </div>
</div>

<div style="display:inline;">
    <div id="td_word_string_html83">
      <div style="font-size:16pt;" onclick="select_data_cell('29008');">After</div>
    </div>
</div>

<div style="display:inline;">
   <div style="display:inline;">
      <div style="font-size:10pt;" onclick="select_data_cell('17657');">AFTER</div>
    </div>
</div>

 

Which looks closer to what we want:

Interacting with index words

The second part of our task is to allow the user to interact with the index words: clicking a word = display its related e-books, clicking an e-book = display the e-book details + display index words specifically linked to that e-book.

For this, we are going to use a few of the convenient features of web5ync, namely: Master/details data binding, and Tabs. (I will write more about these features in a future post)

Web5ync master/details binding allows linking a subset of data to a selected item in the master section. Basically, each data section is an iframe. The event of selecting a data row in one iframe can update the document source of one or more iframes. All what we need is: 1. define a column that will be used as the row's id, and 2. define how the value of that id should be passed to the target iframe (typically: url parameter name).

Tabs are convenient in our case as they will allow distributing the information in several areas while optimizing web page space usage.

In the figure above, we have 3 main data tabs: n Explore by words, n Document info and n Selected document words.

On the first tab:

  • clicking a word (in the upper iframe) should display the list of its related e-books (in lower iframe of that same tab)
  • clicking an e-book row on the lower iframe should: first displays its details (an iframe on the second tab), and display all words directly linked to the selected e-book (an iframe in the 3rd tab). (figures below)

In that last tab, we can play once again with the displayed words, to show other documents sharing one of them:

Html Content Viewer for Silverlight

In a previous post, I talked about a solution to manipulate (animate for example) the Silverlight control inside the hosting html page.

 

The reverse side of the problem (displaying html content inside a Silverlight control), is a requirement which comes out in the context of numerous web projects.

 

Some of the proposed solutions suggest html translators in order to obtain the final text into a sort of ‘Rich Edit’ control.

Although it is very good to have a rich edit Silverlight control, the solution seems too tedious to solve a relatively simple problem: displaying html content into an html page (i.e. the page hosting our Silverlight control).

 

Building on the previous sample of animating the Silverlight control into the hosting page, I tried to make a user control that dynamically creates an iFrame to display the desired html content inside the main Silverlight page.

 

You can have a look at the proof of concept Here!

 

 

 

Dynamically create an html control

Suppose we want to display the html page: http://mycompany.com/page.html inside our Silverlight control.

 

The solution can be:

§  Use the HtmlPage to locate the hosting HtmlElement (where our Silverlight control lives: that can be straightforward:  HtmlPage.Plugin.Parent;)

§  Create a new html container (for example, a DIV)

§  Place an iframe html element inside this container

§  Set the iframe source to the desired page

§  Insert the container into the hosting HtmlElement

 

 

Here is a sample code illustrating these steps

 

private void CreateHtmlContent()

{

       HtmlElement  plugin_div   = HtmlPage.Plugin.Parent,

                    new_div      = HtmlPage.Document.CreateElement("div"),

                    iframe       = HtmlPage.Document.CreateElement("iframe");

 

       /// set the new div position to absolute

       new_div.SetStyleAttribute("position", "absolute");

       new_div.SetStyleAttribute("z-order", "5");

 

       new_div.SetStyleAttribute("left", "200px");

       new_div.SetStyleAttribute("top", "40px");

       new_div.SetStyleAttribute("width", "400px");

       new_div.SetStyleAttribute("height", "400px");

 

       /// setup the iframe style, attributes and target page

       iframe.SetStyleAttribute("width", "100%");

       iframe.SetStyleAttribute("height", "100%");

       iframe.SetAttribute("src", "http://www.isosoft.org/taoffi/");

 

       /// add the iframe to the new div

       new_div.AppendChild(iframe);

 

       /// add the new div to the hosting html page

       plugin_div.AppendChild(new_div);

}

 

The solution exposed here can be a suitable foundation to solve many situations where html content can be composed and/or displayed inline within a Silverlight control.

Let us begin by making a custom control, a HtmlContentViewer say!

 

The sample code exposes some more!

 

SilverHtmlContent.zip (49.34 kb)

Animating Silverlight control inside the HTML Page

Live demo

.

 

 

A Silverlight control can communicate and interact with other HTML elements of the page on which it is placed.

This is done by using the HtmlPage object (namespace System.Windows.Browser)

 

By using HtmlPage, you can have access to all HTML elements of the current page and be able to get and set many of their properties and attributes, and, accessorily, call DHTML script functions that may be located there.

This means, among other things, that you can get and set style attributes of the HTML element inside which your own Silverlight control is hosted (the DIV element having the id commonly named "silverlightControlHost")

 

<div id="silverlightControlHost"

       ...

 

This gave me an idea about creating a custom control which would represent this same hosting html DIV element and, through this custom control, interact with the hosting element properties (width, height, location… etc.)

One of the nice features of Silverlight is the Storyboard animations usually used to animate Silverlight controls.

So, what if we try to use a storyboard to animate the Silverlight hosting html element!

 

Suppose that our animation should move the html host element across the page. From an html viewpoint, this would mean to change its top/left coordinates.

A Silverlight user control doesn’t contain Top / Left properties required to do such a job. So we have to add those properties to our custom control.

 

I first created an (empty) custom user control:

 

Xaml:

<UserControl x:Class="SilverHtmlInteraction.HostingDivControl"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

       Background="Transparent"

       SizeChanged="UserControl_SizeChanged"

    Width="200" Height="200">

    <Grid x:Name="LayoutRoot" Background="White">

 

    </Grid>

</UserControl>

 

The control members:

 

private HtmlElement m_div        = null;

 

public HtmlElement Div

{

       get { return m_div; }

       set { m_div = value; }

}

 

The new Left and Top properties

 

public double Left

{

       get { return attrib = GetDimension("left", "offsetLeft", 0.0); }

       set { SetDimentionAttribute("left", value); }

}

public double Top

{

       get { return attrib = GetDimension("top", "offsetTop", 0.0); }

       set { SetDimentionAttribute("top", value); }

}

 

 

How to obtain html element dimensions (width, height, left, top... etc.)

 

// get the html element’s dimension (example: width, height, left...)

// first try to find the dimension in the element’s style (width, height, left, top...)

// if not found, try to obtain the element’s dimension property (offsetWidth, offsetHeight...)

private double GetDimension(string attrib_name, string property_name, double default_value)

{

       double attrib = GetDimensionAttribute(attrib_name, default_value);

 

       // if the style attribute is not present, try to get the dimension property

       if (attrib <= 0.0)

             return GetDimensionProperty( property_name, default_value);

       return attrib;

}

 

// get the html element’s style attribute (example: width, height, left...)

protected double GetDimensionAttribute(string attrib_name, double default_value)

{

       if (m_div == null)

       {

             debug_display_div_error();

             return default_value;

       }

 

       // try to get the style attribute’s value

       string str_value = m_div.GetStyleAttribute(attrib_name);

 

       if (string.IsNullOrEmpty(str_value))

       {

             return default_value;

       }

       // remove the dimension's unit (px)

       str_value = str_value.Replace("px", "");

 

       double value = default_value;

 

       // try to convert to numeric value

       if (double.TryParse(str_value, out value) == false)

             return default_value;

       return value;

}

 

// get the html element’s dimension property (example: offsetHeight, offsetWidth...)

protected double GetDimensionProperty(string property_name, double default_value)

{

       if( m_div == null)

       {

             debug_display_div_error();

             return default_value;

       }

      

       string str_value    = m_div.GetProperty( property_name).ToString();

 

       if (string.IsNullOrEmpty(str_value))

       {

             return default_value;

       }

       double value = default_value;

 

       // try to convert to numeric value

       if (double.TryParse(str_value, out value) == false)

             return default_value;

 

       return value;

}

 

I then registered the new Left and Top properties with the dependency system:

 

public static DependencyProperty LeftProperty = DependencyProperty.Register(

                    "Left", typeof(double), typeof(HostingDivControl),

                    new PropertyMetadata(

                           new PropertyChangedCallback(LeftPropertyChanged) ));

 

public static DependencyProperty TopProperty = DependencyProperty.Register(

                    "Top", typeof(double), typeof(HostingDivControl),

                    new PropertyMetadata(

                           new PropertyChangedCallback(TopPropertyChanged)));

 

private static void LeftPropertyChanged(DependencyObject obj,

                           DependencyPropertyChangedEventArgs e)

{

       if (obj == null)

             return;

       if (obj is HostingDivControl != true)

             return;

 

       HostingDivControl ctrl = (HostingDivControl)obj;

 

       ctrl.Left    = (double)e.NewValue;

}

 

 

private static void TopPropertyChanged(DependencyObject obj,

                           DependencyPropertyChangedEventArgs e)

{

       if( obj == null)

             return;

       if( obj is HostingDivControl !=true)

             return;

 

       HostingDivControl   ctrl   = (HostingDivControl) obj;

 

       ctrl.Top     = (double) e.NewValue;

}

 

 

The html element accessor should do some work each time this member is changed:

 

public HtmlElement Div

{

       get { return m_div; }

       set

       {

             m_div = value;

 

             if (m_div != null)

             {

                    base.Width   = this.Width;

                    base.Height  = this.Height;

                    Left         = GetDimension("left", "offsetLeft", 0.0);

                    Top          = GetDimension("top", "offsetTop", 0.0);

 

                    // to be able to change the left and top propertites,

                    // the html element should have its position style set

                    // to relative or absolute.

                    If (string.IsNullOrEmpty( m_div.GetStyleAttribute("position")))

                           m_div.SetStyleAttribute("position", "relative");

             }

             else

                    m_div_id = "";

       }

}

 

 

We are now ready to use our new custom control to animate the Silverlight control inside its hosting html page.

 

<Grid x:Name="LayoutRoot" Background="Transparent">

       <local:HostingDivControl x:Name="host_div" Margin="0"/>

 

The animation Storyboard

Let’s create a storyboard that changes the top/left properties of our control (which is the silver light control itself)

Note: I couldn’t use Expression Blend to create the storyboard. Blend seemed confused about the properties to be animated for this ‘special’ control. So I ended up by creating the storyboard by hand!

 

<UserControl.Resources>

       <Storyboard x:Name="Storyboard1">

             <DoubleAnimationUsingKeyFrames BeginTime="00:00:00"

             Storyboard.TargetName="host_div" Storyboard.TargetProperty="(Top)">

                    <EasingDoubleKeyFrame KeyTime="00:00:00" Value="0"/>

                    <EasingDoubleKeyFrame KeyTime="00:00:00.50" Value="50"/>

                    <EasingDoubleKeyFrame KeyTime="00:00:00.80" Value="85"/>

                    <EasingDoubleKeyFrame KeyTime="00:00:01.00" Value="130"/>

             </DoubleAnimationUsingKeyFrames>

 

             <DoubleAnimationUsingKeyFrames BeginTime="00:00:00"

             Storyboard.TargetName="host_div" Storyboard.TargetProperty="(Left)">

                    <EasingDoubleKeyFrame KeyTime="00:00:00" Value="0"/>

                    <EasingDoubleKeyFrame KeyTime="00:00:00.50" Value="100"/>

                    <EasingDoubleKeyFrame KeyTime="00:00:00.80" Value="225"/>

                    <EasingDoubleKeyFrame KeyTime="00:00:01.00" Value="350"/>

             </DoubleAnimationUsingKeyFrames>

                    ...

                    ...

 

       </Storyboard>

</UserControl.Resources>

 

 

Live demo

 

Download the sample code

AnimatedSilverHtmlHost.zip (83.45 kb)