Want to see a live demo?
Check it out here!
Vendor
MindTouch
Type Script
Categories Search
Requires MindTouch Core 1.8.3 or later
Status Prototype
License Free/Open Source
Manifest http://scripts.mindtouch.com/advancedsearch.xml

 

Install Script
To add  this script to your site, enter the address of your MindTouch installation (ex: http://www.mindtouch.com) and click the Add Script button.  This will open your control panel and prepopulate the necessary values.  You will still need to manually add configuration settings if required.  Note that no changes are made to your site until you confirm the action in your control panel.
Your site address:     


Table of Contents

Description

This extension contains functions for adding advanced search functionality.

See also How to add a script, Using the Extension Dialog, Learn about DekiScript, Extensions Directory.


Functions

advancedSearch.form(request : map, field : str, path : str, publish : str, useStyles : bool) : xml

Embeds an advanced search form.

Parameters:

NameTypeDescription
requestmap(optional) Instance of the __request.args map (default: nil)
fieldstr(optional) Search query field name. (default: "search")
pathstr(optional) Sets the value of the "located under" field. (default: nil)
publishstr(optional) Publish on channel or a URI. (default: "")
useStylesbool(optional) If set to "false" then no styles are inlined allowing custom styles. (default: "true")


advancedSearch.results(results : list) : xml

Displays search results from the wiki.getSearch function. More functionality is enabled if inlined on a page.

Parameters:

NameTypeDescription
resultslistSearch result map object returned from wiki.getSearch


Script Source

<extension>
	<title>Advanced Search Extension</title>
	<label>Advanced Search</label>
	<description>This extension contains functions for adding advanced search functionality.</description>
	<uri.help>http://developer.mindtouch.com/App_Catalog/Advanced_Search</uri.help>
	<namespace>advancedSearch</namespace>
	
	<function>
		<name>form</name>
		<description>Embeds an advanced search form.</description>
		<param name="request" type="map" optional="true">Instance of the __request.args map (default: nil)</param>
		<param name="field" type="str" optional="true">Search query field name. (default: "search")</param>
		<param name="path" type="str" optional="true">Sets the value of the "located under" field. (default: nil)</param>
		<param name="publish" type="str" optional="true">Publish on channel or a URI. (default: "")</param>
		<param name="useStyles" type="bool" optional="true">If set to "false" then no styles are inlined allowing custom styles. (default: "true")</param>
		<return>
			<html xmlns:eval="http://mindtouch.com/2007/dekiscript">
				<head>
					<script type="text/javascript" src="/skins/common/yui/yahoo-dom-event/yahoo-dom-event.js"></script>
					<script type="text/javascript" src="/skins/common/yui/connection/connection.js"></script>
					<script type="text/javascript" src="/skins/common/yui/autocomplete/autocomplete.js"></script>

					<script type="text/javascript">
						var GS = GS ? GS : [];
						GS.buildLuceneQuery = function()
						{
							// build the lucene search query
							// hack hack hack: prefix, suffix
							var prefix = '', suffix = '', val = '';
							var rSplit = /\s|%20/;

							// all words
							val = GS.sanitize(this.all.value);
							if (val)
							{
								prefix += val;
							}

							// any words
							var any = '';
							val = GS.sanitize(this.any1.value);
							if (val != '')
							{
								any += ' OR "' + val + '"';
							}
							val = GS.sanitize(this.any2.value);
							if (val != '')
							{
								any += ' OR "' + val + '"';
							}
							val = GS.sanitize(this.any3.value);
							if (val != '')
							{
								any += ' OR "' + val + '"';
							}
							if (any != '')
							{
								prefix += " " + any.substring(4);
							}

							// tags
							val = GS.sanitize(this.tags.value);
							if (val != '')
							{
								var aTags = String(val).split(rSplit);
								for (var i = 0, i_m = aTags.length; i &lt; i_m; i++)
								{
									suffix += ' tag:"' + aTags[i] + '"';
								}
							}

							// not
							val = GS.sanitize(this.not.value);
							if (val != '')
							{
								// split on spaces
								var aWords = String(val).split(rSplit);
								for (var i = 0, i_m = aWords.length; i &lt; i_m; i++)
								{
									prefix += ' -"' + GS.sanitize(aWords[i]) + '"';
								}
							}
							
							// path
							val = this.path ? GS.sanitize(this.path.value) : '';
							if (val != '')
							{
								val += (val[val.length-1] != '/') ? '/*' : '*';
								suffix += ' path:' + val;
							}

							// author
							val = GS.sanitize(this.author.value);
							if (val != '')
							{
								suffix += ' author:"' + val + '"';
							}

							// type
							val = GS.sanitize(this.type.value);
							if (val != '')
							{
								suffix += ' type:"' + val + '"';
							}
							
							// set the form's search field
							var fieldName = this.getAttribute('field') || 'search';
							this.elements[fieldName].value = jQuery.trim(prefix + suffix);
							// hack hack hack: prefix, suffix
							this.elements['prefix'].value = jQuery.trim(prefix);
							this.elements['suffix'].value = jQuery.trim(suffix);

							return !GS.publish(this);
						};
						
						// helper method for cleaning up user input
						GS.sanitize = function(s)
						{
							return escape(jQuery.trim(s));
						};

						// publishes form fields on channel defined by publish attribute
						// fields are namespaced by the form name if it exists
						GS.publish = function(o)
						{
							var channel = o.getAttribute('publish') || null;
							if (channel)
							{
								var m = {};
								var namespace = o.name ? o.name + '.' : '';
								DekiWiki.$(o).find('input').each(function()
								{ 
									if (DekiWiki.hasValue(this.name))
									{
										m[namespace + this.name] = this.value;
									}
								});

								DekiWiki.publish(channel, m);
								return true;
							}
							
							return false;
						};

						GS.createAutoComplete = function(elInput, elResults)
						{
							var oDs = new YAHOO.widget.DS_XHR('/Special:Listusers', ['results', 'user']);
							oDs.scriptQueryParam = 'matchuser';
							
							var oAc = new YAHOO.widget.AutoComplete(elInput, elResults, oDs);
							oAc.minQueryLength = 2;
							oAc.animVert = false;
						};
					</script>

					<eval:if test="args.useStyles ?? true">
						<style type="text/css">
						form.gs-advancedSearch table th {
							font-weight: bold;
						}
						form.gs-advancedSearch table td {
							padding-left: 10px;
						}
						form.gs-advancedSearch input.long {
							width: 95%;
						}
						form.gs-advancedSearch input.short,
						form.gs-advancedSearch select.short {
							width: 28%;
						}
						form.gs-advancedSearch div.submit {
							padding: 10px 0;
						}

						form.gs-advancedSearch .autocomplete div.yui-ac-content {
							position: absolute;
							background-color: #fff;
							border: solid 1px #999;
							font-size: 10pt;
						}
						form.gs-advancedSearch .yui-ac-highlight {
							background-color: #EEEEFF;
						}
						#topic form.gs-advancedSearch .autocomplete div.yui-ac-bd ul {
							list-style-type: none;
							padding: 0;
							margin: 0;
						}
						#topic form.gs-advancedSearch .autocomplete div.yui-ac-bd li {
							list-style-type: none;
							padding: 2px;
						}
						</style>
					</eval:if>
				</head>
				<body block="let isuri=string.startswith(args.publish ?? '', 'http://', true) || string.startswith(args.publish ?? '', 'https://', true) || args.publish == _ || args.publish == '';">
					<noscript>Advanced Search requires JavaScript to be enabled.</noscript>

					<form
						method="get"
						class="gs-advancedSearch"
						eval:id="@formId"
						eval:action="if (isuri) { args.publish ?? ''; } else { ''; }"
						eval:field="args.field ?? 'search'"
						eval:publish="if (!isuri) { args.publish ?? ''; } else { ''; }"
						>
						<input type="hidden" eval:name="args.field ?? 'search'" value="" />
						<!-- hack hack hack: prefix, suffix -->
						<input type="hidden" name="prefix" value="" />
						<input type="hidden" name="suffix" value="" />
						<table width="100%">
							<colgroup>
								<col width="200" />
								<col width="" />
							</colgroup>
							<tr>
								<th colspan="2">Find results that have...</th>
							</tr>
							<tr>
								<td>all these words:</td>
								<td><input type="text" class="long" name="all" eval:value="args.request.all ?? ''" /></td>
							</tr>
							<!--
							<tr>
								<td>this exact phrase:</td>
								<td><input type="text" class="long" name="exact" value="" /></td>
							</tr>
							-->
							<tr>
								<td>any of these words:</td>
								<td>
									<input type="text" class="short" name="any1" eval:value="args.request.any1 ?? ''" /> OR
									<input type="text" class="short" name="any2" eval:value="args.request.any2 ?? ''" /> OR
									<input type="text" class="short" name="any3" eval:value="args.request.any3 ?? ''" />
								</td>
							</tr>
							<tr>
								<td>been tagged with:</td>
								<td><input type="text" class="long" name="tags" eval:value="args.request.tags ?? ''" /></td>
							</tr>
						</table>

						<table width="100%">
							<colgroup>
								<col width="200" />
								<col width="" />
							</colgroup>
							<tr>
								<th colspan="2">But also...</th>
							</tr>
							<tr>
								<td>don't have these words:</td>
								<td><input type="text" class="long" name="not" eval:value="args.request.not ?? ''" /></td>
							</tr>
							
							<tr>
								<td>is located under:</td>
								<td><input type="text" class="long" name="path" eval:value="args.request.path ?? args.path ?? ''" /></td>
							</tr>

							<tr>
								<td>was authored by:</td>
								<td class="autocomplete">
									<input eval:id="@acId" type="text" class="short" name="author" eval:value="args.request.author ?? ''" />
									<div eval:id="@acResultsId" class="results"></div>
								</td>
							</tr>
							<tr>
								<td>are only:</td>
								<td>
									<select name="type" class="short">
										<option value="" eval:selected="args.request.type == nil ? 'selected' : nil">any type</option>
										<option value="wiki" eval:selected="args.request.type == 'wiki' ? 'selected' : nil">wiki pages</option>
										<option value="document" eval:selected="args.request.type == 'document' ? 'selected' : nil">documents</option>
										<option value="image" eval:selected="args.request.type == 'image' ? 'selected' : nil">images</option>
									</select>
								</td>
							</tr>
						</table>
						
						<div class="submit">
							<button type="submit">Advanced Search</button>
						</div>
					</form>
					<script type="text/javascript">
						DekiWiki.$(<eval:js>'#' .. @formId</eval:js>).submit(GS.buildLuceneQuery);
						DekiWiki.$(document).ready(function()
						{
							GS.createAutoComplete(<eval:js>@acId</eval:js>, <eval:js>@acResultsId</eval:js>);
						});
					</script>
				</body>
			</html>
		</return>
	</function>

	<function>
		<name>results</name>
		<description>Displays search results from the wiki.getSearch function. More functionality is enabled if inlined on a page.</description>
		<param name="results" type="list">Search result map object returned from wiki.getSearch</param>
		<return>
			<html xmlns:eval="http://mindtouch.com/2007/dekiscript">
				<body>
					<eval:expr>
						/* initialize searchResults with backward compat */
						let searchResults = false;

						/* check if this is inlined in a page */
						let isExtension = __env.__request ? false : true;
						/* sets the maximum number of search results to display when inlining code */
						let numResults = 20;
						/* sets the length of the content preview when inlining code */
						let previewLength = 255;

						if (isExtension)
						{
							let searchResults = args.results ?? false;
						}
						else
						{
							if (__request.args.prefix ?? false)
							{
								/* args.results should be generated by the following when invoking this code as a function */
								let searchResults = wiki.getSearch(uri.decode(__request.args.prefix ?? ''), numResults, _, uri.decode(__request.args.suffix ?? ''));
							}
						}

						let html = '';
						let html ..= '&lt;h3&gt;Search Results&lt;/h3&gt;';
						let html ..= '&lt;div id="searchResults"&gt;';

						if (searchResults)
						{							
							if (#searchResults &gt; 0)
							{	
								let html ..= '&lt;ul&gt;';
								foreach (var object in searchResults)
								{
									let html ..= '&lt;li&gt;';
										if (object.path)
										{
											let html ..= '&lt;div class="title"&gt;';
											let html ..= '&lt;a href="' .. object.uri .. '"&gt;' .. object.title .. '&lt;/a&gt;';
											if (!isExtension &amp;&amp; object.parent)
											{
												let html ..= ' in &lt;a href="' .. object.parent.uri .. '"&gt;' .. object.parent.title .. '&lt;/a&gt;';
											}
											let html ..= '&lt;/div&gt;';
											let html ..= '&lt;div class="itemmatch"&gt;' .. object.date .. ' (' .. object.editsummary .. ')&lt;/div&gt;';
											if (!isExtension &amp;&amp; object.text)
											{
												let html ..= '&lt;div class="searchpreview"&gt;' .. string.substr(object.text, 0, previewLength) .. '&lt;/div&gt;';
											}
										}
										else
										{
											let html ..= '&lt;div class="title"&gt;&lt;a href="' .. object.uri .. '"&gt;' .. object.name .. '&lt;/a&gt;&lt;/div&gt;';
											let html ..= '&lt;div class="itemmatch"&gt;' .. object.date .. ' (' .. object.description .. ')&lt;/div&gt;';
										}
									let html ..= '&lt;/li&gt;';
								}
								let html ..= '&lt;/ul&gt;';
							}
							else
							{
								let html ..= '&lt;p&gt;No Results found for your search.&lt;/p&gt;';
							}
						}
						else
						{
							let html ..= '&lt;p&gt;Please enter a search above.&lt;/p&gt;';
						}

						let html ..= '&lt;/div&gt;';
						web.html(html);
					</eval:expr>
				</body>
			</html>
		</return>
	</function>
</extension>

How to use

Download the Advanced Search extension from the MindTouch scripts repository, http://scripts.mindtouch.com/advancedsearch.xml, then install the extension by following the directions above.

Now choose a page to host the advanced search within your wiki and add the following DekiScript code to that page.

advancedSearch.form(__request.args, 'advsearch', '');
if (__request.args.advsearch)
{
 advancedSearch.results(
  wiki.getSearch(uri.decode(__request.args.prefix ?? ''), 20, _, uri.decode(__request.args.suffix ?? ''))
 );
}

Now you can search from the page that the above code has been added to. If you would like to restrict searches to only subpages of the search page then make the following modification. Please note, this merely prepopulates the located under field with the current page path.  It does not restrict the user from making changes to the located under field.

advancedSearch.form(__request.args, 'advsearch', page.path);

For the advanced user, identical output from the built-in Deki search can be obtained.  To get identical output, inline the contents of the advancedSearch.results function in a wiki page. The DekiScript on the advanced search page should look something like the code below.

Important note: You must be in Source mode within the editor and in a DekiScript block when pasting the advancedSearch.results code.
advancedSearch.form(__request.args, 'advsearch', page.path);
/* initialize searchResults with backward compat */
let searchResults = false;
/* check if this is inlined in a page */
let isExtension = __env.__request ? false : true;
/* sets the maximum number of search results to display when inlining code */
let numResults = 20;
/* sets the length of the content preview when inlining code */
let previewLength = 255;
/*.. snip, the rest of the code has been removed for brevity .. */

 

Developer tutorial

As part of an effort to document how easy it is to create new functionality in Deki, the following outlines some of the details that went into rapidly prototyping the advanced search extension.

Step 1: User interface

Designing the user interface was a straight forward endeavor. Starting with a blank HTML document, the form fields were laid out and named according to how they were to function. After the design was created, functionality had to be added to each form field.  In order for the fields to auto-populate with the last value entered, each field's value looks like this:

eval:value="args.request.FIELDNAME ?? ''"

When the extension is executed, Deki will replace the field's value with the value from the query params. This is because each time a user submits the form, it stays on the same wiki page.

Along with building the form fields, we need to add a special attribute to the form itself, a unique id. DekiScript provides a mechanism for generating unique strings for each request.  This is acheived by using special variables prefixed with an "@". By using a unique string, we can successfully find the exact form element we want when we begin writing JavaScript for this form.

<form eval:id="@formId">

 

Step 2: JavaScripting

Now that we have a form which submits to itself and auto-populates its fields, we need to create a Lucene query from the submitted information.  By hooking into the form's submit event using Jquery(don't code without it!), a Lucene query string can be built and added to the form before submitting.  This way, we can check against the query params for a Lucene query string and display the associated search results when we see it.

Here is how we hook a function onto the form submit using Jquery:

DekiWiki.$(<eval:js>'#' .. @formId</eval:js>).submit(GS.buildLuceneQuery);

The above snippet is not devoid of DekiScript.  Using the unique @formId from above, we can grab the exact form element that we created and attach the submit event to it.

From here one can delve into the GS.buildLuceneQuery function to see how it is exactly implemented, but a broad overview is that it creates a string with the correct Lucene modifiers based on the name of each form field.  This is left as an exercise for the reader.

Step 3: Search results

Lucky for us, there is a built-in DekiScript function which allows us to perform searches, wiki.getSearch!  By calling this extension with the prefix & suffix query params generated by the JavaScript code above, we can easily generate the search.  Most of the advancedSearch.results extension code is written to mimic the markup of the actual internal search results page and allow the user to inline the code on a wiki page.

Inlining the code is important because only internal DekiScript functions are available within DekiScript extensions. Therefore, in order to retrieve information about a page object or call any wiki.* extension, the code must be inlined on the page.  Determining whether the code is being executed on a wiki page or in an extension is based upon the existence of the __request object. This object is not available within a DekiScript extension, which explains why it must be passed in as a param, so if we can't access it then we are executing from the extension. Here is the snippet that performs the check.

let isExtension = __env.__request ? false : true;

Now later in the code there are checks against isExtension to determine what functionality can be used.

Step 4: Take a breath

Congratulations, you made it through the tutorial. Hopefully you've garnished some insight as to how easy it is to quickly prototype a new extension using DekiScript and JavaScript. Make sure you give back to the community and share your next great extension!

Files 2

FileSizeDateAttached by 
 advanced-search-ss1.png
No description
14.56 kB23:03, 22 Oct 2008GuerricActions
 aspatch
No description
827 bytes14:43, 20 Jul 2009ivan.zderadickaActions
Viewing 11 of 11 comments: view all
oooooooo
Posted 20:58, 25 Nov 2008
What_happens_if_I_put_a_really_long_word_into_your_comment_widget.I_think_it_might_break.I'm_not_sure_though.Let_me_know.

thanks
Damien
Posted 21:19, 25 Nov 2008
Hello, I was able to edit this page, I click by mistake edit.. just an FYI, did not changed anything.
Regards!
Posted 22:26, 9 Dec 2008
Will this work on Deki 9.02.1? I tried and got the following error message:
/html/body/eval:expr, function 'filedesc' failed (click for details)

Any ideas?
Posted 22:58, 29 Apr 2009
It seems to make this error when there's no result...
Posted 20:39, 29 May 2009
Problem is only with found items, which are files or images - wiki pages are working fine. Problem is that for file found object.description (which is calling this filedesc) fails.
Solution is either to look only for wiki pages or this fix worked for me (not showing object description for found files) :

*** advancedsearch-mod.xml Mon Jul 20 16:27:05 2009
--- advancedsearch.xml Wed Jun 3 10:41:17 2009
***************
*** 349,355 ****
else
{
let html ..= '<div class="title"><a href="' .. object.uri .. '">' .. object.name .. '</a></div>';
! let html ..= '<div class="itemmatch">' .. object.date .. '</div>';
}
let html ..= '</li>';
}
--- 349,355 ----
else
{
let html ..= '<div class="title"><a href="' .. object.uri .. '">' .. object.name .. '</a></div>';
! let html ..= '<div class="itemmatch">' .. object.date .. ' (' .. object.description .. ')</div>';
}
let html ..= '</li>';


(this patch also attached to the page, cause is not been displayed nice in the comment http://developer.mindtouch.com/@api/deki/files/4621/=aspatch) edited 14:45, 20 Jul 2009
Posted 14:37, 20 Jul 2009
I was told by support this extension is in BETA for 9.0.2.4. There is a bug on this: http://bugs.developer.mindtouch.com/view.php?id=6778
Posted 20:02, 14 Aug 2009
I haven't even been able to bring up the form. I get the following error:
/content/body/p/code/span, reference to undefined name 'advancedSearch': line 1, column 1
I'd appreciate any help I can get. FYI, I'm running 9.02.4
Posted 22:46, 25 Aug 2009
Just tried it under version 9.08.1 of Core and got the following error:
/html/body/eval:expr, function 'filedesc' failed (click for details)

MindTouch.Deki.Script.DekiScriptInvokeException: function 'filedesc' failed ---> System.InvalidOperationException: DekiContext.Current is not set
at MindTouch.Deki.DekiScriptFunction.DekiScriptInvokeDelegateWrapper.Invoke(DekiScriptLiteral arguments)
at MindTouch.Deki.DekiExtNativeFunction.<>c__DisplayClass3.<Invoke>b__0()
at System.SysUtil.Rethrow(Exception exception)
at MindTouch.Dream.ResultBase.Confirm()
at MindTouch.Dream.Result`1.Wait()
at MindTouch.Deki.DekiExtNativeFunction.Invoke(DekiScriptLiteral arguments)
at MindTouch.Deki.Script.DekiScriptRuntime.<Invoke>d__0.MoveNext()
--- End of inner exception stack trace ---
at MindTouch.Deki.Script.DekiScriptRuntime.<Invoke>d__0.MoveNext()
at MindTouch.Dream.Coroutine.Iterate(IEnumerator`1 coroutine, TaskEnv env, Action`1 completion)
at System.SysUtil.Rethrow(Exception exception)
at MindTouch.Dream.ResultBase.Confirm()
at MindTouch.Dream.Result`1.Wait()
at MindTouch.Deki.Script.DekiScriptRuntime.EvaluateProperty(DekiScriptLiteral value, DekiScriptEnv env)
at MindTouch.Deki.Script.DekiScriptAccess.Evaluate(DekiScriptEnv env, Boolean evaluateProperties)
at MindTouch.Deki.Script.DekiScriptAccess.Evaluate(DekiScriptEnv env)
at MindTouch.Deki.Script.DekiScriptBinary.Evaluate(DekiScriptEnv env)
at MindTouch.Deki.Script.DekiScriptBinary.Evaluate(DekiScriptEnv env)
at MindTouch.Deki.Script.DekiScriptBinary.Evaluate(DekiScriptEnv env)
at MindTouch.Deki.Script.DekiScriptAssign.Evaluate(DekiScriptEnv env)
at MindTouch.Deki.Script.DekiScriptSequence.Evaluate(DekiScriptEnv env)
at MindTouch.Deki.Script.DekiScriptTernary.Evaluate(DekiScriptEnv env)
at MindTouch.Deki.Script.DekiScriptSequence.Evaluate(DekiScriptEnv env)
at MindTouch.Deki.Script.DekiScriptForeach.<>c__DisplayClass3.<Evaluate>b__0(DekiScriptEnv subEnv)
at MindTouch.Deki.Script.DekiScriptGenerator.Generate(DekiScriptEval eval, DekiScriptEnv env)
at MindTouch.Deki.Script.DekiScriptGeneratorForeachItems.Generate(DekiScriptEval eval, DekiScriptEnv env)
at MindTouch.Deki.Script.DekiScriptForeach.Evaluate(DekiScriptEnv env)
at MindTouch.Deki.Script.DekiScriptSequence.Evaluate(DekiScriptEnv env)
at MindTouch.Deki.Script.DekiScriptTernary.Evaluate(DekiScriptEnv env)
at MindTouch.Deki.Script.DekiScriptTernary.Evaluate(DekiScriptEnv env)
at MindTouch.Deki.Script.DekiScriptSequence.Evaluate(DekiScriptEnv env)
at MindTouch.Deki.Script.Dom.DekiScriptDomExpr.Evaluate(DekiScriptEvalContext context, XmlNode parent, DekiScriptEnv env)

Retrieved from "http://avdeki.qlogic.org/Advanced_Search"
Posted 23:04, 30 Oct 2009
Install 9.12.1 and I am still seeing the above error on some of the searches. Sometimes it fails, sometimes it works. Is anyone else see this issue? Any idea why it is failing? edited 20:56, 2 Mar 2010
Posted 20:55, 2 Mar 2010
@mcraven I believe @guerric is replacing this with 'Enhanced Search" and won't continue to support it. I'll let him fill in the details
Posted 05:17, 3 Mar 2010
Viewing 11 of 11 comments: view all
You must login to post a comment.