Kendo UI Angular JS and AutoComplete with a service - angularjs

I'm making an Angular App and I'm starting to use some of the Kendo UI controls. I'm having some issues wiring up the AutoComplete control. I would like to use a factory that will return the list of "auto complete" values from my database.
I have iincluded the auto complete control and I'm trying to use the k-options attribute:
<input kendo-auto-complete ng-model="myFruit" k-options="FruitAutoComplete" />
In my controller the following hard coded list of fruits work:
$scope.FruitAutoComplete = {
dataTextField: 'Name',
dataSource:[
{ id: 1, Name: "Apples" },
{ id: 2, Name: "Oranges" }
]
}
When I move this over to use my factory I see it calling and returning the data from the factory but it never get bound to the screen.
$scope.FruitAutoComplete = {
dataTextField: 'Name',
dataSource: new kendo.data.DataSource({
transport: {
read: function () {
return FruitFactory.getYummyFruit($scope.myFruit);
}
}
})
}
I end up with the request never being fulfilled to the auto complete.
My factory is just returning an array of fruit [
my Fruit Factory Code:
getYummyFruit: function (val) {
return $http.get('api/getFruitList/' + val)
.then(function (res) {
var fruits= [];
angular.forEach(res.data, function (item) {
fruits.push(item);
});
return fruits;
});
}

Here is your solution
http://plnkr.co/edit/iOq2ikabdSgiTM3sqLxu?p=preview
For the sake of plnker I did not add $http (UPDATE - here is http://plnkr.co/edit/unfgG5?p=preview with $http)
UPDATE 2 - http://plnkr.co/edit/01Udw0sEWADY5Qz3BnPp?p=preview fixed problem as per #SpencerReport
The controller
$scope.FruitAutoCompleteFromFactory = {
dataTextField: 'Name',
dataSource: new kendo.data.DataSource({
transport: {
read: function (options) {
return FruitFactory.getYummyFruit(options)
}
}
})
}
The factory -
factory('FruitFactory', ['$http',
function($http) {
return {
getYummyFruit: function(options) {
return $http.get('myFruits.json').success(
function(results) {
options.success(results);
});
}
}
}

Related

UI Bootstrap typeahead promise issues with angular-formly

I am using angular-formly with integrated ui bootstrap typeahead to query an api via a service. The promises were resolved properly, but typeaheads list only shows the data of the elapsed input query.
Simple example: I type: "Bern", the service fires four requests for:
query_term=B
query_term=Be
query_term=Ber
query_term=Bern
The promises were resolved properly and in the right order. But typeahead list suggestions with the data from query_term=Ber and not the last query_term=Bern.
Any suggestions?
Here are code details:
Service:
var srv = {
getName: function (query_term) {},
};
srv.getName = function (query_term) {
return $http.get(srv._baseUrl + '/locating/api/autocomplete?query_term=' + query_term)
.then(function(res) {
return res.data;
}).catch(function (err) {
console.log(err);
});
};
return {
getName: function (query_term) {
return srv.getName(query_term);
}
};
(formly) Controller:
{
key: '_name',
type: 'autocompleteNoWildcard',
templateOptions: {
options: [],
onChange: function ($viewValue, $scope) {
if (typeof $viewValue != 'undefined') {
return $scope.templateOptions.options = locationService.getName($viewValue);
};
}
}
},
Html template:
formlyConfig.setType({
name: 'autocompleteNoWildcard',
template: '<input type="text" autocomplete="off" ng-model="model._name" uib-typeahead="item.label for item in to.options" typeahead-no-results="noResults" class="form-control">',
wrapper: ['bootstrapLabel', 'bootstrapHasError'],
});
Put a throttle and start polling when the name is longer to 3 characters.

how to test inner controller which loads data for select control in angular-formly

I have a ui-select field
{
key: 'data_id',
type: 'ui-select',
templateOptions: {
required: true,
label: 'Select label',
options: [],
valueProp: 'id',
labelProp: 'name'
},
controller: function($scope, DataService) {
DataService.getSelectData().then(function(response) {
$scope.to.options = response.data;
});
}
}
How can I access that inner controller in my unit tests and check that data loading for the select field actually works ?
UPDATE:
An example of a test could be as such:
var initializePageController = function() {
return $controller('PageCtrl', {
'$state': $state,
'$stateParams': $stateParams
});
};
var initializeSelectController = function(selectElement) {
return $controller(selectElement.controller, {
'$scope': $scope
});
};
Then test case looks like:
it('should be able to get list of data....', function() {
$scope.to = {};
var vm = initializePageController();
$httpBackend.expectGET(/\/api\/v1\/data...../).respond([
{id: 1, name: 'Data 1'},
{id: 2, name: 'Data 2'}
]);
initializeSelectController(vm.fields[1]);
$httpBackend.flush();
expect($scope.to.options.length).to.equal(2);
});
You could do it a few ways. One option would be to test the controller that contains this configuration. So, if you have the field configuration set to $scope.fields like so:
$scope.fields = [ { /* your field config you have above */ } ];
Then in your test you could do something like:
$controller($scope.fields[0].controller, { mockScope, mockDataService });
Then do your assertions.
I recently wrote some test for a type that uses ui-select. I actually create a formly-form and then run the tests there. I use the following helpers
function compileFormlyForm(){
var html = '<formly-form model="model" fields="fields"></formly-form>';
var element = compile(html)(scope, function (clonedElement) {
sandboxEl.html(clonedElement);
});
scope.$digest();
timeout.flush();
return element;
}
function getSelectController(fieldElement){
return fieldElement.find('.ui-select-container').controller('uiSelect');
}
function getSelectMultipleController(fieldElement){
return fieldElement.find('.ui-select-container').scope().$selectMultiple;
}
function triggerEntry(selectController, inputStr) {
selectController.search = inputStr;
scope.$digest();
try {
timeout.flush();
} catch(exception){
// there is no way to flush and not throw errors if there is nothing to flush.
}
}
// accepts either an element or a select controller
function triggerShowOptions(select){
var selectController = select;
if(angular.isElement(select)){
selectController = getSelectController(select);
}
selectController.activate();
scope.$digest();
}
An example of one of the tests
it('should call typeaheadMethod when the input value changes', function(){
scope.fields = [
{
key: 'selectOneThing',
type: 'singleSelect'
},
{
key: 'selectManyThings',
type: 'multipleSelect'
}
];
scope.model = {};
var formlyForm = compileFormlyForm();
var selects = formlyForm.find('.formly-field');
var singleSelectCtrl = getSelectController(selects.eq(0));
triggerEntry(singleSelectCtrl, 'woo');
expect(selectResourceManagerMock.searchAll.calls.count()).toEqual(1);
var multiSelectCtrl = getSelectController(selects.eq(1));
triggerEntry(multiSelectCtrl, 'woo');
expect(selectResourceManagerMock.searchAll.calls.count()).toEqual(2);
});

Difficulting initializing a Kendo grid in an AngularJS controller

I'm trying to bind some data to a Angular-Kendo UI grid, but as soon as I click on the tab which contains my grid widget I get this error below :
Error: error:unpr
Unknown Provider
Unknown provider: $modalInstanceProvider <- $modalInstance
Description
This error results from the $injector being unable to resolve a required dependency.
I'm not really sure why it's crashing on $modalInstance since I'm trying to wire up my Kendo grid with some data. This is strange.
Here's my HTML :
<div data-ng-controller="GridSettingsCtrl" class="col-md-4">
<div class="col-lg-12">
<h3>Choose a Dimension grouping</h3>
<span id="userDimenGrid" kendo-grid="userDimenGrid"
k-data-source="userDimenGridDS"
k-options="userDimenGridOptions"
k-rebind="userDimenGridOptions" />
</div>
</div>
and here's my Angular controller code :
FYI: the HTML attrib k-data-source="userDimenGridDS" is wired to Kendo DataSource $scope.userDimenGridDS in my js code - which in turn calls read: getUserDimensionsList for the data.
(function () {
'use strict';
angular.module('rage')
.controller('GridSettingsCtrl', ['$scope', '$modalInstance', 'datacontext', 'widget', gridSettings]);
function gridSettings($scope, $modalInstance, datacontext, widget) {
var settings = this;
// add widget to scope
$scope.widget = widget;
// set up result object
$scope.result = jQuery.extend(true, {}, widget);
$scope.ok = function () {
$modalInstance.close($scope.result);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
$scope.userDimenGridOptions = {
selectable: true,
sortable: true,
pageable: true,
columns: [
{ field: "id", width: "10px", hidden: true },
{ field: "dimension", width: "80px", editor: dimenDropDown, template: "#=dimension#" }, // DropDown editor defined !!
{ field: "hierarchy", width: "60px" },
{ command: [{ name: "edit", text: '' }, { name: "destroy", text: '' }], width: 120 }
],
editable: "inline",
toolbar: ["create"],
};
// SERVER-SIDE DIMENSIONS DATA SOURCE - see dimenDropDown()
$scope.dimensionsDataSource = new kendo.data.DataSource({
transport: {
read: getDimensionsFromServer
}
});
// USER DIMENSIONS DATA SOURCE !!
$scope.userDimenData = [];
$scope.userDimenGridDS = new kendo.data.DataSource({
transport: {
read: getUserDimensionsList,
create: function (options) {
options.success(options.data);
}
},
schema: {
model: {
id: "id",
fields: {
dimension: { type: "string" },
hierarchy: { type: "boolean" }
}
}
},
pageSize: 5
});
// SERVER-SIDE DIMENSIONS DATA SOURCE - see dimenDropDown()
$scope.dimensionsDataSource = new kendo.data.DataSource({
transport: {
read: getDimensionsFromServer
}
});
}; // end of gridSettings()
function getUserDimensionsList(options) {
// Retrieve user dimensions list from server or local cache, and return to Kendo Grid DataSource.
// Called by: read option of $scope.dimenUserGridDS
var userDimenFromStorage;
if ($scope.readUserDimensionsFromCache) { // pull from cache
userDimenFromStorage = reportsContext.getUserDimenFromLocalStorage();
}
if ($scope.readUserDimensionsFromCache && userDimenFromStorage != null && userDimenFromStorage.length != 0) {
options.success(userDimenFromStorage);
}
else { // pull from server
datacontext.getDimenUserList().then(function (data) {
if (data.status == 'FAIL') {
if (data.messages.length > 0) {
logErr("Error retrieving user Dimensions list: " + data.messages[0]);
return;
}
}
else {
// RETURN DUMMY REC FOR TESTING...
$scope.userDimenData = data;
options.success($scope.userDimenData);
}
});
}
}
function getDimensionsFromServer(options) {
datacontext.getDimensionsFromServer().then(function (data) {
if (data.status == 'FAIL') {
if (data.messages.length > 0) {
logErr("Error retrieving KRI list: " + data.messages[0]);
return;
}
}
else {
options.success(data.data);
}
});
}
// USER DIMENSION DROPDOWN FOR GRID
function dimenDropDown(container, options) {
$('<input id="dimenDropDown" data-text-field="name" data-value-field="name" data-bind="value:' + options.field + '"/>')
.appendTo(container)
.kendoComboBox({
dataTextField: "name",
dataValueField: "name",
dataSource: $scope.dimensionsDataSource
});
}
})();
and here's a screen shot to show the tab I click on to get my "Dimension Grouping" grid, which of course does not render yet due to the error:
As I mentioned, the error occurs as soon as I click on that "Dimensions" tab - which is when it should try and bind the data to the Kendo grid.
Here's a screen shot of that :
If anyone can see a problem with my code, your advice would be appreciated...
regards,
Bob
I found my problem. I'm using Malhar dashboard widget framework, which specifies the controller in it's settings.
Reference to Malhar dashboard on github: https://github.com/DataTorrent/malhar-angular-dashboard
So my HTML ended up looking like this, without the data-controller attrib:
<div class="col-md-4">
<div class="col-lg-12">
<h3>Choose a Dimension grouping</h3>
<span id="userDimenGrid" kendo-grid="userDimenGrid"
k-data-source="userDimenGridDS"
k-options="userDimenGridOptions"
k-rebind="userDimenGridOptions" />
</div>
</div>

How to manually add a datasource to Angularjs-select2 when select2 is set up as <input/> to load remote data?

I am using the Select2 as a typeahead control. The code below works very well when the user types in the search term.
However, when loading data into the page, I need to be able to manually set the value of the search box.
Ideally something like: $scope.selectedProducerId = {id:1, text:"Existing producer}
However, since no data has been retrieved the Select2 data source is empty.
So what I really need to be able to do is to add a new array of data to the datasource and then set the $scope.selectedProducerId, something like: $scope.producersLookupsSelectOptions.addNewData({id:1, text:"Existing producer}) and then
$scope.selectedProducerId = 1;
Researching this I have seen various suggestions to use initSelection(), but I can't see how to get this to work.
I have also tried to set createSearchChoice(term), but the term is not appearing in the input box.
I would be most grateful for any assistance.
Thanks
This is the html
<div class="col-sm-4">
<input type="text" ui-select2="producersLookupsSelectOptions" ng- model="selectedProducerId" class="form-control" placeholder="[Produtor]" ng-change="selectedProducerIdChanged()"/>
</div>
This is the controller
angular.module("home").controller("TestLookupsCtrl", [
"$scope", "$routeParams", "AddressBookService",
function($scope, $routeParams, AddressBookService) {
$scope.producersLookupsSelectOptions = AddressBookService.producersLookupsSelectOptions();
}
]);
This is the service:
angular.module("addressBook").service("AddressBookService", [
"$http", "$q", function($http, $q) {
var routePrefix = "/api/apiAddressBook/";
//var fetchProducers = function(queryParams) {
// return $http.get(routePrefix + "GetClientsLookup/" + queryParams.data.query).then(queryParams.success);
//};
var _getSelectLookupOptions = function(url, minimumInputLength, idField, textField) {
var _dataSource = [];
var _queryParams;
return {
allowClear: true,
minimumInputLength: minimumInputLength || 3,
ajax: {
data: function(term, page) {
return {
query: term
};
},
quietMillis: 500,
transport: function(queryParams) {
_queryParams = queryParams;
return $http.get(url + queryParams.data.query).success(queryParams.success);
},
results: function(data, page) {
var firstItem = data[0];
if (firstItem) {
if (!firstItem[idField]) {
throw "[id] " + idField + " does not exist in datasource";
}
if (!firstItem[textField]) {
throw "[text] " + textField + " field does not exist in datasource";
}
}
var arr = [];
_.each(data, function(returnedData) {
arr.push({
id: returnedData[idField],
text: returnedData[textField],
data: returnedData
});
});
_dataSource = arr;
return { results: arr };
}
},
dataSource: function() {
return _dataSource;
},
getText: function (id) {
if (_dataSource.length === 0) {
throw ("AddressBookService.getText(): Since the control was not automatically loaded the dataSource has no content");
}
return _.find(_dataSource, { id: id }).text;
}
//initSelection: function(element, callback) {
// callback($(element).data('$ngModelController').$modelValue);
//},
//createSearchChoice:function(term) {
// return term;
//},
addNewData:function(data) {
this.ajax.results(data,1);
};
};
return {
producersLookupsSelectOptions: function() {
var url = routePrefix + "GetClientsLookup/";
return _getSelectLookupOptions(url, 2, "Id", "Name");
},
}
}
]);

AngularJS - self referencing services?

I'm building an Angular app that will have a top-level Controller and a second-level controller. There will be n number of second-level controllers, but I want to put global-level functions someplace. I'm doing this in a service.
I'm starting down the path of creating a single service that return an api, really, containing lots of functions (below). The service is returning an object with two property branches that each contain a set of functions. How can I call one of these from the other?
globalModule.factory('global', function($http) {
var squares = MyApp.squares; // this is the *only* link from Global namespace to this app
return {
squareMgr: {
getSquaresEarned: function() {
return squares.earned;
},
getSquaresPlaced: function() {
return squares.placed;
},
setThisSquareEarned: function(value) {
squares.earned.push(value);
},
setThisSquarePlaced: function(value) {
squares.placed.push(value);
}
},
missionMgr: {
missionInfo: {},
setMissionInfo: function(missionInfo) {
this.missionInfo = missionInfo
},
complete: function(missionData) {
log('complete called on video at ' + new Date());
missionData.complete = true;
log(angular.toJson(missionData));
$http({
url: '/show/completeMission',
method: "POST",
data: missionData
})
.then(function(response) {
if (response.data.success === true) {
log('completeMission success');
// increment squares earned counter
this.squareMgr.setThisSquareEarned(missionData.id);
// above is an attempt to run a function contained in this
// same service in a different parent property branch.
// how *should* I do this?
}
});
}
}
}
});
How about something like this:
globalModule.factory('global', function($http) {
var glob = {
squareMgr: {
// ...
},
missionMgr: {
foo: function() {
glob.squareMgr.xyz();
}
}
};
return glob;
});

Resources