jQuery click and the onclick attribute

by Aaron Powell 27. August 2010 04:35

Note: This may be a bit of an example of “doing it wrong”, but I don’t care :P

Yesterday I was having a look at a bug for a client in which we have a search control on a page which does an AJAX search, reloading the whole page.
Obviously that was a bit of a problem, the dynamic search results were being trashed by the full postback.

It was a very basic form, with a text input box for keywords and a button which you could click if you wanted to do the search. When clicking the button everything was fine, but when hitting Enter in the textbox the whole page refreshed.
After doing a few tests I noticed something interesting, the AJAX was working, the search results were loaded client side but then immediately the page posted.

So I went on the hunt for the keyboard event which was on the textbox and I found this:

$(function() {
	$('.searchKeywords).keypress(function(e) {
		if(e.which == 13) {
	                e.preventDefault();
                	if ($(".searchKeywords").val() != "") {
        	            $(".KeywordSearchFilter").click();
	                }
		}
	});
});

Nothing odd there, that’s perfectly fine for handling the keypress event and then doing the search, so my next assumption is that the button is having a problem.

I started digging around to see if I could find where the buttons click event handler was being registered.
But no joy, nothing, nada, zip.
No jQuery click handler is being registered.

Ooooook… So I had a look at the source for the button itself and found this:

<input type="submit" name="..." value="Go" onclick="SearchHelper.searchByKeyword(null);;return false;WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions("...", "", true, "SearchKeyword", "", false, false))" id="..." class="submit KeywordSearchFilter">

I’ve trimmed out the control names so it’s a bit easier to read

Crap, see the problem? The click events are registered in the onclick attribute of the button!

Well first off I was surprised that jQuery actually fired the onclick attributes events, but it is a problem that it wasn’t acknowledging the return false statement.

So I had 2 options in front of me:

1) Refactor the code so that it was registering the event using jQuery

2) Find some funky way to do this

 

Option 1 was not really viable, this search control is a super class of a common search control and I didn’t want to break any other parts of the site, sure our tests would cover it *cough cough :P* but it’s quite a large site and with only a few weeks left here I don’t want to cock anything up.

So it’s option 2, time to work out some funky way around it!
Since onclick is just an attribute, I thought “can I just access it and grab the methods out”? I don’t see why not, jQuery must be doing something like that itself.

And since it’s just a set of functions, could I invoke it?

Well it turns out that I could! And this is what I now have in the codebase:

$(function() {
	$('.searchKeywords).keypress(function(e) {
		if(e.which == 13) {
	                e.preventDefault();
                	if ($(".searchKeywords").val() != "") {
        	            $(".KeywordSearchFilter").attr('onclick')();
	                }
		}
	});
});

I tested this in IE8, Firefox and Chrome and it worked in all three browsers!

Hey, it may not be the best solution, but hey, it worked! :P

Categories: jQuery | JavaScript

Text casing and Examine

by Aaron Powell 23. August 2010 15:41

A few times I’ve seen questions posted on the Umbraco forums which ask how to deal with case insensitivity text with Examine, and it’s also something that we’ve had to handle a few times within our own company.

Here’s a scenario:

  • You have a site search
  • You use examine
  • You want to show the results looking exactly the same as it was before it went into Examine

If you’re running a standard install you’ll notice that the content always ends up lowercased!

This is a bit of a problem, page titles will be lowercase, body content will be lowercase, etc. Part of this will be due to a mistake in Examine, part of it is due to the design of Lucene.

In this article I’ll have a look at what you need to do to make it work as you’d expect.

First, some background

Before we dive directly into what to do to fix it you really should understand what is happening. If you don’t care feel free to skip over this bit though :P.

Searching is a tricky thing, and when searching the statement Examine == examine = false; To get around this searching is best done in a case insensitive manner. To make this work Examine did a forced lowercase of the content before it was pushed into Lucene.Net. This was to ensure that everything was exactly the same when it was searched against.
In hindsight this is not really a great idea, it really should be the responsibility of the Lucene Analyzer to handle this for you.

Many of the common Lucene.Net analyzers actually do automatic lowercasing of content, these analysers are:

  • StandardAnalyzer
  • StopAnalyzer
  • SimpleAnalyzer

So if you’re using the standard Examine config you’ll find yourself using the StandardAnalyzer and still have your content lowercased.

This means that there’s no need to Lucene to concern itself about case sensitivity when searching, everything is parsed by the analyzer (field terms and queries) and you’ll get more matches.

So how do I get around this?

Now that we’ve seen why all your content is generally lower case, how can we work with it in the original format and display it back to the UI?

Well we need some way in which we can have the field data stored without the analyzer screwing around with it.

Note: This doesn’t need to be done if you’re using an analyzer which doesn’t have a LowerCaseTokenizer or LowercaseFilter. If you’re using a different analyzer, like KeywordAnalyzer then this post wont cover what you’re after (since the KeywordAnalyzer isn’t lowercasing, you’re actually using an out-dated version of Examine, I recommend you grab the latest release :)). More information on Analyzers can be found at http://www.aaron-powell.com/lucene-analyzer

Luckily we’ve got some hooks into Examine to allow us to do what we need here, it’s in the form of an event on the Examine.LuceneEngine.Providers.LuceneIndexer, called DocumentWriting. Note that this event is on the LuceneIndexer, not the BaseIndexProvider. This event is Lucene.Net specific and not logical on the base class which is agnostic of any other framework.

What we can do with this event is interact directly with Lucene.Net while Examine is working with it.
You’ll need to have a bit of an understanding of how to work with a Lucene.Net Document (and for that I’d recommend having a read of this article from me: http://www.aaron-powell.com/documents-in-lucene-net), cuz what you’re able to do is play with Lucene.Net… Feel the power!

So we can attach the event handler the same way as you would do any other event in Umbraco, using an Action Handler:

public class UmbracoEvents : ApplicationBase
{
	public UmbracoEvents()
        {
            var indexer = (LuceneIndexer)ExamineManager.Instance.IndexProviderCollection["DefaultIndexer"];

            indexer.DocumentWriting +=new System.EventHandler(indexer_DocumentWriting);
        }
}

To do this we’ve got to cast the indexer so we’ve got the Lucene version to work with, then we’re attaching to our event handler. Let’s have a look at the event handler

void indexer_DocumentWriting(object sender, DocumentWritingEventArgs e)
{
	//grab out lucene document from the event arguments
	var doc = e.Document;

	//the e.Fields dictionary is all the fields which are about to be inserted into Lucene.Net
	//we'll grab out the "bodyContent" one, if there is one to be indexed
	if(e.Fields.ContainsKey("bodyContent")) 
	{
		string content = e.Fields["bodyContent"];
		//Give the field a name which you'll be able to easily remember
		//also, we're telling Lucene to just put this data in, nothing more
		doc.Add(new Field("__bodyContent", content, Field.Store.YES, Field.Index.NOT_ANALYZED));
	}
}

And that’s how you can push data in. I’d recommend that you do a conditional check to ensure that the property you’re looking for does exist in the Fields property of the event args, unless you’re 100% sure that it appears on all the objects which you’re indexing.

Lastly we need to display that on the UI, well it’s easy, rather accessing the bodyContent property of the SearchResults, use the __bodyContent and you’ll get your unanalyzed version.

Conclusion

Here we’ve looked at how we can use the Examine events to interact with the Lucene.Net Document. We’ve decided that we want to push in unanalyzed text, but you could use this idea to really tweak your Lucene.Net document. But really playing with the Document is not recommended unless you *really* know what you’re doing ;).

Categories: .Net | Examine | Umbraco

Examine RC3 Released

by Shannon Deminick 18. August 2010 18:30

Hopefully this will be a quick RC! I’m really hoping to release v1.0 RTM by early next week (latest). If you are able to help out with some testing it would be amazing!!

Here's what's new:

  • PDF Indexing
  • Easily implement custom data indexing outside of Umbraco
  • More XSLT Extensions for Umbraco
  • Some framework refactoring so a new DLL: Examine.LuceneEngine.dll which contains all of the Lucene.Net implementation
    • Because of this refactoring, if you've built your own providers, you may need to update our code to work, otherwise it is backwards compatible for most people.
  • More unit tests
  • More documentation

Get it while it’s hot! And don’t forget to read the release notes.

DOWNLOAD FROM CODEPLEX HERE

Categories: Examine | Umbraco

Paging with Examine

by Aaron Powell 18. August 2010 13:12

I’ve been asked this question a few times, how do you implement pagination in your Examine search results.

Well a fun-fact is that Lucene doesn’t have really good way to do this, it just involves skipping to the point that you want to get the results from.

Thanks to .NET and LINQ we have extension methods which handles that nicely, Skip and Take, and since that the search results are IEnumerable underneath they can be used. But there’s one problem, doing this will result in a bit of a performance hit, as your initial Skip would hydrate a bunch of entities from the underlying Lucene store, which is where you loose performance.

So we implemented our own version of Skip! So you can just use Skip and Take as standard, and they’ll be evaluated without loosing performance.

Here’s the code:

int pageNumber = string.IsNullOrEmpty(Request.QueryString["pageNum"]) ? 0 : int.Parse(Request.QueryString["pageNum"]);
int pageSize = 10; //this could be a config value or something

/* serach snipped */

var results = searcher.Search(...);

//And we'll just set a repeater with the results
Repeater.DataSource = results.Skip(pageNumber * pageSize).Take(pageSize);
Repeater.DataBind();

It's just that easy.

Categories: .Net | Examine | Umbraco

How to build a search query in Examine

by Aaron Powell 12. August 2010 12:14

Now that Examine is able to be used by a wider audience than Umbraco understanding how search works is possibly a bit more important ;).

So today while answering a question on the Umbraco forum I thought that what I was going on about is something that more people might want to hear about. And really, I do like the sound of my own (virtual) voice…

Understanding Lucene.Net from Examine

For this I’m going to be looking at the Lucene.Net implementation of Examine, and this is agnostic of whether you’re using UmbracoExamine or Examine.LuceneEngine.

To get started you should familiarize yourself a bit with the Lucene Query Parser Syntax, as that’s what we’re using internally to get the data back from Lucene.Net.

Also, we’re going to be working with the Fluent API for Examine, so fell free to read up on that here and here. With Examine we’ve made it easy to see what the Lucene query you’re building up is as you’re working with the Fluent API, in fact if you to a .ToString() call on the ISearchCriteria instance you’ll be able to see what you’re search query looks like (we’ve also got some other information in the result of that method call too).

So you can see what’s been generated, let’s build a query and dissect it:

var criteria = searcher.CreateSearchCriteria(IndexTypes.Content);
criteria = criteria.NodeName("Hello").And().NodeTypeAlias("world").Compile();

Console.WriteLine(criteria.ToString()); //+(+nodeName:Hello +nodeTypeAlias:world) +__IndexType:content

So this is what we've got, a total of three conditions… But wait, there’s only two conditions that I entered right?

Not quite, Examine has some smarts built into it around what you’re searching on and it’ll add that restriction on your behalf. This is so you don’t get results back from a different index type in your query. Note: If you don’t specify the IndexType then this condition wont be added for your.

Also, to ensure that all your entered queries don’t get killed by the IndexType (a problem in earlier Examine builds) we combine everything you enter into a GroupedAnd statement.

Let’s have a look at the parts we did request, and how they are comprised:

+nodeName:Hello +nodeTypeAlias:world

Ok, so what we've got here is our two conditions which we've added using Examine. Each is an AND (or in Lucene terminology SHOULD) and this is denoted by the +. Next we have a field name, in this case either nodeName or nodeTypeAlias. Youl’ll notice back in our Fluent API query we actually generated all of that using the build in methods, rather than having to use more magic strings. Next there is a : to indicate where the field name ends. Lastly we have the term which we’re going to search against, either Hello or world.

So essentially it’s built up of BOOLEAN_OPERATION&FieldName:Term (the & is so it’s slightly readable).

Conclusion

This was a brief look at how the Fluent API for Examine will turn your typed query into a Lucene query that is then searching.

With this knowledge you should be better able to design complex queries, use mixed conditionals and just plain go crazy.

Categories: .Net | Examine | Umbraco

Using Examine to index & search with ANY data source

by Shannon Deminick 10. August 2010 10:38

During CodeGarden 2010 a few people were asking how to use Examine to index and search on data from any data source such as custom database tables, etc… Previously, the only way to do this was to override the Umbraco Examine indexing provider, remove the Umbraco functionality embedded in there, and then do a lot of coding yourself.  …But now there’s some great news! As of now you can use all of the Examine goodness with it’s embedded Lucene.Net with any data source and you can do it VERY easily.

Some things you need to know about the new version:

  1. I haven’t made a release version of this yet as it still needs some more testing, though we are putting this into a production site next week.
  2. If you want to try this, currently you’ll need to get the latest source from Examine @ CodePlex
  3. If you are using a previous version of Examine, there’s a few breaking changes as some of the class structures have been moved, however you config file should still work as is… HOWEVER, you should update your config file to reflect the new one with the new class names
  4. There is now 3 DLLs, not just 2:
    • Examine.DLL
      • Still pretty much the same… contains the abstraction layer
    • Examine.LuceneEngine.DLL
      • The new DLL to use to work with data that is not Umbraco specific
    • UmbracoExamine.DLL
      • The DLL that the Umbraco providers are in

Ok, now on to the good stuff. First, I’ve added a demo project to this post which you can download HERE. This project is a simple console app that contains a sample XML data file that has 5 records in it. Here’s what the app does:

  1. This re-indexes all data
  2. Searches the index for node id 1
  3. Ensures one record is found in the index
  4. Updates the dateUpdated time stamp for the data record
  5. Re-indexes the record with node id 1’

So assuming that you have some custom data like a custom database table, xml file, or whatever, there’s really only 3 things that you need to do to get Examine indexing your custom data:

  1. Create your own ISimpleDataService
    • There is only 1 method to implement: IEnumerable<SimpleDataSet> GetAllData(string indexType)
    • This is the method that Examine will call to re-index your data
    • A SimpleDataSet is a simple object containing a Dictionary<string, string> and a IndexedNode object (which consists of a Node Id and a Node Type)
    • For example, if you had a database row, your SimpleDataSet object for the row would be the dictionary of the rows values, it’s node id and type … easy.
  2. Use the ToExamineXml() extension method to re-index individual nodes/records
    • Examine relies on data being in the same XML structure as Umbraco (which we might change in version 2 sometime in the future… like next year) so we need to transform simple data into the XML structure. We’ve made this quite easy for you; all you have to do is get the data from your custom data source into a Dictionary<string, string> object and use this extension method to pass the xml structure in to Examine’s ReIndexNode method.
    • For example: ExamineManager.Instance.ReIndexNode(dataSet.ToExamineXml(dataSet["Id"], "CustomData"), "CustomData");  where dataSet is a Dictionary<string, string> .
  3. Update your Examine config to use the new SimpleDataIndexer index provider and the new LuceneSearcher search provider

If you’re not using Umbraco at all, then you’ll only need to have the 2 Examine DLLs which don’t reference the Umbraco DLLs whatsoever so everything is decoupled.

I’d recommend downloading the demo app and running it as it will show you everything you need to know on how to get Examine running with custom data. However, i know that people just like to see code in blog posts, so here’s the config for the demo app:

<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="Examine" type="Examine.Config.ExamineSettings, Examine"/> <section name="ExamineLuceneIndexSets" type="Examine.LuceneEngine.Config.IndexSets, Examine.LuceneEngine"/> </configSections> <Examine> <ExamineIndexProviders> <providers> <!-- Define the indexer for our custom data. Since we're only indexing one type of data, there's only 1 indexType specified: 'CustomData', however if you have more than one type of index (i.e. Media, Content) then you just need to list them as a comma seperated list without spaces. The dataService is how Examine queries whatever data source you have, in this case it's a custom data service defined in this project. A custom data service only has to implement one method... very easy. --> <add name="CustomIndexer" type="Examine.LuceneEngine.Providers.SimpleDataIndexer, Examine.LuceneEngine" dataService="ExamineDemo.CustomDataService, ExamineDemo" indexTypes="CustomData" runAsync="false"/> </providers> </ExamineIndexProviders> <ExamineSearchProviders defaultProvider="CustomSearcher"> <providers> <!-- A search provider that can query a lucene index, no other work is required here --> <add name="CustomSearcher" type="Examine.LuceneEngine.Providers.LuceneSearcher, Examine.LuceneEngine" /> </providers> </ExamineSearchProviders> </Examine> <ExamineLuceneIndexSets> <!-- Create an index set to hold the data for our index --> <IndexSet SetName="CustomIndexSet" IndexPath="App_Data\CustomIndexSet"> <IndexUserFields> <add Name="name" /> <add Name="description" /> <add Name="dateUpdated" /> </IndexUserFields> </IndexSet> </ExamineLuceneIndexSets> </configuration>
Categories: .Net | Examine | Umbraco

TheFARM needs senior .Net developer!

by Shannon Deminick 5. August 2010 05:50

TheFARM is currently looking for a talented and passionate senior .Net developer. Someone that has a minimum of 4 years ASP.Net development experience, stays up to date with the latest technologies, and actually loves to program. Skill set should include:

  • Expert knowledge of .Net from v2 up to v4
  • Experience with common patterns and practices including Dependency Injection
  • JavaScript, JQuery, AJAX, and everything else to do with client side web programming… yes, you’ll still have to write some CSS and HTML
  • ASP.Net MVC 2+
  • We are an Umbraco CMS development agency so if you’ve used it before, it would be of huge benefit, otherwise be prepared for extensive training
  • … and lots, lots more ;)

If you think you would be suitable for this role, we would love to hear from you! Please email us your CV/Resume to: Work@thefarmdigital.com.au

Tags: , ,

Adding embedded resource with ClientDependency

by Shannon Deminick 3. August 2010 05:32

The uComponents project for Umbraco is coming along rather nicely! So far there are 13 new data types created and a few extensions, hopefully soon we’ll have a package ready to go. In the meantime, I thought I’d share a nice little code snippet that we’re using this throughout uComponents that allows you to add embedded resources to ClientDependency. It’s pretty easy and works perfectly with Umbraco 4.5 or any other site you have running ClientDependency. This will ensure that embedded resources (JavaScript or CSS) are added to the ClientDependency combined scripts/styles and also compressed and cached.

First, I’ll show you how to register your embedded resources using our extension method class (for a Control object):

this.AddResourceToClientDependency(
    "DataTypesForUmbraco.Controls.Shared.Resources.PrevalueEditor.css", 
    ClientDependency.Core.ClientDependencyType.Css);

The above code assumes:

  • The class that you are consuming the code inherits from System.Web.UI.Control
  • That your embedded resource’s path is: DataTypesForUmbraco.Controls.Shared.Resources.PrevalueEditor.css
  • That your embedded resource is a CSS type

Pretty easy right!! Well, the only thing missing is that you’ll need to add our extension method class to your project which looks like this:

/// 
/// Extension methods for embedded resources
/// 
public static class ResourceExtensions
{

    /// 
    /// Adds an embedded resource to the ClientDependency output by name
    /// 
    /// 
    /// 
    /// 
    public static void AddResourceToClientDependency(this Control ctl, string resourceName, ClientDependencyType type)
    {
        //get the urls for the embedded resources           
        var resourceUrl = ctl.Page.ClientScript.GetWebResourceUrl(ctl.GetType(), resourceName);

        //add the resources to client dependency
        ClientDependencyLoader.Instance.RegisterDependency(resourceUrl, type);
    }

    /// 
    /// Adds an embedded resource to the ClientDependency output by name and priority
    /// 
    /// 
    /// 
    /// 
    public static void AddResourceToClientDependency(this Control ctl, string resourceName, ClientDependencyType type, int priority)
    {
        //get the urls for the embedded resources           
        var resourceUrl = ctl.Page.ClientScript.GetWebResourceUrl(ctl.GetType(), resourceName);

        //add the resources to client dependency
        ClientDependencyLoader.Instance.RegisterDependency(priority, resourceUrl, type);
    }

}

So basically, if you have a Control, you can register your embedded resources in ClientDependency with one line of code… sweeeeet.

Categories: .Net | ClientDependency