angular-ui-select2 and breezejs: load ajax list after typing in 2 characters
Asked Answered
A

1

2

I have a project where I'm using BreezeJS to fetch data from my webserver. I'm using AngularJS with the ui-select2 module. Currently, I have it where when I load my page, breezejs makes a call to fetch the data that I dump into a scope variable. From there, select2 can easily make the reference to it and build accordingly.

If I want to ajaxify things, it gets really tricky. I want to have the ability to use select2's ajax or query support, but instead of using it to fetch data, I want to use breezejs to do it. So during a page load, nothing is loaded up until I start typing in X minimum characters before it makes an ajax fetch.

Constraints: I do not want fetch data using select2's "ajax". I want BreezeJS to handle the service calls. When I use ajax, it makes an ajax call everytime I press a character in order to filter the results (and resemble autocomplete). I just want the list to load up once and use the native filtering after that.

Here is what I have so far:

breezejs - StateContext.JS

m.app.factory('StateContext', ['$http', function ($http) {
    configureBreeze();

    var dataService = new breeze.DataService({
        serviceName: "/Map/api",
        hasServerMetadata: false
    });

    var manager = new breeze.EntityManager({ dataService: dataService});

    var datacontext = {
        getAllStates: getAllStates
    };
    return datacontext;


    function getAllStates() {
        var query = breeze.EntityQuery
                .from("States");
        return manager.executeQuery(query);
    }


    function configureBreeze() {
        breeze.config.initializeAdapterInstances({ dataService: "webApi" });
    }
}]);

This works and returns my json object correctly.

Here is how I call the service:

m.app.controller('LocationCtrl', ['$scope', 'StateContext', function ($scope, StateContext) {

    $scope.getAllStates = function () {
        StateContext.getAllStates().then(stateQuerySucceeded).fail(queryFailed);
    }
    $scope.getAllStates();


    $scope.states = [];
    function stateQuerySucceeded(data) {
        data.results.forEach(function (item) {
            $scope.states.push(item);
        });
        $scope.$apply();

        console.log("Fetched States");
    }

    function queryFailed(error) {
        console.log("Query failed");
    }

    $scope.select2StateOptions = {
        placeholder: "Choose a State",
        allowClear: true,
        minimumInputLength: 2
    };
}

and here is my html:

<div ng-app="m" id="ng-app">
    ...
    ...

    <select ui-select2="select2StateOptions" ng-model="LocationModel.State">
        <option value=""></option>
        <option ng-repeat="state in states" value="{{state.id}}">{{state.name}}</option>
    </select>
</div>

Currently the html select2 control loads up when the page loads. But I want to have it so when I type in more than 2 characters, I'll be able to make the call to $scope.getAllStates(); as an ajax call. BreezeJS already uses ajax natively when configuring the BreezeAdapter for webapi.

I was thinking about using select2's ajax, or query calls.. but I'd rather use breeze to fetch the data, since it makes querying extendable, and I don't want to violate my design pattern, or make the code harder to maintain, and I don't want the ajax calls to be made everytime I enter a new character into the textbox, I just want it to occur once.

Close attempt:

changed my html to:

<!-- Select2 needs to make this type="hidden" to use query or ajax, then it applies the UI skin afterwards -->
<input type="hidden" ui-select2="select2StateOptions" ng-model="LocationModel.State" /><br />

in my controller, changing select2StateOptions:

$scope.select2StateOptions = {
    placeholder: "Choose a State",
    allowClear: true,
    minimumInputLength: 2,
    query: function (query) {
        debugger;
        var data = StateContext.getAllStates().then(stateQuerySucceeded).fail(queryFailed);
    }
};

Here's the problem. BreezeJS uses a Q library, which makes use of a thing called a "promise"; which is a promise that data will be returned after making the ajax call. The problem with this, the query function is expecting data to be populated, but the promise to call the "stateQuerySucceeded" function is made after returning from the query function.

So it hits the query function first. Then hits getAllStates(). Returns from the query (nothing is populated), then "stateQuerySucceeded" is called after that.

In otherwords, even though I have been able to fetch data, this is done too late.. select2's query function did not receive the data at the right time, and my html select is hanging on "Searching ... " with a search spinner.gif.

Assertive answered 5/2, 2014 at 19:33 Comment(0)
P
3

I don't really know this angular-ui-select2 control. I think the relevant part of the documentation is this example:

$("#e5").select2({
    minimumInputLength: 2,
    query: function (query) {
       var data = {results: []}, i, j, s;
        // simulate getting data from the server
        for (i = 1; i < 5; i++) {
            s = "";
            for (j = 0; j < i; j++) {s = s + query.term;}
            data.results.push({id: query.term + i, text: s});
        }
        query.callback(data);
    }
});

I will leave aside the fact that you don't seem to be interested in using the two-or-more characters that the user enters in your query (maybe you just left that out). I'll proceed with what seems to me to be nonsense, namely, to fetch all states after the user types any two letters.

What I think you're missing is the role of the query.callback which is to tell "angular-ui-select2" when the data have arrived. I'm guessing you want to call query.callback in your success function.

$scope.select2StateOptions = {
    placeholder: "Choose a State",
    allowClear: true,
    minimumInputLength: 2,
    query: function (query) {
        StateContext.getAllStates()
                    .then(querySucceeded).catch(queryFailed);

       function querySucceeded(response) {
            // give the {results:data-array} to the query callback
            query.callback(response);
       }

       function queryFailed(error) {
           // I don't know what you're supposed to do.
           // maybe return nothing in the query callback?
           // Tell the user SOMETHING and then
           query.callback({results:[]});      
       }
    }
};

As I said, I'm just guessing based on a quick reading of the documentation. Consider this answer a "hint" and please don't expect me to follow through and make this actually work.

Personality answered 7/2, 2014 at 8:9 Comment(4)
Hey Ward! Thanks for noticing my question, I've favorited a lot of your responses. This is certain a great response, select2-ui is an angularjs wrapper for select2 dropdown. I made progress using your code, but I was wondering if it's possible to switch between using server side data to load once, then fill up the local browser cache, then use that instead? I am pretty close to getting this solved.Assertive
Like have the data fill in the browser cache, and have breezejs query and filter that? If you noticed in my code getAllStates(), this function has no where clause. So it doesn't simulate autocomplete, each time I press a button, I'd expect breezejs to filter down results. That's the reason why I want to use a cache since I don't want to make a call to the server each time on a button press for filtering results (this is what select2 does if you use ajax, but if you use local data on a page load, then it filters on whatever data you pass it locally).Assertive
found the answer and read up on the breeze docs. I'll post my final solution soon. #16393101Assertive
Glad you're making progress. You absolutely can adjust whether Breeze responds with remote data or cached data ... as I gather you have learned. Looking forward to your findings.Personality

© 2022 - 2024 — McMap. All rights reserved.