AngularJs ng-model does not work with jQuery autocomplete - javascript

JSFiddle: https://jsfiddle.net/ealonwang/7y25ru40/
I am implementing the typeahead function. It seems that ng-model does not work with autocomplete.
Here is my code.
HTML:
<input type="text" id="origin" ng-model="searchForm.origin" placeholder="City, State">
AngularJS:
var origin = ["DALLAS, TX", "DALLAS, NE"];
$("#origin").autocomplete({
source: origin,
autoFocus: true,
delay: 0,
minLength: 3
});
When I type DAL in the input and select DALLAS, TX from the dropdown list, I actually get DAL for ng-model. Anyone has a solution? Thanks in advance.

I have to create a directive for this.
app.directive("autoComplete", function ($timeout) {
return {
restrict: "A",
link: function (scope, element) {
var location = ["OMAHA, NE", "OMAHA, TX", "DALLAS, TX", "DALLAS, NE"];
element.autocomplete({
source: location,
autoFocus: true,
delay: 0,
minLength: 3,
select: function () {
$timeout(function () {
element.trigger("input");
}, 0);
}
});
}
}
});

Related

Javascript Autocomplete Inputfield Variables

how can I use a value of an autocomplete inputfield in a second-inputfield?
I wrote this script below but I dont know how to use the variables from the first in the second-inputfield? the first-field responses:
[{"label":"TEST","value":1}]
and i need the "value" for the second field..
$(document).ready(function(){
jQuery('#first-field').autocomplete({
source:'',
minLength: 2,
delay:0,
select: function(event, ui) {
event.preventDefault();
$("first-field").val(ui.item.label);
},
focus: function(event, ui) {
event.preventDefault();
$("#first-field").val(ui.item.label);
}
});
jQuery('#second-field').autocomplete({
source:'',
minLength: 2,
delay:0,
select: function(event, ui) {
event.preventDefault();
$("second-field").val(ui.item.label);
},
focus: function(event, ui) {
event.preventDefault();
$("#second-field").val(ui.item.label);
}
});
});
Try This
<label for="FirstField">First Name</label> <input id="FirstField" />
<br />
<br />
<label for="SecondField">Second Name</label> <input id="SecondField" />
$(function() {
$('#FirstField, #SecondField').autocomplete({
source: [
"Some Name",
"Value",
"Place"
],
minLength: 0,
select: function( event, ui ) {
$('#SecondField').val(ui.item.value);
$('#FirstField').val(ui.item.value);
return false;
}
})
.focus(function()
{
var self = this;
window.setTimeout(function()
{
if (self.value.length == 0)
$(self).autocomplete('search', '');
});
})
$('#FirstField').change(function() {
$('#SecondField').val($(this).val());
});
});

How to trigger ng-change on md-select when model is changed?

I'm using md-select and need to trigger certain code when the value changes (building a Country/State selector). I have it working fine when I change the value through the control but I also need to have the controls reflect the values properly when the model is changed from code. I'm using ng-change to trigger the change code (need ng-change as user can change the value from the keyboard without clicking on it). The problem is that when the value is changed from the code, the event isn't fired. To complicate things a bit more, the md-selects live in a directive to allow me to use the setup in several places.
Here's my directive template:
<md-input-container class="md-block">
<label>Country</label>
<md-select name="country" ng-model="countryState.countryModel" ng-change="countryState.onCountrySelected()">
<md-option ng-repeat="country in countryState.countries" ng-value="country.itemId">
{{ country.name | translate }}
</md-option>
</md-select>
</md-input-container>
<md-input-container class="md-block">
<label>State</label>
<md-select name="state" ng-model="countryState.stateModel" ng-disabled="countryState.countryModel == null">
<md-option ng-repeat="state in countryState.states" ng-value="state.itemId">
{{ state.name | translate }}
</md-option>
</md-select>
</md-input-container>
Here's the directive code:
angular.module('myapp.shared')
.directive('countryStateInput', [function () {
return {
restrict: 'E',
templateUrl: 'app/shared/inputs/countryState/country-state-input.directive.html',
transclude: false,
scope: {
coordsForm: '=',
countryModel: '=',
stateModel: '='
},
bindToController: true,
controllerAs: 'countryState',
controller: ['$scope', '$document', 'OptionService', function($scope, $document, OptionService) {
var ctrl = this;
// Properties
ctrl.countries = null;
ctrl.states = [];
ctrl.selectedCountryIndex = null;
ctrl.onCountrySelected = function() {
// Get the index of the country
for (var i = 0; i < ctrl.countries.length; i++) {
if (ctrl.countryModel === ctrl.countries[i].itemId) {
// If a different country was chosen, clear the selected state
if (i !== ctrl.selectedCountryIndex) {
ctrl.stateModel = null;
angular.copy(ctrl.countries[i].states, ctrl.states);
};
// Save the index of the selected country
ctrl.selectedCountryIndex = i;
return;
}
}
};
// Initialization
var initialize = function () {
OptionService.getCountries().then(
function (result) {
ctrl.countries = result;
});
};
(function () {
$document.ready(function () {
initialize();
})
})();
}]
}
}]);
Here's a usage example:
<country-state-input country-model="app.location.countryId" state-model="app.location.stateId" input-form="locationsForm"></country-state-input>
And OptionService.getCountries() returns something like this (states lists are shortened):
[
{
"itemId": 1,
"name": "CA",
"states": [
{
"itemId": 1,
"name": "CA_ON",
"abbreviation": "ON"
},
{
"itemId": 2,
"name": "CA_QC",
"abbreviation": "QC"
}
]
},
{
"itemId": 2,
"name": "US",
"states": [
{
"itemId": 14,
"name": "US_AL",
"abbreviation": "AL"
},
{
"itemId": 15,
"name": "US_AK",
"abbreviation": "AK"
}
]
}
]
Basically, I'm trying to figure out if there's a way to trigger onCountrySelected that will cover all 3 use cases.
You could use $scope.$watch
$scope.$watch(
function valueGetter(){
return smth;
},
function onChange(newSmth, oldSmth){
}
)

Angular scope.$watch not working in directive

I have an angular directive with a scope.$watch which is not working, but I know the value is changing. Here's the directive:
var StepFormDirective = function ($timeout, $sce, dataFactory) {
return {
replace: false,
restrict: 'AE',
scope: {
currentStep: "=",
title: "="
},
template: '<h3>{{title}}</h3><form class="step-form"></form>',
compile: function (tElem, attrs) {
return function (scope, elem, attrs) {
scope
.$watch(
function(){return scope.currentStep;},
function (newValue) {
var stepFormFields = Array();
stepFormFields.push($sce.trustAsHtml("<p>Fields</p>"));
alert(scope.currentStep);
}
);
};
}
};
};
Here's the tag:
<div title="'test'" currentStep="currentContext.currentStep"></div>
I know the currentContext.currentStep is changing, because I also have this on my page and it updates:
<pre>{{currentContext.currentStep | json}}</pre>
The alert gets called the first time, but then when the value changes (evidenced by the bit in the pre tags) the alert does not get called again and I have no js console errors.
The output for the step (it's data type) is:
{
"identifier": "830abacc-5f88-4f9a-a368-d8184adae70d",
"name": "Test 1",
"action": {
"name": "Approval",
"description": "Approve",
"instructions": "Select 'Approved' or 'Denied'",
"validOutcomes": [
{
"outcome": "Approved",
"display": "Approved",
"id": "Approved"
},
{
"outcome": "Denied",
"display": "Denied",
"id": "Denied"
}
]
...
Try to use $watch method with third argument set by true (objectEquality):
$watch('currentStep', function(newValue){...}, true);
Or use $watchCollection:
$watchCollection('currentStep', function(newValue){...})

Typeahead shows results as undefined

I am trying to use typeahead to display google suggestions.
The Ajax call works fine and data is returned properly:
Before executing return process(data);
data contains an array of strings that start with "w".
data = ["walmart", "weather", "wells fargo", "worldstarhiphop",
"walgreens", "wikipedia", "white pages", "world cup", "webmd",
"weather radar"]
However the suggestions displayed show "undefined" instead of real words.
Any idea what I am missing here? Thanks.
<input type="text" class="typeahead" placeholder="Search">
$('.typeahead').typeahead({
hint: true,
highlight: true,
minLength: 1
},
{
source: function (query, process) {
$.getJSON("Home/Suggest", { query: query }, function (data) {
return process(data);
});
}
});
Update:
After some research, I found an answer to my question and will post it here, if someone needs it.
The trick is - the "process" callback function expects the results in a format:
[{value: "string1"}, {value: "string2"}, {value: "string3"}]
and not just an array of strings.
$('.typeahead').typeahead(
{ hint: true, highlight: true, minLength: 1 }, // options
{
source: function (query, process) { // source dataset, data = array of strings
$.getJSON('Home/Suggest', { query: query }, function (data) {
//data=["string1", "string2", "string3"]
//process callback function needs it
//in a format [{value: "string1"}, {value: "string2"}, {value: "string3"}]
var output = $.map(data, function (string) { return { value: string }; });
process(output);
});
}
});

Use different value from JSON data instead of displayKey using Typeahead

I have started using Typeahead.js and am struggling to figure out a way of allowing a user to type and search for a company name, once selected input the associated company code.
.json file:
[{
"company_name": "Facebook",
"code": "fb",
}, {
"company_name": "Google",
"code": "goog",
}, {
"company_name": "Yahoo",
"code": "yhoo",
}, {
"company_name": "Apple",
"code": "aapl",
}, {
"company_name": "Royal Mail",
"code": "rmg.l",
}]
.js Script:
var stocks = new Bloodhound({
datumTokenizer: function(d) {
return Bloodhound.tokenizers.whitespace(d.code);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
limit: 3,
prefetch: {
url: 'javascripts/stockCodes.json',
filter: function(list) {
return $.map(list, function(stock) {
return {
code: stock
};
});
}
}
});
stocks.initialize();
$('.typeahead').typeahead(null, {
name: 'stocks',
displayKey: 'code',
source: stocks.ttAdapter()
});
Currently, this just displays the list of codes when the user types in the input field. However, I would like to know if there is a way to allow them to search on code but once selected, the value in the textbox to be company_name? Is this even possible using this plugin. Any help will be greatly appreciated.
Thanks!
displayKey is used to indicate which key should be shown in the dropdown list and in the input field after the user has selected an entry.
If you want the entries shown in the dropdown list to be different from what ends up in the input field you need to use a custom template.
From the documentation:
suggestion – Used to render a single suggestion. If set, this has to be a
precompiled template. The associated suggestion object will serve as the
context. Defaults to the value of displayKey wrapped in a p tag i.e.
<p>{{value}}</p>.
Something like this should work:
$('.typeahead').typeahead(null, {
name: 'stocks',
displayKey: 'company_name',
source: stocks.ttAdapter(),
templates: {
suggestion: function (stock) {
return '<p>' + stock.code + '</p>';
}
}
});
The submitted template will be used to show stock.code in the dropdown list, while stock.company_name will be shown in the input field after the user selects an entry.
I am no jQuery / JavaScript-Guru. So I prefer it the easy way. Just to understand what I did when I look at my code later...
Thanks to Eric Saupe I found a smart solution:
//JSON FILE
[
{
"company_name": "Facebook",
"code": "fb",
},
{
"company_name": "Google",
"code": "goog",
},
{
"company_name": "Yahoo",
"code": "yhoo",
},
{
"company_name": "Apple",
"code": "aapl",
},
{
"company_name": "Royal Mail",
"code": "rmg.l",
},
]
// JS SCRIPT
var stocks = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('company_name'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: 'javascripts/stockCodes.json'
});
stocks.initialize();
$('.typeahead').typeahead(
null, {
name: 'stocks',
displayKey: 'company_name',
source: stocks.ttAdapter()
}).on('typeahead:selected', function(event, data){
$('.typeahead').val(data.code);
});
Just use the typeahead custom events as described in the docs on github.
Hope, this helps (and when it's just for my own later reference).
JSFIDDLE
You need to use the ´displayKey´ variable. See below copy-paste from the typeahead docs:
displayKey – For a given suggestion object, determines the string representation of it. This will be used when setting the value of the input control after a suggestion is selected. Can be either a key string or a function that transforms a suggestion object into a string. Defaults to value.
https://github.com/twitter/typeahead.js/blob/master/doc/jquery_typeahead.md
Your specific code would then be something like this:
var stocks = new Bloodhound({
datumTokenizer: function(d) { return Bloodhound.tokenizers.whitespace(d.code); },
queryTokenizer: Bloodhound.tokenizers.whitespace,
limit: 3,
prefetch: {
url: 'javascripts/stockCodes.json',
filter: function(list) {
// This should not be required, but I have left it incase you still need some sort of filtering on your server response
return $.map(list, function(stock) { return { code: stock.code, company_name: stock.company_name }; });
}
}
});
stocks.initialize();
$('.typeahead').typeahead(null, {
name: 'stocks',
displayKey: function(stock) {
return stock.company_name;
},
source: stocks.ttAdapter()
});
I had a very similar requirement on my own site, and I had this working with no issue. I have not tried this particular example though, so let me know if it works.
Remember to call stocks.clearPrefetchCache(); to clear your cache to easier track bugs.
If I read correctly, I believe this is what you want:
var stocksData = [{
"Facebook": "fb",
}, {
"Google": "goog",
}, {
"Yahoo": "yhoo",
}, {
"Apple": "aapl",
}, {
"Royal Mail": "rmg.l",
}, ];
var stocks = new Bloodhound({
datumTokenizer: function (d) {
for (var prop in d) {
return Bloodhound.tokenizers.whitespace(d[prop]);
}
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
limit: 3,
local: stocksData,
});
stocks.initialize();
$('input').typeahead(null, {
name: 'stocks',
displayKey: function(stocks) {
for (var prop in stocks) {
return prop;
}
},
source: stocks.ttAdapter()
});
You will of course want to change the local to prefetch/remote like you had for the json file.
UPDATE
FIDDLE
Had the same problem, could not get it working with the display or displayKey property. Worked around it by adding the name property to the source objects:
$.each(stocks, function (i, item) {
item.name = item.company_name;
});
Add the value that need to be shown in the display parameter:
$(".comp-search").typeahead({
hint: true,
highlight: true,
minLength: 1,
displayKey: 'company_name',
}, {
source: engine.ttAdapter(),
templates: {
empty: ['<div class="list-group search-results-dropdown"><div class="list-group-item">Nothing found.</div></div>'],
header: ['<div class="list-group search-results-dropdown">'],
suggestion: function (data) {
return '' + data.company_name + '';
}
},
display: function(data) { return data.company_name; }
});
Hope this is helpful

Categories