Ajax Search Framework

From VYRE

Jump to: navigation, search

The Ajax Search Framework is built on top of VYRE Unify's content and user gateways. The framework allows you to control how to search content repositories and user realms.

Ajax Search Framework
type: Javascript Framework
module: Publishing module

Contents

[edit] Getting Started

The ajax framework will be inluded in VYRE Unify 4.3.1.7 and above.

This section will go through the basics of setting up the framework and the page for use

[edit] Including the Javascript files

The framework has been coded in javascript, in order to perform searches we need to import these scripts onto the page for use.

<!--The main Search framework-->
 
 <script type="text/javascript" src="/javascript/pub_module/search_jax.js"></script>
 
 <!--JSON utility-->
 <script type="text/javascript" src="/javascript/pub_module/JSONstring.js"></script>  
 
 <!--If you are searching Unify's user realms include this file-->
 <script type="text/javascript" src="/javascript/pub_module/user_jax.js"></script>
 
 <!--If you are searching Unifys data-stores and file-stores include this file-->
 <script type="text/javascript" src="/javascript/pub_module/content_jax.js"></script>

[edit] CSS

Some of the frameworks rendering is dependant on CSS styling, you will need to import the appropriate CSS so that pagination and plugins are rendered appropriately.

[edit] HTML Containers

Inorder for the framework to render results and other widgets and controls on the page, the framework needs to have certain containers setup. When an instance of a search is initialised and executed the framework will look for these containers to update them with the search. The following containers need to be declared on the page

Loader
The loader container will be displayed when the search is waiting for a response from the server. This container usually contains some funky loading gif.
Sortbar
The framework supports extensions known as plugins, these are self contained widgets that control the state of the search. The sortbar container will be used as the default container for these plugins to be appended to.
Pagination(top)
The framework generates pagination automatically, the framework can render pagination on up to two locations, usually above and below the search results. This container will be used to render the pagination at the top.
searchResults
Search responses from the server come in the form of transformed xml using XSL, the resultant XHTML is rendered in this container.
Pagination(bottom)
This container will render pagination below the search results.
<div id ="loadingDiv" valign="middle" style="display:none"></div>
 <div id = "sortBar"></div>
 <div id = "paginationContainer" class = "ajaxPagination"></div>
 <div id ="searchResults"></div>
 <div id = "paginationContainer2" class = "ajaxPagination"></div>
 
 <!--In the xsl to facilitate pagination-->
 <input type = "hidden" id = "countValue" value = "{search-results/size}"/>

Remember to add a hidden input into the xsl you use to render the results, this is so that framework knows how many total results there are for a request to generate the pagination.

[edit] Basic Search

In this chapter we will discuss how to setup a basic search using the framework. Make sure you have followed the steps in chapter 2.

[edit] Logging

A logging module has been implemented for the framework, and it used throughout the code to log warnings,errors and information. The default logging mechanism uses Firebug's console object to print debugging messages.

The logging is disabled by default, but you can enable it with the following code.

VYRE.Utils.Logger.setLogging(true);


The logger allows different modes of logging;


log
VYRE.Utils.Logger.log("Logging something...");
info
VYRE.Utils.Logger.info("LSome information");
warn
VYRE.Utils.Logger.warn("Something is not right");
error
VYRE.Utils.Logger.error("Woops.. Something broke:");


[edit] VYRE.AjaxSearch Constructor

The search framework is centered around one main class VYRE.AjaxSearch , we need to instantiate this class with settings that the framework will rely upon.

var ajs = new VYRE.AjaxSearch({
   searchType:'content',
   gatewayId: 1,
   pageId : $page_id,
   xslId :1,
   storeId:"1",
   displayItemId:null,
   realmId:'initial',
   contextPath:'$context_path',
   loadContentAndMetadata : true,
   loadLinkedItems : true, 
   loadLinkedContent: true,
   loadLinkedUsers : true,
   loadLocalizations : true,
   showTaxonomyTree : true
 });

The constructor takes in an object which contains a varying amount of properties. Below are explanations of what the properties mean.

searchType
This property will tell the framework to either search for 'content' or for 'user'
gatewayId
The gatewayId which allows you to view specific content or user realms, make sure you have created this before configuring. This can be done in publishing -> gateways
pageId
Inorder for the XSL to know information about the current path we need to pass this when we make the request. Use the velocity variable $current_path to do this.
xslId
This the id of the XSL file that will be used to transform the search result.
storeId
specifies the storeId(s) of the store(s) that wil be search upon. If you are searching more than one store make sure you delimit the stores with a comma. e.g. "1,3,5"
displayItemId
Inorder for the XSL to reference the current item id on an item display page we need to pass this along with the ajax request. We can do this using the velocity variable $current_item_id
contextPath
In order for the XSL to know the context path, we need to pass this along with the ajax request. We can do this by using the velocity variable $context_path

[edit] Performing the Search

Once all the containers and object instantiation has been done we can invoke the search, which will send a request to the server, the response will then be rendered in the searchResults container.

[Note]

Note

The default saved seach is set to active:true and the results per page is defaulted to 10.


ajs.search();  // performs search
 ajs.sortedSearch(); //defaults to page 1 of the result set and then searches


[edit] Sorting

The framework allows the ability to change how the results are sorted, we need to pass the field that the results will be sorted by. The results will then be sorted by lucene serverside before we recieve the response.

ajs.setSortField("creationDate");

The above example will sort the results by the creationDate, the main sort fields we can use are as follows.


  • creationDate (sort by when items were first created)
  • lastModDate (sort by when items were first modified)
  • viewCount (sort by views)
  • score (sort by relevance)
  • name (sort by the name field)


VYRE Unify allows the ability to make custom attributes sortable, we can pass these as sort fields to the framework using the following format


  • sort_att<attributeId>
  • sort_att<attributeId>_l (values will be sorted by integer)
  • sort_att<attributeId>_D (values will be sorted by double)


[Note]

Note

the default sort field is name


The framework ships with plugins/widgets that can manipulate sorting using more user friendly interaction patterns. VYRE.Plugins.SortPlugin, VYRE.Plugins.SortSelect, VYRE.Plugins.TablerSorter are plugins which manipulate the sort field and sort direction of the search. These plugins will be explained later.

[edit] Sort Direction

As well as the ability to specify a sort field to sort by, we can also specify the sort direction.

ajs.setDescending(true);// results will be sorted descending


[Note]

Note

The default sort direction is ascending


[edit] Results Per Page

We can specify how many items we want displayed per page using the framework, the pagination will be rendered automatically based on this value.

ajs.setResultsPerPage(50);// 50 results per page
 ajs.setResultsPerPage(-1);//all items will be returned on a single page

The plugin used to make changing the results per page more user friendly is the VYRE.Plugins.CountPlugin.

[edit] Advanced Search

This chapter will explain further functionality that can be used when configuring an instance of the search.


[edit] Basic Search

To get a basic search input, with search and reset buttons add the following code

At the top

<input type="text" id="ajaxSearchInput"/>
<input type="button" id="ajaxSearchButton" value="Search"/>
<input type="button" id="ajaxResetButton" value="Reset"/>

In the javascript

ajs.registerSubmit("ajaxSearchButton");
ajs.registerReset("ajaxResetButton");
ajs.addPlugin(new VYRE.Plugins.FilterPlugin(new FilterPluginField("ajaxSearchInput", "","text"),true));

[edit] Saved Searches

The content and user gateways allow saved searches to be sent as part of the request to filter down result sets. To set the base saved search we use the setSavedSearch() method.

ajs.setSavedSearch("active:true"); // bring back all active items
 ajs.setSavedSearch("att34:1 AND ITEM_LINK_DEF12($current_item_id)");// all items linked through link def 12 with the attribute34 being true.

[Note]

Note

You will need to convert your saved searches to use "AND/OR/NOT" syntax instead of "+ -"

[edit] Cookie Managment

The framework allows the ability to save the state of a search in a cookie for when the page is next loaded. This includes management of plugin state through MVC architecture. The cookie's information is stored by serialising the state of a search to a string using JSON(Javascript Object Notation) .

// This turns on the cookie functionality, and sets the key/name for the cookie to be stored
 ajs.setCookieName("ajsSearch");
 
 // Reads a partial state of the stored search. It persists all information which DOES NOT filter the resultset down.
 ajs.setLightweightCookie(true);
 
 //Reads the entire state of the search which includes information that can filter the resultset.
 ajs.setLightweightCookie(false);
 
 // Sets the expiration(in days) of the cookie. The cookie is a session cookie by default.
 ajs.setCookieExpiration(2);
 
 // The actual operation of reading and persisting data from a cookie
 ajs.readCookie();


[Note]

Note

When developing Ajax Search Result pages becareful when using cookies, if you are making many changes to the configuration of a search the changes may not persist immediately if you have cookies enabled.

[edit] Registering Taxonomies

The framework can be used in conjuction with taxonomies rendered on the page. It relies on the use of the Advanced Search portlet. Using taxonomies with the framework requires a few steps.


  1. Add an Advanced search portlet to the page and configure the taxonomy.
  2. Make sure that the option to perform a search on selecting a node is unchecked as this will reload the page uneccesseryily.
  3. Find out the portlet namespace for the Advanced search portlet you used, this will be in the format of p<portletId>I (e.g. p765I)
  4. ajs.registerTaxonomy("p765I");//Registers the taxonomy located in portlet p765I
VYRE.Utils.clearFormAction("p765I"); //nullifies the form action for the advanced search portlet(p765I) so form submits don't occur.


The state of the taxonomy works with the cookie functionality, however only non lightweight cookies will persist the state of a taxonomy as this filters down the result set.


[Note]

Note

The taxonomies ability to automatically remove categories which bring back 0 results cannot be used in conjuction with the framework as this is a serverside operation requiring a form submittion.

[edit] Submit and Reset Buttons

The framework allows the registration of submit and reset buttons, this works by passing the id of the button to a method in the framework.

<input type = "button" value = "Search" id = "ajaxSearchButton"/>
 
 <input type = "button" value = "Reset" id = "ajaxResetButton"/>
ajs.registerSubmit("ajaxSearchButton");
 ajs.registerReset("ajaxResetButton");


[Note]

Note

The html elements need to be rendered before the javascript is registered, and you are able to register as many buttons as you like to submit or reset.


[edit] Lucene Attributes

The framework takes advantage of the key:value nature of lucene by storing attribute objects in a collection which then are appended to the saved search inorder to filter results.

E.g. if we had a base saved search of "active:true" and we wanted filter the results further to the following "active:true and creator:$current_user" we would do the following

ajs.setSavedSearch("active:true");// sets the base saved search
 ajs.attributes.set(new VYRE.Attribute("creator","$current_user"));//sets the attribute for the creator

By having attributes in a collection we have much better control on the key value pairings meaning we change the value of an attribute without some long winded string manipulation. The framework makes extensive use of the collection class VYRE.Utils.ArrayList which contains critical methods that the default Array implementation doesn't.


  1. add(someObject); //adds object to collection
  2. get(index); //gets object at specified index
  3. set(someObject); // sets the object(if it exists, using implemented equals method) otherwise it adds it
  4. size(); //returns size of the collection
  5. remove(object); // removes the object(relies on implement equals method)
  6. contains(object) // return whether the object exists(relies on implemented equals method)
  7. each(function (item,index){}); //invokes a specified function over the entire collection passing in the object and index.
  8. fromArray() and toArray() ..allows the collection to be set and get to an Array object.

[edit] Pagination

The framework should generate the pagination if the correct containers are present. There are some properties of the pagination that can be changed to tweak the presentation of the pagination. This is so the framework can cater for varying clients needs.


[edit] Range

This attribute can be changed to specify how broad the pagination is, the bigger the number the more page links will show.

ajs.paginationElements.range = 3;// 1-5 is most common ranges, defaulted = 3;


[edit] Rendering Criteria

The boolean property "alwaysRender" is used when you want to control if the pagination should render if there is only 1 page or there are no results.

ajs.paginationElements.alwaysRender = false; // defaulted to true

[edit] Copy Text

The framework alows the ability to change the text for the divider, next and previous links. There is also the ability to use images rather than text, this was introduced for certain clients.

//text examples
 ajs.paginationElements.nextLink = "Next";
 ajs.paginationElements.divider = "----";
 ajs.paginationElements.previousLink = "Previous";
 
 //Image examples
 ajs.paginationElements.nextLink = VYRE.Utils.makeImage("/other_files/general/pag-next.gif");
 ajs.paginationElements.divider = VYRE.Utils.makeImage("/other_files/general/divider.gif");
 ajs.paginationElements.previousLink = VYRE.Utils.makeImage("/other_files/general/pag-previous.gif");


[edit] Callback functions

since each ajax request takes an unknown amount of time to be performed, there needs to be a mechanism to trigger callback functions so that frontend programmers can invoke code after the response has been recieved rather than during. There is a VYRE.Utils.ArrayList collection which stores these functions.

function hello() {
    alert("hello");
 }
 
 ajs.callBackFunctions.add(hello); // add call back using function reference;
 // add call back using anonymous function
 ajs.callBackFunctions.add(function () {
   alert("Anonymous hello");
 });

[edit] Container Namespace

The framework has a feature which makes it easier to help manage containers when you have several instances of the search framework on a single page. This functionality now means you dont need to come up with different ids for containers in different instances of the search. Instead what used to be Ids are now treated as class names, and the only thing that needs to be changed is the containing Id.

<div id = "containerNamespace1">
 
   <div class ="loadingDiv" valign="middle" style="display:none"></div>
   <div class = "sortBar"></div>
   <div class = "paginationContainer" class = "ajaxPagination"></div>
   <div class ="searchResults"></div>
   <div class = "paginationContainer2" class = "ajaxPagination"></div>
 
 </div>
 
 
 <!-- in the xsl -->
 <input type = "hidden" value = "{search-results/size}" class = "countValue"/>
ajs.setContainerNamespace("containerNamespace1");

notice how the id attribute have now been changed to a class attribute, and the containers are now contained in a single div with a unique id which will be set as the containerNamespace in the search framework. Remember to also modify the xsl to use a class for the countValue hidden input.

[edit] Plugins

The framework has a plugin architecture which allows custom widgets/plugins which can manipulate and react to the state of the ajax search instance to be added to the page. The architecture is based on MVC(model-view-controller) and decoupled from the main framework, this is to allow new plugins to be added for varying client needs without changing the sourcecode of the other classes.


[edit] Count Plugin

The count plugin is added so the user can change how many results per page they want. this a visual plugin that gets appended to the sortbar container.

ajs.addPlugin(new VYRE.Plugins.CountPlugin([5,15,30,45],'Items per page: ',0));

The CountPlugin is added to the frameworks collection of plugins, it will obtain the html from the getHTML() method of the plugin and then append it to the container. The parameters that the plugin needed are;


  • [5,15,30,45] - Array of user specifible counts.
  • 'Items per page:' - label for the plugin
  • 0 - default index for the count, (e.g. 0 = 5 items per page, 2 = 30 items per page)


[edit] SortPlugin

The sort plugin is used to allow users to control the sort field and sort direction through a visual widget.

ajs.addPlugin(new VYRE.Plugins.SortPlugin([false,true,false,true],
                                            ["lastModDate","lastModDate","name","name"],
                                            1));

The sort plugin accepts 3 arguments which are;


  • [false,true,false,true] - Array of sort directions(4)
  • ["lastModDate","lastModDate","name","name"] -Array of sort fields(4)
  • 1 -Index of default selection (index :0-3)


[edit] TablerSorter Plugin

The table sorter plugin allows users to sort by table headers. This plugin needs to be registered as an "invokeLaterPlugin", this is because the plugin needs to attach event handlers to the headers everytime the results have been rendered. All the other plugins attach their event handlers only once during the initial setup stage.

ajs.addInvokeLaterPlugin(new VYRE.Plugins.TableSorter([new Header(0,"name"),new Header(2,"lastModDate")],"ajaxTable"));

We use the addInvokerLaterPlugin() method as this is a different type of plugin. Here are the auguments that the TablerSorter object accept.


  • [new Header(0,"name"),new Header(2,"lastModDate")] - Array of headers, which are composed of an header index and the sortField the column is sorted by.
  • "ajaxTable" - this is the id (or class if using containerNamespace) of the table in the xsl.


When this plugin is initialised, all sortable headers are given the className of "sortable", if a header is in its down state the className is "down" and if in an up state the classname is "up". You will need to use css to highlight the different states.

A new feature added recently was the ability to specify the default sort direction of a header. E.g. you might want a header based on creationDate to be sorted descending when its first clicked, you can do this by providing an additional boolean parameter when creating a new Header object.

new Header(3,"creationDate",true);//when this is header is first clicked the sort direction is descending.

[edit] ListPlugin

This plugin allows users to switch the XSL file which is used to render the results on the fly using a visual widget. This is useful for asset pages where it is nice to give the option for either a grid view or a list view.

ajs.addPlugin(new VYRE.Plugins.ListPlugin([new View("Grid",408),new View("List",497)],1));

The auguments needed for the ListPlugin are.


  • [new View("Grid",408),new View("List",497)] - An Array of View objects which are composed of a label, and xslId.
  • 1 - Index of which which view is selected on initial load.


[edit] FilterPlugin

The filter plugin is used for most of the filtering interactions in the framework and manages lucene key:value pairings using the attributes Collection located in the VYRE.AjaxSearch class. This plugins works in a different way to the other plugins, no HTML is returned by this plugin instead form elements on the page and registered as a FilterPluginField which then is binded to a lucene key:value pairng. Usually an advanced search portlet is used to produce the relevant dropdowns as it can retrieve the presentation rules and list the linked items for us.

ajs.addPlugin(new VYRE.Plugins.FilterPlugin(new FilterPluginField("SEARCH_ATTRIBUTE_19", "att19","select"),true));

The auguments that the FilterPlugin accept are;


  • FilterPluginField - "SEARCH_ATTRIBUTE_19" refers to id of the form element, "att19" is the lucene attribute the plugin is binded too, "select" is the form input type ("text" and "radio" are the others)
  • The additional augument is a boolean which signifies if a search should be invoked on a click or enter key down event.


Below are some additional examples which show uses of attributes, linked items and linked users in an advanced search portlet.

//filterplugin based on an attribute with an id of 15
 ajs.addPlugin(new VYRE.Plugins.FilterPlugin(new FilterPluginField("SEARCH_ATTRIBUTE_15", "att15","text"),true));
 
 //filterplugin based on item link definition 56
 ajs.addPlugin(new VYRE.Plugins.FilterPlugin(new FilterPluginField("link_56", "ITEM_LINK_DEF56", "select"),true));
 
 //filterplugin based on user link definition 4
 ajs.addPlugin(new VYRE.Plugins.FilterPlugin(new FilterPluginField("ulink_4", "USER_LINK4","select"),true));


[Note]

Note

Remember that when using the advanced search portlet you will need to clear the form action using the VYRE.Utils.clearFormAction("p<portletId>I") method.


[edit] Primary Filter

The primary filter is an instance of a filterplugin but the VYRE.AjaxSearch class holds a reference to this so it can expose it to the url parameter functionality. This is usually applied to the general/keyword search so the framework can except get strings in the url and directly map it to this particular plugin.

ajs.setPrimaryFilter(new VYRE.Plugins.FilterPlugin(new FilterPluginField("ajaxSearchInput", "","text"),true));

The example above sets the primary filter for an instance of the search framework. Notice that in the plugin field the lucene attribute is bound to "" which means that this will be a general search which searches the entire item for matches. Now if the url gets appended with the get request of "?primary=test" the keyword test will be bound to this plugin and automatically filled.

[edit] SortSelect Plugin

This plugin is used to control the sort field and sort direction, it is used as an alternative the SortPlugin as it allows more sort fields to be displayed in the space via a drop down box.

ajs.addPlugin(new VYRE.Plugins.SortSelect([new SortableAttribute("name","Name"),new SortableAttribute("creationDate","Creation Date"),new SortableAttribute("lastModDate","Last Modification Date")],true));

The SortSelect plugin takes in the following arguments.


  • Array of SortableAttibutes, which are composed of a sortField and a label.
  • Boolean to indicate whether a search should be invoked upon onchange of the selectbox.


[edit] DateRange Plugin

The data range plugin is used to search between 2 date ranges, it works by registering events with the 2 input boxes generated in an Advanced Search portlet with lowerbound and upperbound dates.

ajs.addPlugin(new VYRE.Plugins.DateRange(new DateRangeField("att20","ITEM_ATT20low_DATE","ITEM_ATT20high_DATE"),true,true/*isUSDate*/));

The DateRange plugin accepts the following arguments.


  • A DateRangeField which consists of the lucene attribute the plugin is binded to, the id of the lower bound input box and the id of the upperbound inputbox.
  • A Boolean to indicate if a search should be invoked when a both dates have been chosen for lowerbound/upperbound input boxes.
  • An optional boolean to indicate that the date is in US format.

[edit] Connector Plugin

The connector plugin is used to change the connector (AND/OR) for lucene search based on taxonomy.

ajs.addPlugin(new VYRE.Plugins.ConnectorPlugin([new Connector("And","AND"), new Connector("Or","OR")],"Connector: ",0));

The auguments whihc the ConnectorPlugin need are.


  • Array of Connector objects which are composed of an operator ("AND"/ "OR") and a label.
  • A Label to be displayed next to the controls for the widget
  • An index of which connector is selected by default.


[edit] User Search Example

The framework also supports requests to be made to the user gateway. There are however a few differences when using the framework for the user gateway.


[edit] VYRE.AjaxSearch Constructor

The object passed to the constructor is slightly different to that of the content gateway examples.

var ajs = new VYRE.AjaxSearch({
   searchType:'user',
   gatewayId: 1,
   pageId : $page_id,
   xslId :1,
   storeIds:null,
   displayItemId:null,
   realmId:'initial',
   contextPath:'$context_path',
   loadLinkedItems : true,
   loadLinkedContentAndMetadata : true
 });
searchType
This is set to 'user', so the framework knows to generate references to objects which handle requests to the user gateway.
gatewayId
The gateway id now needs to be an id of a user gateway rather than a content gateway.
realmId
The realmId of the user realm you want to search goes here. The default realm has an id of 'initial'
XML Loading
The user gateway has less options to load xml than the content gateway, these are 'loadLinkedItems', 'loadLinkedContent', 'loadLinkedUsers', 'loadLocalizations' and 'loadLinkedContentAndMetadata'


After this has been setup, most of the other mechanisms in the framework should work.

[edit] Debugging

This section will briefly explain how to debug the framework.


[edit] Firebug

I would advise anyone building instances of framework to have firebug installed in firefox. When debugging is turned on, plenty of information is logged into firebug which can help alot when things go wrong. The following are provided in the logs


  • Response time - how long a search takes place in MS
  • Container checks, the framework will tell you if you are missing any containers.
  • Some of the settings in the API will log details when they are used, setResultCount() is an example
  • Lucene query, the query which is sent to the server is always logged, this includeds attributes appended to the base saved search. Always check this when you think the savedsearch is incorrect.
  • Taxonomy, taxonomy categories and selection information will be provided in the logging. There are also error logs for when certain Unify methods haven't been overriden properly.

[edit] Example Logging output

[13:03:32] setting sort range startIndex= 5, endIndex= 9, currentPage= 1s32.js (line 1768)
 [13:03:32] Searching....s32.js (line 1768)
 [13:03:32] Taxonomy String: s32.js (line 1768)
 [13:03:32] LuceneQuery = +active:true AND (att295:active)s32.js (line 1768)
 [13:03:33] Ajax Request duration: 546ms
 [13:03:33] recieved results from server...s32.js (line 1768)
 [13:03:33] Tablesort table cannot be found!s32.js (line 1768)
 [13:03:33] Taxonomy String: s32.js (line 1768)
 [13:03:33] Setting Sort Field: lastModDates32.js (line 1768)
 [13:03:33] Setting descending: trues32.js (line 1768)
 [13:03:33] Setting Result Count: 5s32.js (line 1768)
 [13:03:33] Setting xslId: 103s32.js (line 1768)
 [13:03:33] Setting taxonomy connector: ANDs32.js (line 1768)
 [13:03:33] Taxonomy String: s32.js (line 1768)
 [13:03:33] writing Cookie...


[edit] Visual Studio Express( IE Debugging)

TODO

[edit] Custom Crossbrowser Logging Module

There is currently a prototype for a custom crossbrowser debugging module, instructions will appear here.

Personal tools