none
HTML Client: Javascript Apply Filter to Screen Data Collection

    Question

  • Huy has shed some light on creating new JavaScript queries with filters here:

    http://social.msdn.microsoft.com/Forums/vstudio/en-US/65287efb-58c2-43ac-9210-d16860ef0f76/programmatically-add-new-data-item-in-html-client-as-part-of-a-entity-set-relationship-using

    Is there an example of adding a filter in javascript and applying it to the screen data collection?

    I tried the following but doesn't work (error: 'there is no .filter method'):

    myapp.BrowseContacts.ApplyFilter_execute = function (screen) {
        // Write code here.
        var filter = "FirstName eq " + msls._toODataString(screen.FilterString, ":String");
        screen.Contacts.filter(filter);
        screen.Contacts.load();
    };

    The following doesn't error and the screen data re-loads, but the filter is not applied to the data:

    myapp.BrowseContacts.ApplyFilter_execute = function (screen) {
        // Write code here.
        var filter = "FirstName eq " + msls._toODataString(screen.FilterString, ":String");
        screen.details.dataWorkspace.ApplicationData.Contacts.filter(filter);
        screen.Contacts.load();
    };

    Basically I'm trying to see what is supported for filtering of screen data using pure JS rather than Query Designer and PreProcessQuery method.  After stumbling in the dark through msls watching variables and banging head on walls; either this is not supported or I just haven't found the right object to apply the filter. 

    TIA,

    Josh

    Monday, September 30, 2013 2:54 PM

Answers

  • Forcing a visual collection to have the results that you want is not supported.

    The only way to do this is to back the visual collection with a query that returns the results that you want. Using optional query parameters, as has been suggested, is the best way to make a configurable query that returns different results based on the dynamic filter that you want to apply.

    In fact, this is how the Filter Control sample works. It creates the filter expression, serializes it, passes to a query through a parameter, deserializes the expression in the PreprocessQuery method of the query, builds the expression, and applies it to the query object.

    My suggestion is that you do something similar to what the Filter Control sample does.


    Justin Anderson, LightSwitch Development Team

    Tuesday, February 11, 2014 6:08 PM
    Moderator

All replies

  • Bump.  Does anyone know if this is possible?
    Monday, October 28, 2013 5:04 PM
  • Your code is close, especially in the second try.  Both .load() and .filter() methods return a promise object that when fulfilled contain your data.  The difference is that LightSwitch's API is programmed so that .load() will trigger the screen Visual Collection to re-display but .filter() does not.

    The results of the .filter() method will include an array results that has the filtered data.

    screen.details.dataWorkspace.ApplicationData.Contacts.filter(filter)

    .then(function(results) {

    // do what you want with the results.results array containing your filtered query

    });

    The screen's Visual Collection is not affected by this query however.  To do that, you're better off using LS' Query Designer unless there's some compelling reason not to.  You stated, "Basically I'm trying to see what is supported for filtering of screen data using pure JS rather than Query Designer and PreProcessQuery method."  For what you are trying to do in this specific example, LS will take your Query Designer instructions and convert it to "pure JS" automatically, just look at your data.js file to see what I mean.  In this particular case, preProcessQuery does not need to be used.

    Tuesday, October 29, 2013 12:40 AM
  • Thanks Allen.  I got so far as to see the results coming back from the .filter method, but I really want to know how to update the screens VC with a query.  I hacked around with the loader objects to no avail.

    The compelling reason:  the query designer misses because you have to determine at design time the fields on which to allow filtering.  I want to enable dynamic runtime filters on fields of the users' choosing.  Same for dynamic sorting. 

    I understand this can be done in a convoluted way using preprocess query (in which case you still must handle every field by name so the code is not reusable on another entity), but it seems there should be a way to load the VC from a query. 

    I'm in search of a way to do dynamic filtering and sorting without the need for a the linq dynamic query library, you might remember this post:

    http://social.msdn.microsoft.com/Forums/vstudio/en-US/500229db-f462-4fb4-ac25-9ab501a32607/html-table-server-side-sort?forum=lightswitch#dfeed9fd-bfa7-414b-b859-dadbd7c0ba87

    If it were this simple it would be great:

    myapp.BrowseContacts.ApplyFilter_execute = function (screen) { // Write code here. var filter = "FirstName eq " + msls._toODataString(screen.FilterString, ":String"); screen.details.dataWorkspace.ApplicationData.Contacts.filter(filter) .then(function(results){ screen.Contacts.load(results);

    }; };


    Maybe Huy will give us a nugget about using the loader objects, or at least tell us this simply cannot be done.

    TIA,

    Josh



    • Edited by joshbooker Tuesday, October 29, 2013 2:47 AM
    Tuesday, October 29, 2013 2:37 AM
  • "the query designer misses because you have to determine at design time the fields on which to allow filtering. I want to enable dynamic runtime filters on fields of the users' choosing."

    I have some query screens that allow the user to filter from 6 different parameters in both the SL and HTML clients using the query designer.  As long as the parameters are marked Optional in the query designer, the user decides what to filter with and what to ignore.

    Haven't seen Huy on here in quite a while. Perhaps he's moved on to another project.


    ComponentOne's OLAP grid for LS (both SL and HTML) may have a functionality more to your specifications.
    Tuesday, October 29, 2013 3:16 AM
  • Thanks Allen I understand devs can use optional parameters to allow some choice for filtering. Not so for sorting. This still falls short in my view. LS uses oData filters in Uri to fetch data. All the filter string conversion is in the js client. So why not allow us to query and refresh screens dynamically? Hopefully someone will present a way to override default loader query.
    Tuesday, October 29, 2013 1:31 PM
  • Forcing a visual collection to have the results that you want is not supported.

    The only way to do this is to back the visual collection with a query that returns the results that you want. Using optional query parameters, as has been suggested, is the best way to make a configurable query that returns different results based on the dynamic filter that you want to apply.

    In fact, this is how the Filter Control sample works. It creates the filter expression, serializes it, passes to a query through a parameter, deserializes the expression in the PreprocessQuery method of the query, builds the expression, and applies it to the query object.

    My suggestion is that you do something similar to what the Filter Control sample does.


    Justin Anderson, LightSwitch Development Team

    Tuesday, February 11, 2014 6:08 PM
    Moderator
  • Thanks for your reply Justin.  I was afraid of that.   Field names cannot be passed into PreprocessQuery since OrderBy and Where require Lambda expressions.  The bummer, IMHO, is that HTML Client has string based odata filters and we still cannot manipulate the loader objects to enable dynamic filter sort at runtime. 

    What would be your opinion about adding the Linq Dynamic Query Library to the middle tier so we don't have to serialize then deserialize then build lambda expressions? 

    This would open the door to a lot of dynamic  runtime behaviors that are currently difficult to implement.  Not to mention, prevent LS team from needing to include the same filter and sort extensions in all your samples (ie: Customizing the Table Control Sortable by Column )

    UV suggestion:

    Enable Dynamic Queries

    Thanks again,

    Josh



    • Edited by joshbooker Tuesday, February 11, 2014 6:36 PM
    Tuesday, February 11, 2014 6:34 PM
  • Hi Justin,

    I am trying to use visual collection like a cache which I modify programmatically to hold the latest data. Can you confirm that this is also not supported?

    My scenario is that I want to move to another screen without saving the cache back to datasource, but LS will prompt asking for 'save' or 'discard'.


    Frank

    Wednesday, February 12, 2014 8:30 PM
  • I don't know if this is supported, but it works...

    If you overwrite the createQuery method of the screen collection property's simpleDescriptor object, you can inject an OData filter string.

    myapp.BrowseCities.created = function (screen) {

        // Write code here.

        screen.details.properties["Cities"]._entry.simpleDescriptor.createQuery =

        function (filterString) {

            return this.dataWorkspace.ApplicationData["Cities"].filter(filterString);

        };

    };

    No need for PreProcessQuery or Dynamic Linq.

    How to:

    http://joshuabooker.com/Blog/Post/3/LightSwitch-HTML-Client-Filter-Hack

    HTH,

    Josh

    Thursday, February 27, 2014 5:15 AM
  • very very clever Josh !!!

    paul van bladel

    Thursday, February 27, 2014 5:14 PM
  • Thanks Paul.  This could be used to take control of any of the query methods including:

    filter: function filter(expression) {
        /// <summary>
        /// Filters results using an expression defined
        /// by the OData $filter system query option.
        /// </summary>
        /// <param name="expression" type="String">
        /// An OData filter expression.
        /// </param>
        /// <returns type="msls.DataServiceQuery" />
    },
    
    orderBy: function orderBy(propertyName) {
        /// <summary>
        /// Orders results by a property in ascending order.
        /// </summary>
        /// <param name="propertyName" type="String">
        /// A property name.
        /// </param>
        /// <returns type="msls.DataServiceQuery" />
    },
    
    orderByDescending: function orderByDescending(propertyName) {
        /// <summary>
        /// Orders results by a property in descending order.
        /// </summary>
        /// <param name="propertyName" type="String">
        /// A property name.
        /// </param>
        /// <returns type="msls.DataServiceQuery" />
    },
    
    thenBy: function thenBy(propertyName) {
        /// <summary>
        /// Further orders results by a property in ascending order.
        /// </summary>
        /// <param name="propertyName" type="String">
        /// A property name.
        /// </param>
        /// <returns type="msls.DataServiceQuery" />
    },
    
    thenByDescending: function thenByDescending(propertyName) {
        /// <summary>
        /// Further orders results by a property in descending order.
        /// </summary>
        /// <param name="propertyName" type="String">
        /// A property name.
        /// </param>
        /// <returns type="msls.DataServiceQuery" />
    },
    
    expand: function expand(expression) {
        /// <summary>
        /// Expands results by including additional navigation properties using
        /// an expression defined by the OData $expand system query option.
        /// </summary>
        /// <param name="expression" type="String">
        /// An OData expand expression (a comma-separated
        /// list of names of navigation properties).
        /// </param>
        /// <returns type="msls.DataServiceQuery" />
    },
    
    skip: function skip(count) {
        /// <summary>
        /// Bypasses a specified number of results.
        /// </summary>
        /// <param name="count" type="Number">
        /// The number of results to skip.
        /// </param>
        /// <returns type="msls.DataServiceQuery" />
    },
    
    top: function top(count) {
        /// <summary>
        /// Restricts results by a specified number.
        /// </summary>
        /// <param name="count" type="Number">
        /// The number of results to return.
        /// </param>
        /// <returns type="msls.DataServiceQuery" />
    },
    
    includeTotalCount: function includeTotalCount() {
        /// <summary>
        /// Requests that the total result count as if the skip and top
        /// operators were not applied is returned in addition to the results.
        /// </summary>
        /// <returns type="msls.DataServiceQuery" />
    

    Lots of possibilities.

    Josh

    Thursday, February 27, 2014 6:06 PM
  • Josh,

    Brilliant. I could use already your approach for a more decent way to get autocomplete as you type in search screens !

    See: http://blog.pragmaswitch.com/?p=1670


    paul van bladel

    Friday, February 28, 2014 8:22 AM
  • Nice Paul. 

    I'm working on some generic code that will apply the query methods to all screen collections.

    http://joshuabooker.com/Blog/Post/5/Iterate-over-Screen-Collections

    Josh

    Friday, February 28, 2014 12:10 PM
  • Cool. Looking forward to the wizardry :)

    paul van bladel

    Friday, February 28, 2014 12:37 PM