суббота, 1 сентября 2012 г.

Search in XsltListViewWebPart


I had the following problem in our project: my customer has a long list with many text fields. I should give his users a tool for quick navigation in this list, as well as for searching  and editing elements. The best solution was a text filter. When a user enters a text into it, the list automatically is filtered by all columns as follows:
First I added XsltListViewWebPart (XLVWP) with a default view, then I added an input text box with a 'Search' button:
<input type="text" name="searchText" />
<button type="submit">Search</button>

I configured a new ParameterBinding element in the XLVWP to bind it with my text box:
<ParameterBinding Name="SearchText" Location="Form(searchText)" DefaultValue="" />

Into View parameter I have added following query:
<Query>
  <Where>
    <Or>
      <Or>
        <Contains>
          <FieldRef Name="Title"/>
          <Value Type="Text">{SearchText}</Value>
        </Contains>
        <Contains>
          <FieldRef Name="Author"/>
          <Value Type="Text">{SearchText}</Value>
        </Contains>
      </Or>
      <Contains>
        <FieldRef Name="PostCategory"/>
        <Value Type="Text">{SearchText}</Value>
      </Contains>
    </Or>
  </Where>
  <OrderBy>
    <FieldRef Name="PublishedDate" Ascending="FALSE"/>
  </OrderBy>
</Query>

Now that I enter a text into my filter text box and press  'Search' button my list is filtered by Title, Author and Category columns. I can see here 3 important problems:
1.       the user has to press  'Search' button to start filtering instead of simply entering the text
2.       The user has to wait for page reload
3.        When the user first opens this page the list is empty because the filter is empty.
I started fixing these problems one by one. First I added asynchronous update to my list view. Check 'Enable Asynchronous Update' and 'Show Manual Refresh Button' in the properties of XLVWP :

 

Users have got a manual refresh button in the right-hand upper corner of the list:

When they enter a text into the filter text box and press this button, XLVWP is filtered without the page reload.  I found an event receiver in IE developer tools:

javascript: __doPostBack('ctl00$m$g_09891d16_ead7_4eb6_9588_3c2eb636c6eactl02','cancel');return false;

I added it to the onkeyup event handler of my filter text box and then removed  'Search' button:
Search: <input onkeyup="javascript: __doPostBack('ctl00$m$g_09891d16_ead7_4eb6_9588_3c2eb636c6ea$ctl02','cancel');" />

Great, now the list is filtered without page update while the user inputs the text. Ok, but there remains the last problem: an empty list when the user first comes to the page. To solve it I used a calculated field in my list: _TitleToFilter with formula: ="###"&Title. Then I added a default value to the binding parameter: ###
<ParameterBinding Name="SearchText" Location="Form(searchText)" DefaultValue="###" />

In the query I replaced Title column with _TitleToFilter:
<Contains>
  <FieldRef Name="_TitleToFilter"/>
  <Value Type="Text">{SearchText}</Value>
</Contains>

Now that the filter is empty, the sequence of three sharps (###) is used as a filter pattern. And all items have this substring in their _TitleToFilter column.

Ok, but a new problem occured: when the user clears the filter text box, the list becomes empty. The default value does not apply because the filter sends a postback parameter but with an empty value. So I added a new hidden field to send the filter value to my XLVWP and fill this field with javascript while the user enters the text into the filter:

<input type="hidden" name="searchText" id="searchText" />
Search: 
<input onkeyup="document.getElementById('searchText').value = this.value == '' ? '###' : this.value; javascript: __doPostBack('ctl00$m$g_09891d16_ead7_4eb6_9588_3c2eb636c6ea$ctl02','cancel');" />

Now it works perfectly. There is no need for the manual refresh button now. To remove it form XLVWP you can just uncheck 'Show Manual Refresh Button' in its properties.

вторник, 7 августа 2012 г.

Silverlight Online Plotter


Today I'd like to tell you about one of my old projects Silverlight Online Plotter. This is a tool that allows you to create function graphs right in browser, compare them, export and import. 

Live demo is not allow to export because I have not implemented web service that saves data. In silverlight 3.0 there is no way to create save dialog, so I exported data with http-handler. I created web request to web-service that saved data to a file and set session variable with filename. Than I made browser redirect to http-handler that returned this file.

There is code of web-service in PHP:

<?php

session_start();

if ($_GET['file']) {
$filename = $_SERVER['DOCUMENT_ROOT'].'/files/'.$_GET['file']; 
$f = fopen($filename, 'r');
if (!$f) { 
header('HTTP/1.0 404 Not Found');
exit();
}
header('Content-type: application/xplt');
header('Content-Disposition: attachment; filename="plots.xplt"');
header('Content-Length: '.filesize($filename));
echo fread($f, filesize($filename));
fclose($f);
@unlink($filename);
exit();

if ($_POST['data']) {
$f = fopen($_SERVER['DOCUMENT_ROOT'].'/files/'.session_id().'.xml', 'w');
fwrite($f, stripslashes($_POST['data']));
fclose($f);
header('Content-type: text/xml');
echo '<?xml version="1.0" encoding="utf-8" ?><data status="0" file="'.session_id().'.xml" />';
exit();
}

header('Content-type: text/xml');
echo '<?xml version="1.0" encoding="utf-8" ?><data status="1" />';

?>

пятница, 11 мая 2012 г.

Custom fields and save site as template problem


If you have custom fields with custom properties on your site and use default field serialization you'll get an error while saving such site as template. It happens because of template validation through xsd-scheme 14\TEMPLATE\XML\wss.xsd. Let's see how it looks:

<xs:complexType name="FieldDefinition" mixed="true">
    <xs:all>
      <xs:element name="FieldRefs" type="FieldRefDefinitions" minOccurs="0" maxOccurs="1" />
      <xs:element name="DisplayPattern" type="CamlViewRoot" minOccurs="0" maxOccurs="1" />
      <xs:element name="DisplayBidiPattern" type="CamlViewRoot" minOccurs="0" maxOccurs="1" />
      <xs:element name="CHOICES" type="CHOICEDEFINITIONS" minOccurs="0" maxOccurs="1" />
      <xs:element name="MAPPINGS" type="MAPPINGDEFINITIONS" minOccurs="0" maxOccurs="1" />
      <xs:element name="Default" type="xs:string" minOccurs="0" maxOccurs="1" />
      <xs:element name="Formula" type="xs:string" minOccurs="0" maxOccurs="1" />
      <xs:element name="FormulaDisplayNames" type="xs:string" minOccurs="0" maxOccurs="1" />
      <xs:element name="DefaultFormula" type="xs:string" minOccurs="0" maxOccurs="1" />
      <xs:element name="Validation" type="ValidationDefinition" minOccurs="0" maxOccurs="1" />
      <xs:element name="Customization" type="CustomizationDefinition" minOccurs="0" maxOccurs="1" />
    </xs:all>
    <xs:attribute name="Aggregation" type="xs:string" />
    <xs:attribute name="AllowDeletion" type="TRUEFALSE" />
    <xs:attribute name="AllowDuplicateValues" type="TRUEFALSE" />
    <xs:attribute name="AllowHyperlink" type="TRUEFALSE" />
    <xs:attribute name="AllowMultiVote" type="TRUEFALSE" />
    <xs:attribute name="AppendOnly" type="TRUEFALSE" />
    <xs:attribute name="AuthoringInfo" type="xs:string" />
    <xs:attribute name="BaseType" type="xs:string" />
    <xs:attribute name="CalType" type="xs:int" />
    <xs:attribute name="CanToggleHidden" type="TRUEFALSE" />
    <xs:attribute name="ClassInfo" type="xs:string" />
    <xs:attribute name="ColName" type="xs:string" />
    <xs:attribute name="ColName2" type="xs:string" use="optional" />
    <xs:attribute name="Commas" type="TRUEFALSE" />
    <xs:attribute name="Customization" type="xs:string" />
    <xs:attribute name="DefaultListField" type="TRUEFALSE" />
    <xs:attribute name="Decimals" type="xs:int" />
    <xs:attribute name="Description" type="xs:string" />
    <xs:attribute name="Dir" type="xs:string" />
    <xs:attribute name="Direction" type="xs:string" />
    <xs:attribute name="DisplayImage" type="xs:string" />
    <xs:attribute name="DisplayName" type="xs:string" />
    <xs:attribute name="DisplayNameSrcField" type="xs:string" />
    <xs:attribute name="DisplaySize" type="xs:int" />
    <xs:attribute name="Div" type="xs:string" />
    <xs:attribute name="ListItemMenu" type="TRUEFALSE" />
    <xs:attribute name="ListItemMenuAllowed" type="ListItemMenuType" />
    <xs:attribute name="EnableLookup" type="TRUEFALSE" />
    <xs:attribute name="EnforceUniqueValues" type="TRUEFALSE" />
    <xs:attribute name="ExceptionImage" type="xs:string" />
    <xs:attribute name="FieldRef" type="xs:string" />
    <xs:attribute name="FillInChoice" type="TRUEFALSE" />
    <xs:attribute name="Filterable" type="TRUEFALSE" />
    <xs:attribute name="FilterableNoRecurrence" type="TRUEFALSE" />
    <xs:attribute name="ForcedDisplay" type="xs:string" />
    <xs:attribute name="ForcePromoteDemote" type="TRUEFALSE" />
    <xs:attribute name="Format" type="DisplayFormat" />
    <xs:attribute name="FromBaseType" type="TRUEFALSE" />
    <xs:attribute name="HeaderImage" type="xs:string" />
    <xs:attribute name="Height" type="xs:int" />
    <xs:attribute name="Hidden" type="TRUEFALSE" />
    <xs:attribute name="HTMLEncode" type="xs:string" />
    <xs:attribute name="Id" type="xs:string" />
    <xs:attribute name="IMEMode" type="IMEMode" />
    <xs:attribute name="Indexed" type="TRUEFALSE" />
    <xs:attribute name="IsolateStyles" type="TRUEFALSE" />
    <xs:attribute name="IsRelationship" type="TRUEFALSE" />
    <xs:attribute name="JoinColName" type="xs:string" />
    <xs:attribute name="JoinType" type="JoinType" />
    <xs:attribute name="LCID" type="LocalizableString" />
    <xs:attribute name="LinkToItem" type="TRUEFALSE" />
    <xs:attribute name="LinkToItemAllowed" type="ListItemMenuType" />
    <xs:attribute name="List" type="xs:string" />
    <xs:attribute name="Max" type="xs:float" />
    <xs:attribute name="Min" type="xs:string" />
    <xs:attribute name="Mult" type="TRUEFALSE" />
    <xs:attribute name="Name" type="xs:string" use="required"/>
    <xs:attribute name="NegativeFormat" type="xs:string" />
    <xs:attribute name="Node" type="xs:string" />
    <xs:attribute name="NoEditFormBreak" type="TRUEFALSE" />
    <xs:attribute name="NumLines" type="xs:int" />
    <xs:attribute name="Percentage" type="TRUEFALSE" />
    <xs:attribute name="PIAttribute" type="xs:string" />
    <xs:attribute name="PITarget" type="xs:string" />
    <xs:attribute name="PrimaryPIAttribute" type="xs:string" />
    <xs:attribute name="PrimaryPITarget" type="xs:string" />
    <xs:attribute name="Presence" type="TRUEFALSE" />
    <xs:attribute name="PrimaryKey" type="TRUEFALSE" />
    <xs:attribute name="ReadOnly" type="TRUEFALSE" />
    <xs:attribute name="ReadOnlyEnforced" type="TRUEFALSE" />
    <xs:attribute name="RelationshipDeleteBehavior" type="DeleteBehaviorType" />
    <xs:attribute name="RenderXMLUsingPattern" type="TRUEFALSE" />
    <xs:attribute name="Required" type="TRUEFALSE" />
    <xs:attribute name="RestrictedMode" type="TRUEFALSE" />
    <xs:attribute name="ResultType" type="xs:string" />
    <xs:attribute name="RichTextMode" type="xs:string" />
    <xs:attribute name="RichText" type="TRUEFALSE" />
    <xs:attribute name="Sealed" type="TRUEFALSE" />
    <xs:attribute name="SeperateLine" type="TRUEFALSE" />
    <xs:attribute name="SetAs" type="xs:string" />
    <xs:attribute name="ShowAddressBookButton" type="TRUEFALSE" />
    <xs:attribute name="ShowAlways" type="TRUEFALSE" />
    <xs:attribute name="ShowField" type="xs:string" />
    <xs:attribute name="ShowInDisplayForm" type="TRUEFALSE" />
    <xs:attribute name="ShowInEditForm" type="TRUEFALSE" />
    <xs:attribute name="ShowInFileDlg" type="TRUEFALSE" />
    <xs:attribute name="ShowInListSettings" type="TRUEFALSE" />
    <xs:attribute name="ShowInNewForm" type="TRUEFALSE" />
    <xs:attribute name="ShowInViewForms" type="TRUEFALSE" />
    <xs:attribute name="Sortable" type="TRUEFALSE" />
    <xs:attribute name="StorageTZ" type="xs:string" />
    <xs:attribute name="StripWS" type="xs:string" />
    <xs:attribute name="SuppressNameDisplay" type="TRUEFALSE" />
    <xs:attribute name="TextOnly" type="TRUEFALSE" />
    <xs:attribute name="Title" type="xs:string" />
    <xs:attribute name="Type" type="xs:string" use="required" />
    <xs:attribute name="UniqueId" type="xs:string" />
    <xs:attribute name="UnlimitedLengthInDocumentLibrary" type="TRUEFALSE" />
    <xs:attribute name="URLEncode" type="TRUEFALSE" />
    <xs:attribute name="URLEncodeAsURL" type="TRUEFALSE" />
    <xs:attribute name="Version" type="xs:string" use="optional" />
    <xs:attribute name="Viewable" type="TRUEFALSE" />
    <xs:attribute name="WikiLinking" type="TRUEFALSE" />
    <xs:attribute name="Width" type="xs:int" />
    <xs:attribute name="WebId" type="UniqueIdentifier" use="optional" />
    <xs:attribute name="XName" type="xs:string" />
    <xs:attribute name="ID" type="UniqueIdentifier" />
    <xs:attribute name="Group" type="xs:string" />
    <xs:attribute name="MaxLength" type="xs:int" />
    <xs:attribute name="SourceID" type="xs:string" />
    <xs:attribute name="StaticName" type="xs:string" />
    <xs:attribute name="JoinRowOrdinal" type="xs:int" />
    <xs:attribute name="RowOrdinal" type="xs:int" />
    <xs:attribute name="ShowInVersionHistory" type="TRUEFALSE" />
    <xs:attribute name="PrependId" type="TRUEFALSE" />
    <xs:attribute name="DisplaceOnUpgrade" type="TRUEFALSE" />
    <xs:attribute name="UserSelectionMode" type="xs:string" />
    <xs:attribute name="UserSelectionScope" type="xs:int" />
    <xs:anyAttribute namespace="##other" processContents="lax" />   
  </xs:complexType>


If you use default namespace for your custom property and its name differs from properties defined in this file, you'll get validation error because of this attribute namespace="##other" of xs:anyAttribute element.
You can change schema to pass validation. Just remove an attrubute namespace="##other" and you can create fields with any properties with default namespace.

среда, 15 февраля 2012 г.

REST with WCF and Entity Framework with JSON Serialization


Problem: you have some database and use Entity Framework to get data from it. You need develop web-service which returns JSON-data based on your entities. And as a consumer of this service you need to use javascript client. Also, you should provide cross-domain access to your web-service.

First, lets define configuration for this service:
<bindings>
  <webHttpBinding>
    <binding name="crossDomain" crossDomainScriptAccessEnabled="true" />
  </webHttpBinding>
</bindings>
<services>
  <service name="[Name of your service]">
    <endpoint address="" behaviorConfiguration="restBehavior" binding="webHttpBinding" bindingConfiguration="crossDomain" contract="[Name of your contract]">
      <identity>
        <dns value="localhost" />
      </identity>
    </endpoint>
  </service>
</services>
<behaviors>
  <endpointBehaviors>
    <behavior name="restBehavior">
      <webHttp />
    </behavior>
  </endpointBehaviors>
</behaviors>

Parameter crossDomainScriptAccessEnabled="true" allows requests from another domain. If you need to use SSL, just add security tag to the binding tag with mode="Transport":
<binding name="crossDomain" crossDomainScriptAccessEnabled="true">
  <security mode="Transport" />
</binding>

Okey, now we should define service contract. To get JSON-data from your web-methods you should add WebGet(ResponseFormat = WebMessageFormat.Json)  attribute to each web-method:
[ServiceContract]
public interface IService1
{
    [
OperationContract]
    [
WebGet(ResponseFormat = WebMessageFormat.Json)]
    
string GetOrder(Int32 id);
}

Now, we can request our service something like this, I use jquery:
$.getJSON('/GetOrder?id=7&callback=?'function (data) {
...
});

Great, but when we try to return entity we get this error:
The type 'xxx' cannot be serialized to JSON because its IsReference setting is 'True'.

Entity Framework doesn't support JSON-serialization, so I found this workaround:
public string GetOrder(Int32 id)
{
    
// getting order…
    return SerializeJSON(order);
}

static string SerializeJSON<T>(T obj)
{
    JavaScriptSerializer serializer = 
new JavaScriptSerializer();
    
return serializer.Serialize(obj);
}