locked
LightSwitch List: access the "QuickSearch" pipeline RRS feed

  • Question

  • Hey Experts,

    I'm messing around a bit with some LightSwitch applications, as usual.  Today, I hoped on planning to accomplish (& blog about) "fuzzy searching", but I'm stuck on step 1...

    When you have an EntityCollection on a screen and look at the properties, one can enable or disable the "Support Search".  Is it possible to also hook into this search pipeline, get the string used as a search term and change it, before the query is executed?

    Thanks for any helpful advice!

    Jan

    


    It's your story - time to switch on the innovation. || About me || LightSwitch blog

    Monday, April 9, 2012 3:13 PM

Answers

  • My question is though: am i correct that I have to use reflection to find the CollectionLoader?  IScreenCollectionPropertyLoader is a public interface, so it doesn't feel natural to have to use reflection.  Or is it indeed the only way?

    Much simpler way of getting the loader:

    IScreenCollectionPropertyLoader loader = this.Details.Properties.Students.Loader as IScreenCollectionPropertyLoader;

    I would also recommend moving your event subscription code from the Activated method to the Created method. Otherwise, you'll subscribe yet another handler every time you user moves away from the screen and then back to it again.



    Justin Anderson, LightSwitch Development Team

    Thursday, April 12, 2012 8:49 AM
    Moderator

All replies

  • Hi Jan,

    I presume you want to do this server side.

    As you probably know, an IQueryable is under the hood an expression tree. So, the query is not really available as a string, but as a Linq expression. With "System.Linq.Expressions" it is possible, though pretty complicated, to further extend the existing expression.  I have provided in following thread some code with which you can do this, in the preprocessQuery method.

    A straight forward way to construct a dynamic query/dynamic where clause in LightSwitch


    paul van bladel



    Monday, April 9, 2012 6:12 PM
  • Hey Paul, fellow Belgian!

    I'm sure examining the expression tree is possible, and I'll be happy to mark your comment as answer... Although before I do I'd like to ask one more time if anyone has an easy way to get this string and change it, before the query actually executes:

    


    It's your story - time to switch on the innovation. || About me || LightSwitch blog

    Monday, April 9, 2012 9:35 PM
  • There is no (reasonable) way to intercept the search terms or modify the generated search query. I say "no (reasonable) way" because it is theoretically possible to listen for when the SearchTerms property changes on the IScreenCollectionPropertyLoader to which the data grid or list control is bound and reset the terms to the new terms you want. If you get this working, it should have the effect of tossing away the terms that the user enters and displays the terms that you have set. In any case, you cannot intercept the generated query and rewrite it before it is sent to the data source.

    Justin Anderson, LightSwitch Development Team

    Monday, April 9, 2012 11:56 PM
    Moderator
  • Hi Jan,

    You gave few information regarding your precise intention (I fully respect wizard secret :) ), so I just give it a try:

    I'm sure you know the famous filter extension (Filter Control for Visual Studio LightSwitch), if I remember well, the search text (which is composed based on the different were clauses the user composes) is transferred as an xml fragment, which is parsed server side and constructed to an IQueryable.  Would that be a workable approach for your problem? Of course this presumes you do not use a standard search box, but it addresses the fact that you can use the query as a "string".



    paul van bladel


    Tuesday, April 10, 2012 6:01 AM
  • Hey Paul & Justin,

    thank you both for your replies!

    @Justin: am I correct in my believe that this is a client-side hack?  The user enters "abc", I grab & replace with "ABC" before they can hit enter / search?  This means that the user would also see my replacement happening?

    Also, could you shed some light in how the control calls the generated query and where I can find the generated query?  Perhaps I can use your technique not to alter the "Text" but to replace the "commandhandler" (or whatever is used) entirely?

    Alternatively, is there any event fired when the user executes the search?  As a last resort, I can modify the SearchTerm in that event, and change it back when the screen is reloaded?

    @Paul, you're active early :-) (8 AM here in Belgium)

    I have no need to grab the entire query, just the word (/words/characters) that the user entered in the "Quick Search" box.  I'd really like to "hook" into the standard search box, before brewing custom controls :-)

    In short: if the user enters "ab2" and hits enter /search, I really want my application to search for entities that contain "ABTWO" instead...


    It's your story - time to switch on the innovation. || About me || LightSwitch blog

    Tuesday, April 10, 2012 6:23 AM
  • @Justin: am I correct in my believe that this is a client-side hack?  The user enters "abc", I grab & replace with "ABC" before they can hit enter / search?  This means that the user would also see my replacement happening?

    Also, could you shed some light in how the control calls the generated query and where I can find the generated query?  Perhaps I can use your technique not to alter the "Text" but to replace the "commandhandler" (or whatever is used) entirely?

    Alternatively, is there any event fired when the user executes the search?  As a last resort, I can modify the SearchTerm in that event, and change it back when the screen is reloaded?

    You got it, well, almost. You would receive a property changed notification for the SearchTerms property on the collectio property loader. At this point, the user has already hit enter. If you change the search terms in response to this, the user will see your replacement in the search text box.

    The query that is generated is not created by the control. It is an internal implemenation of the underlying IScreenCollectionPropertyLoader. The way that the control executes a search is just by calling the SetSearchTerms(IEnumerable<SearchTerm>) method on the IScreenCollectionPropertyLoader interface. What the loader does is queues up a refresh with a short time out. When the time out occurs, the query is created and executed. This is why when you perform a search, it doesn't immediately fire off a refresh. This is to prevent many queries from executing if you are performing quick successive searches.

    Finally, there is no event that corresponds to search. There is only the ExecuteCompleted event on IExecutable, which signifies that query has finished executing. You can't really determine why the query was executed though (was it a search? a refresh? a sort? could even be a combination of these).


    Justin Anderson, LightSwitch Development Team

    Tuesday, April 10, 2012 7:16 AM
    Moderator
  • Justin, wow very valuable info! Thanks!

    I'll play around a bit during lunch break and get back to you! :-)


    It's your story - time to switch on the innovation. || About me || LightSwitch blog

    Tuesday, April 10, 2012 7:59 AM
  • Hey experts.

    After my last post, the flue got me and knocked me out.  I'm awake for the first time in two days, and first thing I did was play around with this... <= geek.

    Anyways, I did get this working, using the code below (I'll post about it in detail & will actually release it as an extension, links on the way):

     

     public partial class StudentsListDetail
        {
            partial void StudentsListDetail_Activated()
            {
                //Use reflection to find the CollectionLoader
                var t = this.Students.Screen.Details.Properties.All().FirstOrDefault();
                var prop = t.GetType().GetProperty("CollectionLoader", BindingFlags.GetField | BindingFlags.Instance | BindingFlags.NonPublic);
                var loader = prop.GetValue(t, null) as IScreenCollectionPropertyLoader;
    
                //Use the main dispatcher (who owns the CollectionLoader) to listen for changes
                Microsoft.LightSwitch.Threading.Dispatchers.Main.BeginInvoke(
                    () =>
                    {
                        loader.PropertyChanged += loader_PropertyChanged;
                    }
                );
            }
    
            void loader_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
            {
                if (e.PropertyName == "SearchTerms")
                {
                    //Upon change, we replace the SearchTerms. This in turn, fires the PropertyChanged event again.
                    //To avoid endless loops: behold the ignoreNextSearchTermChangedEvent boolean!
                    if (ignoreNextSearchTermsChangedEvent)
                    {
                        ignoreNextSearchTermsChangedEvent = false;
                    }
                    else
                    {
    
                        IScreenCollectionPropertyLoader loader = sender as IScreenCollectionPropertyLoader;
                        List<SearchTerm> fuzzyTerms = new List<SearchTerm>();
                        foreach (var searchTerm in loader.SearchTerms)
                        {
                            fuzzyTerms.Add(MakeFuzzy(searchTerm));
                        }
                        loader.SetSearchTerms(fuzzyTerms);
                        ignoreNextSearchTermsChangedEvent = true;
                    }
                }
            }

    My question is though: am i correct that I have to use reflection to find the CollectionLoader?  IScreenCollectionPropertyLoader is a public interface, so it doesn't feel natural to have to use reflection.  Or is it indeed the only way?

    By the way, this implementation is doing exactly what Justin wrote:There is no (reasonable) way to intercept the search terms or modify the generated search query. I say "no (reasonable) way" because it is theoretically possible to listen for when the SearchTerms property changes on the IScreenCollectionPropertyLoader to which the data grid or list control is bound and reset the terms to the new terms you want. If you get this working, it should have the effect of tossing away the terms that the user enters and displays the terms that you have set. In any case, you cannot intercept the generated query and rewrite it before it is sent to the data source.

    Thanks again!

    Jan


    It's your story - time to switch on the innovation. || About me || LightSwitch blog

    Thursday, April 12, 2012 8:30 AM
  • My question is though: am i correct that I have to use reflection to find the CollectionLoader?  IScreenCollectionPropertyLoader is a public interface, so it doesn't feel natural to have to use reflection.  Or is it indeed the only way?

    Much simpler way of getting the loader:

    IScreenCollectionPropertyLoader loader = this.Details.Properties.Students.Loader as IScreenCollectionPropertyLoader;

    I would also recommend moving your event subscription code from the Activated method to the Created method. Otherwise, you'll subscribe yet another handler every time you user moves away from the screen and then back to it again.



    Justin Anderson, LightSwitch Development Team

    Thursday, April 12, 2012 8:49 AM
    Moderator