Group results in autocompleted dropdown [Meteor] - javascript

I try to do a dropdown list in my app. First of all I use a Meteor, so that's specific kind of app ofc :)
Second thing is that I use sebdah/meteor-autocompletion package, because I want my results to be sorted in specific way and limited.
The last thing I need is to group my results.
For example: If I have 2 products named "blah" I want to get only 1 "blag" in my dropdown "autocompletion" list.
Some code:
HTML:
<template name="InvoicesEditInsertInsertForm">
<input id="descriptionautocomplete" type="text" name="description" value="" class="form-control" autofocus="autofocus" placeholder="New Item...">
</template>
JS:
Template.InvoicesEditInsertInsertForm.rendered = function() {
AutoCompletion.init("input#descriptionautocomplete");
};
Template.InvoicesEditInsertInsertForm.events({
'keyup input#descriptionautocomplete': function () {
AutoCompletion.autocomplete({
element: 'input#descriptionautocomplete', // DOM identifier for the element
collection: InvoicesItem, // MeteorJS collection object
field: 'description', // Document field name to search for
limit: 5, // Max number of elements to show
sort: { modifiedAt: -1 },
}); // Sort object to filter results with
},
});
I need to use function that could group my "description" here.
I tried to do it in helper and I get it on my screen, but to be honest I don't know how to put that into my dropdown :(
try: function() {
var item= InvoicesItem.find({},{sort:{modifiedAt:-1}}).fetch();
var descriptions={};
_.each(item,function(row){
var description = row.description;
if(descriptions[description]==null)
descriptions[description]={description:description};
});
return _.values(descriptions);
},

I don't think you can do what you want with that package. If you have a look at the current limitations of the package documentation, you can see other potential solutions to your problem.
You can do addtional filtering as follows:
filter: { 'gender': 'female' }});
but I don't think this will allow you to demand only unique options.
The code you wrote above for try won't do anything. Autocomplete doesn't take a field called try.

Related

HTML datalist keeps crashing when dynamically loaded with Vue

I'm using Vue.js to load an HTML datalist. Whenever I type in my input box that is connected to my datalist, my page freezes (using Chrome). I'm using jQuery to make an AJAX call to initially get all of my users when the page loads, then using the JavaScript filter function to make a new array that has indexOf whatever is typed in my input box, then putting that new filtered array of objects into the datalist element.
Here is my HTML:
<div id="my-app">
<input list="user-search" type="text" id="find-user" name="find-user" v-model="findUser">
<datalist id="user-search">
<option v-for="user in findUserList">{{ user.vendorName }}</option>
</datalist>
</div>
Here is my Vue JavaScript:
var myApp = new Vue({
el: '#my-app',
data: {
users: [],
findUser: '',
findUserList: '',
},
watch: {
findUser: function(val) {
this.findUserResults();
}
},
methods: {
findUserResults: function() {
var lookFor = this.findUser.toLowerCase();
this.findUserList = this.users.filter(function(user) {
return (user.vendorName.toLowerCase().indexOf(myApp.findUser.toLowerCase()) > -1 || user.email.toLowerCase().indexOf(myApp.findUser.toLowerCase()) > -1);
});
},
loadUsers: function() {
$.ajax({
method: 'GET',
url: '/users',
success: function(data) {
myApp.users = JSON.parse(data);
},
error: function() {
console.log('error...');
}
});
}
},
mounted: function() {
this.loadUsers();
}
})
The users load fine when the page loads. No problems there. I even commented out the v-for and just console.log'd the results of the filter with no problem either. The issue comes in when I implement dynamically adding the <option> values in the <datalist>. I've tried wrapping the options in the vue <template> tag and moving the v-for there with no success.
I have this working fine on another page, but instead I'm populating a table with <tr> and <td> instead of <datalist> and <option>.
What could be causing my page to crash? Is <datalist> not good enough to handle all of this? Maybe I'm missing something that a second set of eyes could help solve.
EDIT
The data that is being populated into users looks like this (it's an array of objects):
[{id:1, vendorName:'john'}, {id:2, vendorName:'Rachel', {id:3, vendorName:'Steve'}]
ANSWER
This was actually just an example of someone overthinking (me). B.Fleming's answer about this being recursive clued me into why my approach above was incorrect. There is no need to keep reloading the datalist options every time the user types. That's exactly what <datalist> being linked to <input> does; no JavaScript needed. All I need(ed) to do was load all ~3,000 of my users into the <datalist> tag as <option>s on page load (or whenever I want the input and datalist to show up), and let the datalist and input handle the rest. I haven't tested this with the app in the example above because I'm not near that computer, but just as a proof of concept that a datalist can handle thousands of option elements, I wrote this and ran it locally with no issue (not sure why the snippet console keeps saying there is an error when it works):
window.onload = function() {
var datalistElement = document.getElementById('datalist-test');
var datalistString = '';
for (var i = 0; i < 3000; i++) {
datalistString += '<option>' + Math.random().toString(36).substring(7); + '</option>';
}
datalistElement.innerHTML = datalistString;
}
<!DOCTYPE html>
<html>
<head>
<title>Datalist Test</title>
</head>
<body>
<input list="datalist-test" type="text">
<datalist id="datalist-test">
</datalist>
</body>
</html>
3,000 datalist option elements, and it's fine.
You have infinitely recursive calls. You're calling myApp.findUser() on entering/deleting search text, which calls myApp.findUserResults(), which calls myApp.findUser(), etc. You'll need to fix this.
To fix this, all you have to do is load all of the users into the datalist options once. Then, let the input box and datalist handle the rest. No need to keep reloading the datalist options every time the user types.
Typically you would use a computed property for this. I also made a few other changes. For one, referencing the Vue itself from inside it is a bad practice; just use this. Also datalist options have a value property that you need to specify.
console.clear()
const users = [
{vendorName: "Vendor One", email: "bob#example.com"},
{vendorName: "Vendor Two", email: "mary#example.com"},
{vendorName: "Vendor Three", email: "jim#example.com"},
{vendorName: "Vendor Four", email: "joe#vendor.com"},
{vendorName: "Vendor Five", email: "sara#vendor.com"},
]
var myApp = new Vue({
el: '#my-app',
data: {
users: [],
findUser: '',
},
computed:{
vendors(){
return this.users.filter(u => {
return u.vendorName.toLowerCase().indexOf(this.findUser) > -1 ||
u.email.toLowerCase().indexOf(this.findUser) > -1
})
}
},
methods: {
loadUsers: function() {
// obviously, this is different for example purposes
this.users = users
}
},
mounted: function() {
this.loadUsers();
}
})
<script src="https://unpkg.com/vue#2.4.2"></script>
<div id="my-app">
<h3>Type "V"</h3>
<input list="user-search" name="find-user" v-model="findUser">
<datalist id="user-search">
<option v-for="user in vendors" :value="user.vendorName">
</datalist>
</div>

AngularJS - Get printed value from scope inside an attribute?

I'm currently working on an AngularJS project and I got stuck in this specific requirement.
We have a service that has all the data, DataFactoryService. Then, I have a controller called DataFactoryController that is making the magic and then plot it in the view.
<div ng-repeat = "list in collection">
{{list.name}}
...
</div>
Now, we have a requirement that pass multiple data into one element. I thought an "ng-repeat" would do, but we need to have it inside an element attribute.
The scenarios are:
At one of the pages, we have multiple lists with multiple data.
Each data has a unique code or ID that should be passed when we do an execution or button click.
There are instances that we're passing multiple data.
Something like this (if we have 3 items in a list or lists, so we're passing the 3 item codes of the list):
<a href = "#" class = "btn btn-primary" data-factory = "code1;code2;code3;">
Submit
</a>
<a href = "#" class = "btn btn-default" data-factory = "code1;code2;code3;">
Cancel
</a>
In the example above, code1,code2,code3 came from the list data. I tried several approach like "ng-repeat", "angular.each", array, "ng-model" but I got no success.
From all I've tried, I knew that "ng-model" is the most possible way to resolve my problem but I didn't know where to start. the code below didn't work though.
<span ng-model = "dataFactorySet.code">{{list.code}}</span>
{{dataFactorySet.code}}
The data is coming from the service, then being called in the controller, and being plot on the HTML page.
// Controller
$scope.list = dataFactoryService.getAllServices();
The data on the list are being loaded upon initialization and hoping to have the data tags initialized as well together with the list data.
The unique code(s) is/are part of the $scope.list.
// Sample JSON structure
[
{ // list level
name: 'My Docs',
debug: false,
contents: [ // list contents level
{
code: 'AHDV3128',
text: 'Directory of documents',
...
},
{
code: 'AHDV3155',
text: 'Directory of pictures',
...
},
],
....
},
{ // list level
name: 'My Features',
debug: false,
contents: [ // list contents level
{
code: 'AHGE5161',
text: 'Directory of documents',
...
},
{
code: 'AHGE1727',
text: 'Directory of pictures',
...
},
],
....
}
]
How can I do this?
PLUNKER -> http://plnkr.co/edit/Hb6bNi7hHbcFa9RtoaMU?p=preview
The solution for this particular problem could be writing 2 functions which will return the baseId and code with respect to the list in loop.
I would suggest to do it like below
Submit
Cancel
//inside your controller write the methods -
$scope.getDataFactory = function(list){
var factory = list.map( (a) => a.code );
factory = factory.join(";");
return factory;
}
$scope.getDataBase= function(list){
var base= list.map( (a) => a.baseId);
base= base.join(";");
return base;
}
Let me know if you see any issue in doing this. This will definitely solve your problem.
You don't really have to pass multiple data from UI if you are using Angular.
Two-way data binding is like blessing which is provided by Angular.
check your updated plunker here [http://plnkr.co/edit/mTzAIiMmiVzQfSkHGgoU?p=preview]1
What I have done here :
I assumed that there must be some unique id (I added Id in the list) in the list.
Pass that Id on click (ng-click) of Submit button.
You already have list in your controller and got the Id which item has been clicked, so you can easily fetch all the data of that Id from the list.
Hope this will help you... cheers.
So basing from Ashvin777's post. I came up with this solution in the Controller.
$scope.getFactoryData = function(list) {
var listData = list.contents;
listData = listData.map(function(i,j) {
return i.code;
});
return listData.join(';');
}

Using initSelection in select2 3.5.2 for custom filtering

I have a text field which uses select2. Here is the initialization:
$("#foo").select2({
createSearchChoice:function(term, data) {
if ($(data).filter(function() {
return this.text.localeCompare(term)===0;
}).length===0)
{return {id:term, text:term};}
},
initSelection : function (element, callback) {
var data = {id: element.val(), text: element.val()};
callback(data);
},
tags:[],
tokenSeparators: [","],
data: [...data goes here...]
});
In this field, the user is supposed to put in a number, or select items from a list. If an item that the user puts in doesn't appear on the list, it ought be created as a simple tag (id and text identical). This works.
What doesn't work is when I set the value programmatically:
myvar.find('.placeholder lorem-ipsum').val(number).trigger("change");
The way it works now, is that when I set it to any value, it takes it without complaint, making a new simple tag. However, if I were to remove the initSelection parameter completely, it would ignore unknown values, and use known values as taken from the list (where tags are complex - id and text are different).
How do I make it so that if the value I set to the field is found on the list, it will use the item, and otherwise make a simple tag? The way it works now (simple tags only) is sort-of acceptable, but I'd prefer it worked ideally.
EDIT:
I've made examples for how it works with and without the initSelection parameter.
http://jppk.byethost12.com/with.html
http://jppk.byethost12.com/without.html
In short, I want it to work like with.html when I push "Add New Item" and I want it to work like without.html when I push "Add Existing Item".
Here is my best interpretation of what you want.
JSFiddle
The trick is to append the element to the select field.
var array = ['foo', 'bar', 'baz']
$.each(array, function(key, value) {
appendSelect(value);
});
$("#foo").select2();
$('#submit').click(function() {
var val = $('#name').val();
$('#name').val('');
array.push(val);
appendSelect(val);
});
function appendSelect(value) {
$('#foo').append($("<option></option>").text(value));
}
<link href="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.0-rc.1/css/select2.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.0-rc.1/js/select2.min.js"></script>
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet">
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
<div class='container'>
<select id='foo' multiple="multiple" class='form-control'></select>
<label>Input</label>
<input id='name' />
<button id='submit'>submit</button>
</div>
Much, much later, I found a solution that works right:
initSelection : function (element, callback) {
var eArr = element.val().split(",");
var data = [];
for (var i=0;i<eArr.length;i++) {
for (var j=0;j<preExistingData.length;j++) {
if (preExistingData[j]["id"] === eArr[i]) {
data.push(preExistingData[j]);
continue;
}
}
data.push({id: eArr[i], text: eArr[i]});
}
callback(data);
},
This not only checks if the value is among the pre-existing values (in which case it uses that item), but also if the value is a comma-separated list of values (in which case it properly encapsulates them as JS objects).

Ractive, two bindings on an <input>

Note: I'm not referring to "two way binding"
I'm using a ractive decorator (select2) to transform an input into a select2. The data I obtain through ajax are some records from the database, example:
[{id:1, name:"test", quantity:2, image:"image.jpg"},
{id:2, name:"bar", quantity:21, image:"image2.jpg"},
{id:3, name:"foo", quantity:21, image:"image3.jpg"}]
I format these object using select2's functions, formatResult and formatSelection
The element on which I'm using the decorator is something like this:
<input type="hidden" value="{{values}}" decorator="select2">
After the user select something, values will be equal to the ids of the selected object, (eg: values=1,3 if i select the first and the last records)
My question is: how can i obtain the full object that was selected? I was thinking about two bindings on the <input> (<input value="{{values}}" data-objects="{{objects}}"> so the decorator can save the full objects too, when the user select something. But when i debug the decorator, node._ractive.binding only shows value and not other attributes.
I solved it by saving the result of the ajax request in ractive, then matching the ids with the object ids to find the original objects.
Not the prettiest thing, but it works.
Ractive.decorators.select2.type.whatever = {
tags: [],
separator: "|",
ajax: {
url: "ajax_url",
data: function(searchterm, page) {
return {
searchterm: searchterm,
page: page,
};
},
results: function(data, page) {
//Here i save the records
ractive.set("data", data.records);
return {results: data.records, more: data.more};
}
}
};
var ractive = new Ractive({
el: "things",
template: "template",
});
ractive.observe("ids", function(ids) {
var data = ractive.get("data");
ids = ids.split("|");
//I can obtain the original objects
});
<script src="http://cdn.ractivejs.org/latest/ractive.js"></script>
<script src="https://rawgit.com/Prezent/ractive-decorators-select2/master/ractive-decorators-select2.js"></script>
<!-- select2, jquery missing -->
<script type="ractive-template" id="template">
<input type="hidden" value="{{ids}}" decorator="select:whatever">
</script>
<div id="things"></div>

creating a dynamic form that builds a multi dimensional array

I am a php developer that is new to angular and javascript in general but finding it really powerful and fast for creating interactive UIs
I want to create a form for a user to create an object called a program, a program has some basic info like title and description and it can have many weeks. I want their to be an 'add week' button that when pressed displays a group of form fields related to weeks, if pushed again it shows another group of form fields to fill in the second weeks information.
edit1: specifically, how I am adding the objects to scope.program with the addWeeks method.
secondly when I console.log the $scope.program it just looks very messy a lot of arrays within objects within objects. it just dosnt look like a clean array of data but maybe thats just because I am not used to javascript and or json? Each week is going to have up to 7 days obviously and each day can have numerous events so it just seems to me like it is going to be quite messy but maybe I should just have faith :p
finally how the addProgram method is creating the json object to be sent to the server
when the form is submitted it should post a json object that looks something like this
program {
title: 'name of programme',
desc: 'description of programme',
weeks: [
{
item1: 'foo',
item2: 'more foo'
},
{
item1: 'foo2',
item2: 'more foo 2'
}
]
]
}
here is a codepen of what I am doing right now but I am not sure it is the best or even an ok way to do it, particularly how I am appending the arrays/objects in teh addWeek method.
there are going to be many more layers to the form and the object it is posting(days, sessions, excersises etc) so I want to get the basics of doing this right before adding all of that.
html
<div ng-app="trainercompare">
<div ng-controller="programsController">
<input type="text" placeholder="Program Title" ng-model="program.title"></br>
<input type="text" placeholder="Program Focus" ng-model="program.focus"></br>
<input type="text" placeholder="Program Description" ng-model="program.desc"></br>
<button ng-click="addWeek()"> add week</button>
<div ng-repeat="week in program.weeks">
<input type="text" placeholder="Name the week" ng-model="week.name">
<input type="text" placeholder="Describe It" ng-model="week.desc">
{{ week.name }}</br>
{{ week.desc }}</br>
</div>
<button ng-click="addProgram()"> add program</button>
</div>
</div>
app.js
var myModule = angular.module("trainercompare", ['ui.bootstrap']);
function programsController($scope, $http) {
$scope.program = {
weeks: [{
}]
};
$scope.addWeek = function() {
$scope.program.weeks.push(
{
}
);
};
function isDefined(x) {
var undefined;
return x !== undefined;
}
$scope.addProgram = function() {
var program = {
title: $scope.program.title,
focus: $scope.program.focus,
desc: $scope.program.desc,
weeks: []
};
angular.forEach($scope.program.weeks, function(week, index){
var weekinfo = {
name: week.name,
desc: week.desc
};
program.weeks.push(weekinfo);
});
$http.post('/programs', program).success(function(data, status) {
if(isDefined(data.errors)) {
console.log(data.errors);
}
if(isDefined(data.success)) {
console.log(data.success);
}
});
};
}
Looks to me like you've got a good grasp on it. The addWeek code looks correct. The extra data you see when you console.log your model is some of Angular's internal stuff to track bindings. When you post that to your server it should be cleaned up by Angular.
Angular has a JSON function that removes all of the hash values and other 'angular' things from your JSON. That's why they start with a $ so it knows to remove them.
This happens automatically when you use $http, it's in the documentation here:
If the data property of the request configuration object contains an object, serialize it into JSON format.
Since Angular will clean up the hashes and things, you don't need to "rebuild" the model when you're posting it... just set data to $scope.program and remove 70% of the code in $scope.addProgram.
To learn more specifically how Angular cleans up the JSON, look at this answer: Quick Way to "Un-Angularize" a JS Object

Categories