Webdrawer - playing audio files

Webdrawer does not support the playing of audio files, instead you will get an error message when you preview a Record with an audio file attached.  This can be remedied by adding some HTML to the preview template. To do this, in your Webdrawer install folder:

Open the file '\Views\WDRecordPreview.cshtml'  in a text editor

Add the following HTML

if (new string[] { "MP3", "M4A", "WAV", "OGG" }.Any(ext => ext == record.Extension.Value))
{
    <audio preload="auto" autobuffer controls>
        <source src="~/Record/@record.Uri/file/document?inline=true" />
        <p>Audio playback not supported in this browser.</p>
    </audio>
}
else

Your HTML should end up looking like this:

audio.PNG

Recursive searching in WebDrawer and ServiceAPI

The CM search syntax allows for recursive searching, for example returning results from folders contained within folders.  The operator that specifies recursion is the plus sign (+), the problem is that in a URL the plus sign is reserved to indicate white space.

To overcome this the ServiceAPI (and WebDrawer) use the tilde (~) instead of the plus sign, so instead of this:

container:[title:asia]+

Do this:

container:[title:asia]~

If you would rather use a character (or sequence of characters) other than the tilde you can customise this in the hptrim.config.  The Web Client, for example, uses _$_ instead of tilde, which is set in the  'searching' element in its hprmServiceAPI.config.

<searching pageSize="30" searchRecursiveOption="_$_"/>

Using a search query instead of a Uri in a URL

Recently I wanted to link to a Record using the external ID, not the Uri or the number.  This was from a customised WeDrawer page so I could have done a search to find the Uri and then used that, but I didn't want that extra step.  A little reflection (and poking around in the code) and I realised that I could use the external id in the URL after all.

If it was a search

A search by external id would look like this, problem was I want to open the actual record page, not a list of one record.

/WebDrawer/record?q=recExternal:abc123

As a URL

As long as a search returns only one result then you can substitute the query for the Uri in the URL, as long as you can compose the query without using a colon (colons are reserved characters in URLs).  So below I use equals instead of colon to either open the details page or go directly to the document via the external id.

http://localhost/WebDrawer/record/recExternal=abc123
http://localhost/WebDrawer/record/recExternal=abc123/file/document

Note

As is my habit I use the internal name for the search clause (recExternal) not the name (external) to minimise the possibility of breakages due to either custom captions or use of different languages.

Where to store config, web.config or not...

For those who customise WebDrawer on one machine and then push it to a production machine this may prove handy.  I wanted to change the setup of IIS on my dev machine to not update the web.config when I change the authentication settings.  Here is what I did...

1. Go to Feature delegation in IIS Manager

feature_delegation_icon.PNG

2. Select Custom Sites

custom_sites.PNG

3. Set the authentication settings to Read Only

read_only.PNG

4. Clean modified web.configs

Remove the <security /> section from all web.config files that have already been updated, otherwise you will get an error when you attempt to open these applications.  Also check c:\inetpub\wwwroot\web.config to ensure that authentication has not been set here.

5. Update applications in IIS

Go through each application in IIS and ensure the authentication settings are correct, this will update applicationHost.config.

Summary

Setting these settings to Read Only prevents them being updated in the application specific web.config, instead they will be set in the applicationHost.config

Custom Authentication

A couple of years I wrote a post describing Google authentication in the ServiceAPI.  This is an update of that.  The details are in the samples repo and here is a video of me applying the sample from the repo to a WebDrawer and Web Client instance.

Other authentication providers

Remember the repo contains a link the ServiceStack documentation which provides assistance for using (and creating) other authentication providers.

Filter WebDrawer Search Forms

Lets say you have designed multiple search forms which are used via WebDrawer.  These forms provide different fields with which to search.  In addition to this you may also want to filter the results for each form to make them appropriate for the target audience.

Embedding the filter

One approach is to embed a filter based on the search form name. I did this by:

  1. editing Views\Search.cshhtml
  2. enable 'Name' retrieval for the search form
  3. add a filter field based on the name.

Name retrieval

Near the top of Search.cshtml you will see code that looks like this

searchForm = TrimHelper.Search<SearchForm>(
    BaseObjectTypes.SearchForm,
    "all",
    properties: new string[] { "ObjectType", "DefinitionForm" }).FirstOrDefault();

Add the property 'Name' in the propert list, so that it now looks like this:

searchForm = TrimHelper.Search<SearchForm>(
    BaseObjectTypes.SearchForm,
    sfName,
    properties: new string[] { "ObjectType", "DefinitionForm", "Name" }).FirstOrDefault();<p>Hello, World!

Add the filter

Further down the page is found the search form itself, starting with this HTML:

<form id="search-form-form" class="form-horizontal search-form" action="FormSearch" method="GET">

After this line add some code to insert a filter INPUT element, potentially different for each search form.  The code below add a filter for the search form named 'Test Form'

@if (searchForm.Name == "Test Form")
{
    <input type="hidden" name="filter" value="recType:Document" />
}

Warning

This is a convenience, not a security measure.  For example a user could still modify the URL resulting from this search to avoid the filtering.

WebDrawer - Show contained Records

How would we show a list of all contained Records within the details page of a container in WebDrawer?  I can think of at least two ways.

Embed contained Records

Edit detailsView.cshtml and include the code below somewhere near (or at) the bottom.  This will embed up to 500 contained Records in this page.

@if (Model.TrimModel.TrimType == BaseObjectTypes.Record)
{
    Record record = trimObject as Record;
    if (record.IsContainer)
    {
        <h2>Contained Records</h2>
        <table class="table">
            <thead>
                <tr>
                    <th>Title</th>
                    <th>Number</th>
                </tr>
            </thead>
            <tbody>
                @foreach(Record containedRec in this.TrimHelper.Search<Record>(
                    BaseObjectTypes.Record,
                    string.Format("recContainer:{0}", record.Uri),
                    properties: new string[] { "RecordTitle", "RecordNumber" },
                    pageSize: 500))
                {
                    <tr>
                        <td><a href="~/Record/@containedRec.Uri">@containedRec.Title</a></td>
                        <td>@containedRec.Number</td>
                    </tr>
                }
            </tbody>
        </table>
    }
}

Embed contained Records within an IFrame

It is also possible to embed the contained Records as an iframe.  The advantage of this is that you can page through a large result set inside the iframe, the cost is that you will need to set up  clean view without the menu and banner and link that up using the routeDefaults (in hprtrim.config).

I can provide examples of the clean view and routeDefaults on request.

<h2>Contained Records</h2>
<iframe src="~/Record?q=@string.Format("recContainer:{0}", record.Uri)" width="100%" height="200"></iframe>

The result

The result of the first option above will look something like this.

WebDrawer download file from PDF/TIF preview

WebDrawer uses the Trapeze viewer for PDF and TIF files, it was pointed out to me that when in this view the user does not have a menu link to download the original document. What the end users wanted was something like this.

The Solution

I am not sure why we do not have a download link in this context to start with, in the mean-time to add one:

  1. open the file \Views\HTML5_Viewer.cshtml
  2. find the menu code as seen below
  3. add a new LI inside the UL (see 'code to add')

Code to add

<li>
    <a href="~/Record/@record.Uri/File/document">@Translations.lang.document</a>
</li>

Add a count button to WebDrawer

It is possible to speed up the search results in WebDrawer by switching to simpler page navigation.  This post shows how to add a 'Get Count' button to allow users to determine the total number of search results.

Follow the steps in the video and use the code snippets below.

The Code

Views\Shared\searchResults.cshtml

<ul id="trim-pagination">
    <li class="@(start < 2 ? "disabled" : null)">
        @if (start > 1)
            {
                queryString["start"] = (start > pageSize ? start - pageSize : 0).ToString();
            <a href="?@queryString.ToFormUrlEncoded()">&laquo;</a>
        }
        else
        {
            <span> &laquo;</span>
        }
    </li>
    <li>
        @{
            var t = ((start - 1) / pageSize) + 1;
        }
        <span>@t</span>
    </li>

    <li class="@(!this.Model.HasMoreItems ? "disabled" : null)">
        @if (this.Model.HasMoreItems)
            {
                start = (start == 0 ? start + 1 : start);
                queryString["start"] = (start + pageSize).ToString();
            <a href="?@queryString.ToFormUrlEncoded()">&raquo;</a>
        }
        else
        {
            <span>&raquo;</span>
        }
    </li>
    <li>
        <button id="getCount" class="btn search-btn count-btn">Get Count </button>
    </li>
</ul>

css\main.css

.count-btn
{
    margin-left: 20px;
    margin-top:4px;
}

html.busy, html.busy * {  
  cursor: wait !important;  
} 

scripts\webdrawer.js

$("html").bind("ajaxStart", function () {
    $(this).addClass('busy');
}).bind("ajaxStop", function () {
    $(this).removeClass('busy');
});


$("#getCount").click(function () {
    var q = location.href;
    var my_url = location.href + "&format=json&ExcludeCount=true&CountResults=true";
    $.get(my_url, function (response) {
        $("#getCount").text("Count: " + response.Count);
    });
});

WebDrawer Error Message

In WebDrawer 8.2 and 8.3 you might see the error message below...

bad_error.PNG

This error reflects the fact that an error has occurred in the process of parsing an error message, so you have an error but do not know what it is. A better error message might look like this...

To fix this problem edit the hptrim.config file and add the uploadBasePath as seen below.  The uploadBasePath is not used by WebDrawer but the error handling in the underlying framework needs it. 

<hptrim 
    uploadBasePath="[Not Used]"
    ...

WebDrawer - Hide And/Or button in search form

When you create a search form you may choose to only give it one field, in which case the And/Or button is redundant (see below).

The cleanest way to hide this button is to edit the razor template that produces the form.  One simple change is required, first open Views\Search.cshtml (in 8.2 Views\FormSearch.cshtml) and then:

Find the HTML that looks like this:

<label class="radio inline">
    <input type="radio" name="BooleanOperator" value="and" />And
 </label>
<label class="radio inline">
    <input type="radio" name="BooleanOperator" value="or" checked />Or
</label>

And wrap it in an IF statement like this:

@if (searchForm.DefinitionForm.Pages.SelectMany(x => x.PageItems).Count() > 1)
{
    <label class="radio inline">
        <input type="radio" name="BooleanOperator" value="and" />And
    </label>
    <label class="radio inline">
        <input type="radio" name="BooleanOperator" value="or" checked />Or
    </label>
}

Create a new Record from WebDrawer

There are many samples in the ServiceAPI documentation for creating forms to create a new Record, you can adapt any of these to work in WebDrawer if you want to add a record creation form in your WebDrawer instance.  Here is a very simple first form... 

Steps

1. Create file

In the WebDrawer install folder create a new folder, give it any name (I called mine 'My'). Inside that folder create a text file (with a CSHTML extension) with any name (I called mine 'Page.cshtml').

2. Create a Form

Paste the code below into your new file:

@{
    ViewBag.Title = "Create and Upload a Document";

    var recordTypes = this.TrimHelper.Search<RecordType>(
        BaseObjectTypes.RecordType, "usable",
        pageSize: 100, properties:new string[]{"Name"}, sortBy:new string[]{"Name"});
}

<form class="trim-form" method="post" action="~/Record" enctype="multipart/form-data" style="margin-left:20px">
    <input type="hidden" name="Continue" value="Record?q=uri:{0}" />
    <fieldset>
        <legend>Document details</legend>
        <label>Record Type:</label>
        <select name="@PropertyIds.RecordRecordType">
            @foreach (var recordType in recordTypes)
            {
                <option value="@recordType.Uri">@recordType.Name</option>
            }
        </select>
        <label>Title</label>
        <input name="@PropertyIds.RecordTitle" />
        <label>Files:</label>
        <input type="file" name="Files" multiple="multiple" />
        <button type="submit" class="btn">Submit</button>
    </fieldset>
</form>

3. Open the page

Because my folder is called 'My' and my page called 'Page.cshtml' I can open the page like this:

http://MyServer/webdrawer/my/page

Summary

Read through the ServiceAPI documentation and browse the samples for more ideas.  In essence you can create any type of RM object simply by posting a set of correctly named properties to the correct end point.

WebDrawer search form lookup items

As you can see in the image below the lookup items displayed for an Additional Field in a search form are not sorted by name.  This is not a problem when there are only four items but is when there are many.  The good news is that, with a little bit of code, we can fix this.

The process

Find the correct WebDrawer view

In the WebDrawer folder find the file Views\FormSearch.cshtml (or Views\Search.cshtml in 8.3 and later).

Add code to fetch items

Near the top of the page find this line of code:

HttpContext.Current.Response.AppendCookie(new HttpCookie("HPRM_webDrawer_SearchForm", sfName));

On the next line add this code:

    var roadSurfaces = TrimHelper.Search<LookupItem>(
            BaseObjectTypes.LookupItem,
            "lkiSet:\"Road Surfaces\"",
            pageSize:1000,,
            sortBy:new string[] { "lkiName" },
            properties: new string[] { "Name" }).Select(li => li.Name);

Now edit this code so that instead of searching for all the items in the Lookup Set 'Road Surfaces' it uses the name of your lookup set.  If you want to also change the name of the variable from 'roadSurfaces' to something else, if you do this then code you add later will also need to be modified.

 

Create a SELECT element

Find the code that looks like this:

Prepend this code:

if (pageItem.Id == "9000000000")
{
   <select name="@pageItem.ClauseName" multiple class="wd-search-field">
       @foreach (string val in roadSurfaces)
       {
           <option>@val</option>
        }
    </select>
 } else

So that now it looks like this:

Set Field Uri

Modify the code pasted above so that instead of '9000000000' you have the Uri of the additional field you wish to use the lookup items for.

The Result

The result of adding these two code blocks is that you have complete control of the Lookup Items are displayed for a particular field.  You can add the blocks multiple times for multiple fields.

Warning

Keep a record of any changes you make to your WebDrawer templates so that you can re-apply them when you upgrade.

Displaying Notes in WebDrawer

Someone just pointed out that Record Notes do not display in the WebDrawer details page.  We hope to fix that in an upcoming 9.x release but if you want to patch it manually here is how.

First

Before you fix WebDrawer to allow it to display Notes make sure you have added Notes to the routeDefaults, for example:

<add
    name="Record"
    model="RecordFind"
    template="WDRecordDetails"
    properties="NameString,RecordIsElectronic,enabledcommandids,RecordRelatedRecs,RecordIsContainer,RecordIsInPartSeries,RecordRecordType,RecordPrimaryContact,Notes"
    propertySets="Identification,Locations,Electronic,Action,ChildLists,ChildDetails,Parts,Archiving,Container,Other"
/>

The Update

 Edit string resources

Add the label 'Notes' to the string resources by editing App_GlobalResources\lang.resx and adding the followg XML before the end tag </root>.

  <data name="field_groups_notes" xml:space="preserve">
    <value>Notes</value>
  </data>

Update detailsView.cshtml

Follow these steps to update the file Views\Shared\detailsView.cshtml. 

Add Notes variable

Add this below the line that reads 'string jr_other = 'jr_other'

string notes = "notes";

Add field group

Inside the initialisation for the fieldGroups array add this line:

new { Caption = Translations.lang.field_groups_notes, Id = notes, forTrimType = BaseObjectTypes.Record},

Add property category

After the line that reads 'var propertyCategories = new Dictionary<PropertyIds, string>();' add this:

propertyCategories.Add(PropertyIds.RecordNotes, notes);

Done

Once you have done the above Record Notes should display in a Notes