I'm using a JS plugin called EasyAutocomplete to handle advanced autosuggestion on my website. It supports remote data sets (JSON, XML, plain text) and uses ajax method calls to search, sort and match the response phrase.
I'm trying to write a custom match formula, which it allows me to do, but would love some help writing this out.
Right now, as you type, it searches for any part of a word/phrase for what you're typing. I'd like to have it only check the beginning of each word in a single or multi-word phrase.
Let's say my remote data set contains:
"Market Street"
"Market Thestreet Lane"
"Street South Road"
If my search is "Street", by the plugin's default functionality, all 3 results will show, because the word "street" is part of each of those 3 results. (Demo Below)
I found a similar question to mine on the plugin's Github repo (and the answer from the plugin author), but that question was for matching only the beginning of each result item (not each word in each result item). So if my search is "Street", only the third result will show. (Demo Below)
My question is for a match formula that searches the beginning of any word in a result item. So, if my search is "Street", the first and third result will show.
Here is how the plugin functions by default (I've commented out the custom match formula):
jQuery(document).ready(function($) {
var options = {
data: [
{"name": "Market Street", "parent": "Dog Management", "link": "https://marketstreet.com"},
{"name": "Market Thestreet Lane", "parent": "Dog Management", "link": "https://marketlane.com"},
{"name": "Street South Road", "parent": "Cat Management", "link": "https://streetsouthroad.com"}
],
getValue: "name",
list: {
match: {
enabled: true,
/*
method: function(element, phrase) {
if(element.indexOf(phrase) === 0) {
return true;
} else {
return false;
}
}
*/
},
onSelectItemEvent: function() {
var selectedItemValue = $("#companies").getSelectedItemData().parent;
var selectedItemLink = $("#companies").getSelectedItemData().link;
$("#company_result").html('Contact '+selectedItemValue+'').trigger("change");
},
},
template: {
type: "description",
fields: {
description: "parent"
}
}
};
$("#companies").easyAutocomplete(options);
});
<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css'>
<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css'>
<link rel='stylesheet' href='https://d3vv6lp55qjaqc.cloudfront.net/items/040r1I2Y1K3T3w3L3i3E/easy-autocomplete.css'>
<script src='https://code.jquery.com/jquery-1.11.2.min.js'></script>
<script src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js'></script>
<script src='https://d3vv6lp55qjaqc.cloudfront.net/items/2Q0s1P3D0D2i3u0M1m1x/jquery.easy-autocomplete.min.js'></script>
<div class="container">
<br>
<div class="row">
<div class="col-md-8 col-offset-3">
<p>Search for "Street"</p>
<form style="text-align: center; margin: 0 auto;" onkeypress="return event.keyCode != 13;">
<input class="form-control" id="companies" placeholder="Type your resort property name..."/>
<div id="company_result"></div>
</form>
</div>
</div>
</div>
Here is that custom match formula that only matches if the result item begins with the search phrase:
jQuery(document).ready(function($) {
var options = {
data: [
{"name": "Market Street", "parent": "Dog Management", "link": "https://marketstreet.com"},
{"name": "Market Thestreet Lane", "parent": "Dog Management", "link": "https://marketlane.com"},
{"name": "Street South Road", "parent": "Cat Management", "link": "https://streetsouthroad.com"}
],
getValue: "name",
list: {
match: {
enabled: true,
method: function(element, phrase) {
if(element.indexOf(phrase) === 0) {
return true;
} else {
return false;
}
}
},
onSelectItemEvent: function() {
var selectedItemValue = $("#companies").getSelectedItemData().parent;
var selectedItemLink = $("#companies").getSelectedItemData().link;
$("#company_result").html('Contact '+selectedItemValue+'').trigger("change");
},
},
template: {
type: "description",
fields: {
description: "parent"
}
}
};
$("#companies").easyAutocomplete(options);
});
<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css'>
<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css'>
<link rel='stylesheet' href='https://d3vv6lp55qjaqc.cloudfront.net/items/040r1I2Y1K3T3w3L3i3E/easy-autocomplete.css'>
<script src='https://code.jquery.com/jquery-1.11.2.min.js'></script>
<script src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js'></script>
<script src='https://d3vv6lp55qjaqc.cloudfront.net/items/2Q0s1P3D0D2i3u0M1m1x/jquery.easy-autocomplete.min.js'></script>
<div class="container">
<br>
<div class="row">
<div class="col-md-8 col-offset-3">
<p>Search for "Street"</p>
<form style="text-align: center; margin: 0 auto;" onkeypress="return event.keyCode != 13;">
<input class="form-control" id="companies" placeholder="Type your resort property name..."/>
<div id="company_result"></div>
</form>
</div>
</div>
</div>
Thank you!
method: function(element, phrase) {
return !!element.toLowerCase().split(" ").filter((word) => word.indexOf(phrase.toLowerCase()) === 0).length;
}
This function:
Makes the element lowercase (no capitalization woes)
Splits it into its words
Filters out any words that don't start with the given input
If the filtered array has any elements in it, it must match. Otherwise, it doesn't. That's what the .length check there is for.
Related
Please tell me why if you add a value to the search, then the span is not filled? If you leave only the if, then everything works.
I can't understand what the problem might be, since this is a common condition, if the code is found, then it displays the country in the span.
Without the else:
var phones = [{
"country": "UA",
"code": "+380"
},
{
"country": "RU",
"code": "+7"
},
{
"country": "MD",
"code": "+373"
}
];
$(".phone").keyup(function() {
var val = $(this).val();
phones.find(function(phones) {
if (phones.code == val) {
$("#county").text(phones.country);
}
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span id="county"></span>
<input type="text" class="phone">
With else:
var phones = [{
"country": "UA",
"code": "+380"
},
{
"country": "RU",
"code": "+7"
},
{
"country": "MD",
"code": "+373"
}
];
$(".phone").keyup(function() {
var val = $(this).val();
phones.find(function(phones) {
if (phones.code == val) {
$("#county").text(phones.country);
} else {
$("#county").text("no");
}
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span id="county"></span>
<input type="text" class="phone">
Because .find is looping through all your objects. If you search for lets say +7, it will show no then RU then no again, so it overwrites the value while without the else it gets in once and stays like that without replacing.
The solution here is to finish with your find first, and then do the if.
I would do something like this:
var phones = [{
"country": "UA",
"code": "+380"
},
{
"country": "RU",
"code": "+7"
},
{
"country": "MD",
"code": "+373"
}
];
$(".phone").keyup(function () {
var val = $(this).val();
var phoneObj = phones.find(x => x.code == val);
if (phoneObj == undefined) {
$("#county").text("no");
}
else {
$("#county").text(phoneObj.country);
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span id="county"></span>
<input type="text" class="phone">
That is because the next value is not same as the input code, so the else loop is executed & shows no.
Explanation: The array method iterates all the object. So when the input matches it shows the country. But when the next object is under iteration it does not match with the input value. Hence the previous text is over written.
The input is +7 and matches with the code and showing the country. But immediately after that +7 is compared with next object and the code does not match. So else loop is executed.
When using only if nothing happens when the code and input value does not match.
I need to filter a list by their category, but each of these categories are custom, every user can write any category they want in the list.
I tried to make a list with ng-repeat in which I filtered all the duplicated values (In the code below is the unique:'Category' part), and I gave them the Category name as the value to filter, also I added an "All" category to show all the elements:
<ul class="categoriesList">
<li>
<label>
<input type="radio" ng-model="searchCategory.Category" value=""> All
</label>
</li>
<li ng-repeat="x in myList | unique:'Category'">
</label>
<input type="radio" ng-model="searchCategory.Category" value="{{x.Category}}"> {{x.Category}}
</label>
</li>
</ul>
But this approach isn't working. I've made a Plunker as example: Here is my plunker
I need to be able to add any category I want in the json example, and to be able of filter them. Thanks in advance.
OK the issue with your code was just that your property searchCategory was not defined on the $scope. Adding $scope.searchCategory = {}; to your controller will solve the issue. And the reason behind this is that ng-repeat creates its own child scope. Below is a snippet with the working solution.
Also one more thing was missing i.e you need to have the same group for all the radio buttons so that only one is selected at a time and that you do by adding name='filter' attribute all radio button.
var app = angular.module('app', []);
//duplicates filter
app.filter('unique', function() {
return function(items, filterOn) {
if (filterOn === false) {
return items;
}
if ((filterOn || angular.isUndefined(filterOn)) && angular.isArray(items)) {
var hashCheck = {},
newItems = [];
var extractValueToCompare = function(item) {
if (angular.isObject(item) && angular.isString(filterOn)) {
return item[filterOn];
} else {
return item;
}
};
angular.forEach(items, function(item) {
var valueToCheck, isDuplicate = false;
for (var i = 0; i < newItems.length; i++) {
if (angular.equals(extractValueToCompare(newItems[i]), extractValueToCompare(item))) {
isDuplicate = true;
break;
}
}
if (!isDuplicate) {
newItems.push(item);
}
});
// items = newItems;
}
return newItems;
};
});
app.controller('MainCtrl', function($scope) {
$scope.searchCategory = {};
$scope.myList = [{
"Category": "My custom category",
"Title": "Title example",
"Comments": "Example comments"
},
{
"Category": "My custom category",
"Title": "My cat is named George",
"Comments": "Example comments"
},
{
"Category": "My custom category",
"Title": "Hocus pokus",
"Comments": "Example comments"
},
{
"Category": "My custom category",
"Title": "Tyrion Lannister must have been king",
"Comments": "Example comments"
},
{
"Category": "My custom category",
"Title": "some text",
"Comments": "Example comments"
},
{
"Category": "Some new category",
"Title": "7 projects going LIVE now",
"Comments": "Example comments"
},
{
"Category": "Some new category",
"Title": "Batman vs Superman was a good movie",
"Comments": "Example comments"
},
{
"Category": "Some new category",
"Title": "Youtube channel projects",
"Comments": "Example comments"
},
{
"Category": "Some new category",
"Title": "Some project name",
"Comments": "Example comments"
},
{
"Category": "Some new category",
"Title": "projects (more)",
"Comments": "Example comments"
},
{
"Category": "A different category",
"Title": "Remember, remember the fifth of november",
"Comments": "Hello there!"
},
{
"Category": "A different category",
"Title": "It's night, electric night",
"Comments": "General Kenobi"
},
{
"Category": "Custom category",
"Title": "project name again",
"Comments": "Example comments"
}
];
});
<!DOCTYPE html>
<html ng-app="app">
<head>
<meta charset="utf-8" />
<title>AngularJS Filter with custom values</title>
<script>
document.write('<base href="' + document.location + '" />');
</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js#1.4.x" src="https://code.angularjs.org/1.4.12/angular.js" data-semver="1.4.9"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<ul class="categoriesList">
<li>
<label>
<input name="filter" type="radio" ng-model="searchCategory.Category" ng-value=""> All
</label>
</li>
<li ng-repeat="x in myList | unique:'Category'">
<label>
<input name="filter" type="radio" ng-model="searchCategory.Category" ng-value="x.Category"> {{x.Category}}
</label>
</li>
</ul>
<div class="wrapper" ng-repeat="y in myList | filter:searchCategory:true">
<ul class="click-text">
<li>{{y.Title}} - {{y.Comments}}</li>
</ul>
</div>
</body>
</html>
Hope this helps :)
You have to write Ex- name="rdoCategory" attribute for radio button
`<ul class="categoriesList">
<li>
<label>
<input type="radio" name="rdoCategory" ng-model="searchCategory.Category" value=""> All
</label>
</li>
<li ng-repeat="x in myList | unique:'Category'">
</label>
<input type="radio" name="rdoCategory" ng-model="searchCategory.Category" value="{{x.Category}}"> {{x.Category}}
</label>
</li>
</ul>`
After that it will work.
I have created a simple form using alpacajs, as per the documentation provided by alpacajs.org we can use properties such as optionsSource, schemaSource, viewSource, dataSource for loading the external json files into our application. But what i need is i need to use only one json file for all these. I mean instead of using all these 3 different properties can i use only one parameter for loading the single json file which comes from the backend. please check my code below..
<html>
<head>
<meta charset="UTF-8">
<title>My Little Alpaca Form</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.5/handlebars.min.js"> </script>
<script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
<script src="//code.cloudcms.com/alpaca/1.5.22/bootstrap/alpaca.min.js"></script>
<!-- typeahead.js https://github.com/twitter/typeahead.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/typeahead.js/0.10.5/bloodhound.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/typeahead.js/0.10.5/typeahead.bundle.min.js"></script>
<link href="//code.cloudcms.com/alpaca/1.5.22/bootstrap/alpaca.min.css" rel="stylesheet" />
<link type="text/css" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet"/>
</head>
<body>
<div id="form1"></div>
<script type="text/javascript">
$(document).ready(function() {
$("#form1").alpaca({
"dataSource": "/fulfiller/connector-custom-data.json",
"schemaSource": "/fulfiller/connector-custom-schema.json",
"optionsSource": "/fulfiller/connector-custom-options.json",
"viewSource": "/fulfiller/connector-custom-view.json",
"view": {
"parent": "bootstrap-edit",
"layout": {
"template": "threeColumnGridLayout",
"bindings": {
"requestedfor": "column-1",
"location": "column-2",
"shortdescription": "column-3",
"description": "column-3",
}
},
"templates": {
"threeColumnGridLayout": '<div class="row">' + '{{#if options.label}}<h2>{{options.label}}</h2><span></span>{{/if}}' + '{{#if options.helper}}<p>{{options.helper}}</p>{{/if}}' + '<div id="column-1" class="col-md-6"> </div>' + '<div id="column-2" class="col-md-6"> </div>' + '<div id="column-3" class="col-md-12"> </div>' + '<div class="clear"></div>' + '</div>'
}
},
"options": {
"fields": {
"requestedfor": {
"type": "text",
"id": "requestedfor",
"label": "*Requested For",
"typeahead": {
"config": {},
"datasets": {
"type": "remote",
"displayKey": "value",
"templates": {},
"source": "http://www.alpacajs.org/data/tokenfield-ajax1.json"
}
}
},
"location": {
"type": "text",
"label": "*Location"
},
"shortdescription": {
"type": "text",
"label": "Short Description"
},
"description": {
"type": "textarea",
"rows": 5,
"cols": 40,
"label": "Description"
}
},
"form": {
"attributes": {
"action": "#",
"method": "post"
},
"buttons": {
"submit": {
"value": "Submit",
"class": "btn btn-default"
}
}
}
}
});
});
</script>
</body>
</html>
So here in the above code i have used these urls for loading json data..
"dataSource": "/fulfiller/connector-custom-data.json"
"schemaSource": "/fulfiller/connector-custom-schema.json"
"optionsSource": "/fulfiller/connector-custom-options.json"
"viewSource": "/fulfiller/connector-custom-view.json"
So instead of using these 3 different properties can i use only one property like "oneSingleJSONSource": "oneJSONRemoteFile.json"
Can anybody provide inputs?
For Alpaca to be inialized it must have a DOM element + a config object that contains the schema, options and other properties that Alpaca already knew in its core code, so in your case this is possible if you try to modify the alpaca core code... If your objective is only to optimize resource loading you can use only one json file that contains all the configuration and input them in the alpaca initialization $(dom).alpaca(_json_data_from_loaded_file). And if you want to have only schema, options and view settings in on file you should divide the data loaded to 3 objects, 1 for schema, 1 for options and the last one for view settings.
Tell me if you want more details on achieving this.
I'll be glad to help.
I'm trying to conditionally show and hide columns based on the data returned, if the data set contains any objects meeting conditions.
Here is a sample of the data returned from my search results
[
{
"id": "typeahead-241-1091-option-0",
"label": "Android Home Page",
"model": {
"type": "link",
}
},
{
"id": "typeahead-241-1091-option-1",
"label": "Google",
"model": {
"type": "link",
}
},
{
"id": "typeahead-241-1091-option-2",
"label": "Forgotten Google Play Password",
"model": {
"type": "kb",
}
}
]
Now I'm presenting the data in columns, based on the type.
<div class="flexitem">
<h4>External Links</h4>
<div ng-repeat="match in matches" ng-if="match.model.type == 'link'">{{match.label}}</div>
</div>
<div class="flexitem">
<h4>Knowledge Base</h4>
<div ng-repeat="match in matches" ng-if="match.model.type == 'kb'">{{match.label}}</div>
</div>
<!-- the below has no results. I want it hidden altogether
currently it shows the <h4>Products</h4> with nothing below it-->
<div class="flexitem">
<h4>Products</h4>
<div ng-repeat="match in matches" ng-if="match.model.type == 'product'">{{match.label}}</div>
</div>
What I need to accomplish is putting conditions on the flexitem divs altogether to only show if there are results for that type. So if there are no results with the type == 'product', then don't even show that div. A ng-if on that row would work, but what will be the best way to cycle through all of the children of match to determine if there is a result? indexOf doesn't work through children arrays.
Put the logic on the angular side using Array.filter to separate arrays;
Angular controller:
$scope.linkMathches = $scope.matches.filter(function(m){
return m.model.type === 'link'
});
$scope.kbMathches = $scope.matches.filter(function(m){
return m.model.type === 'kb'
});
HTML:
<div class="flexitem" ng-if="linkMathches.length">
<h4>External Links</h4>
<div ng-repeat="match in linkMathches">
{{match.label}}
</div>
</div>
<div class="flexitem" ng-if="kbMathches.length">
<h4>Knowledge Base</h4>
<div ng-repeat="match in kbMathches">
{{match.label}}
</div>
</div>
Going further for dynamic values in model.type:
Angular controller:
$scope.typeMatches = {
link: {title: 'External Links', matches: []},
kb: {title: 'Knowledge Base', matches: []},
product: {title: 'Products', matches: []}
};
$scope.matches.forEach(function(match){
$scope.typeMatches[match.model.type].matches.push(match);
});
HTML:
<div class="flexitem"
ng-if="value.matches.length"
ng-repeat="(key,value) in typeMatches">
<h4>{{value.title}}</h4>
<div ng-repeat="match in value.matches">
{{match.label}}
</div>
</div>
I have an AngularJS app that is searching through a list of services, which then displays the results below the search bar using ngRepeat. The right amount of search results are being displayed, but the bindings are being filled with nothing, leaving the anchor tags empty.
Here is the markup being generated:
<h4 class="media-heading result-heading"></h4>
<p class="result-abstract"></p>
<p></p>
<p></p>
<div class="row">
<div class="col-all-12 result-contact">
<div class="">
<span class="icon-office"></span>
</div>
<div class="">
<span class="icon-phone"></span>
</div>
</div>
</div>
I have the exact same code running on my local machine, it works as expected, and the versions of AngularJS are the same, so I have no clue what is going on. Has anyone experienced this before, and how have hey resolved it?
No errors are thrown, the data is being retrieved via AJAX from a json file.
Here is the template with the ngRepeats in it:
<div class="media-body search-result" ng-repeat="service in services | filter: query | filterTags: allTags | orderBy: serviceOrder:direction | hasValue | startFrom:(currentPage - 1) * servicesPerPage | limitTo:servicesPerPage">
<h4 class="media-heading result-heading">{{service.serviceName}}</h4>
<p class="result-abstract">{{service.abstract}}</p>
</p>
</p>
<div class="row">
<div class="col-all-12 result-contact">
<div class="">
<span class="icon-office"></span> {{service.office}}
</div>
<div class="">
<span class="icon-phone"></span> {{service.contact}}
</div>
</div>
</div>
</div>
The code behind this isn't really all that important since it is an exact copy of everything from one page to another, and it works on the previous page. There are no differences in the HTML, CSS or Javascript, other than the fact that the markup on the new page is filled in with blanks.
I have already checked to make sure that the json data is being pulled in correctly, and that the versions of AngularJS are the same (Version is 1.2.21).
Here are the custom filters:
serviceSearchbar.filter('startFrom', function () {
return function (input, start) {
start = +start; //parse to int
return input.slice(start);
}
});
serviceSearchbar.filter('hasValue', function() {
return function (input) {
if (angular.element('#servicesQuery').val()) {
return input;
}
return [];
}
});
serviceSearchbar.filter("isSelected", function () {
return function (input) {
var response = {};
if (input !== undefined) {
Object.keys(input).forEach(function (tag) {
if (input[tag] !== false) {
response[tag] = true;
}
});
}
return response;
}
});
serviceSearchbar.filter("filterTags", ["$filter", function ($filter) {
return function (input, tags) {
var selectedTags = [],
response = input,
found = false,
numSelectedTags;
if (input === undefined) {
return input;
}
if (tags !== undefined) {
Object.keys(tags).forEach(function (tag) {
if (tags[tag] !== false) {
selectedTags.push(tag);
}
});
}
if (selectedTags.length > 0) {
response = $filter("filter")(response, function(value) {
var found = false;
selectedTags.forEach(function(tag) {
if (value.tags.indexOf(tag) !== -1) {
found = true;
return true;
}
});
return found;
});
}
return response;
}
}]);
And here is an example of the data that is being used:
{
"services": [
{
"serviceName": "Department of Art",
"sponsor": "Department of Art",
"shortName": "art-dep",
"url": "http://www.byui.edu/art/",
"contactName": "Contact Name",
"office": "Spori some room",
"contact": "(123) 456-7890",
"mapUrl": "http://www.byui.edu/maps#SPO",
"tags": [
"art",
"design",
"art department",
"art program"
],
"abstract": "The BYU-Idaho Department of Art provides an aesthetic, conceptual, and technical foundation in the visual arts for students who possess a wide range of interests, experiences, and abilities."
},
{
"serviceName": "College of Performing and Visual Arts",
"sponsor": "College of Performing and Visual Arts",
"shortName": "performing-visual-arts",
"url": "http://www.byui.edu/performing-visual-arts",
"contactName": "Contact Name",
"office": "Spori some room",
"contact": "(123) 456-7890",
"mapUrl": "http://www.byui.edu/maps#SPO",
"tags": [
"art",
"dance",
"music",
"spori",
"theater"
],
"abstract": "The College of Performing and Visual Arts provide an important means of communication where thoughts, creativity, and expression can be directed to ennoble, uplift, inspire..."
},
{
"serviceName": "Spori Art Gallery",
"sponsor": "Department of Art",
"shortName": "spo-gal",
"url": "http://www.byui.edu/spori-gallery/",
"contactName": "Contact Name",
"office": "Spori some room",
"contact": "(123) 456-7890",
"mapUrl": "http://www.byui.edu/maps#SPO",
"tags": [
"art",
"gallery",
"spori"
],
"abstract": "Jacob Spori Art Gallery will show works of current BYU-Idaho Visual Arts students, selected by the art department faculty."
}
]
}