Ractive, two bindings on an <input> - javascript

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>

Related

How add new values in drop-down list using plugin "selectory" jquery

I need some help. How can I add new values in code to the list if I use a plugin from jquery. I wrote this code, but the list is empty, although the values are passed to the view. This is probably due to the fact that I am referring to the id of the div tag, but the plugin did not work differently. Help please
<html>
<main>
<form action="#">
<div class="form-group col-xs-12 col-sm-4" id="example-2"> </div>
</form>
</main>
<script>
$('#example-2').selectivity({
items: ['Amsterdam', 'Antwerp'],
multiple: true,
placeholder: 'Type to search a city'
});
function addOption() {
var ul = document.getElementById("#example-2");
for (var item in #ViewBag.List)
{
var value = item;
}
var newOption = new Option(value, value);
ul.options[ul.options.length] = newOption;
}
</script>
</html>
result of code from answer 1
The documentation of the selectivity library covers how to add new options to the dropdown.
The main issue you have is that the output from #ViewBag.List won't be in a format that JS can understand. I would suggest formatting it as JSON before outputting it to the page, then the JS can access this as a standard object, though which you can loop.
// initialisation
$('#example-2').selectivity({
items: ['Amsterdam', 'Antwerp'],
multiple: true,
placeholder: 'Type to search a city'
});
// add options, somewhere else in your codebase...
const $list = $('#example-2')
const options = #Html.Raw(Json.Encode(ViewBag.List));
options.forEach((option, i) => {
$list.selectivity('add', { id: i, text: option })
});
Note that for this to work the JS code which reads from the ViewBag needs to be placed somewhere the C# code will be executed, ie. in a .cshtml file, not in a .js file.

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>

Group results in autocompleted dropdown [Meteor]

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.

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).

Saving changes in SlickGrid

HI,
I'm looking at SlickGrid and I can see example on how to edit the cell, however do I save these changes. I have yet to find an example that tells me how to do this.
The trick to saving the SlickGrid is to realise that the grid will update the array of data that you supplied when creating the grid as the cells are edited.
The way I then save that is to include a form with a submit button and a hidden field below the grid. I trap the submit event and use the JSON plugin to serialise the array and place it in the hidden field. On the server side you'll receive a JSON string which you can deserialise, loop through and write to the database.
Assuming your array of data is called "data" like the samples, the following should work for you:
<form action="?" method="POST">
<input type="submit" value="Save">
<input type="hidden" name="data" value="">
</form>
<script>
$(function() {
$("form").submit(
function() {
$("input[name='data']").val($.JSON.encode(data));
}
);
});
</script>
For completeness, a minimal example demonstrating the usage of onCellChange, referred to in Jim OHalloran's post.
For more information, and to see all events that can be utilized similarly to onCellChange, see comments at the beginning of the SlickGrid source.
<head>
<!-- boilerplate omitted ... -->
<script type="text/javascript">
var grid;
var options = {
enableCellNavigation: true,
enableColumnReorder: false,
autoEdit: false,
editable: true,
};
var columns = [
{id: "item_key", name: "Key", field: "item_key" },
{id: "value", name: "value", field: "value", editor: LongTextCellEditor }
];
var data = [
{item_key: "item1", value: "val1"},
{item_key: "item2", value: "val2"},
];
$(document).ready(function () {
grid = new Slick.Grid($("#myGrid"), data, columns, options);
//Earlier code for earlier version of slickgrid
// grid.onCellChange = function (currentRow, currentCell, item) {
// alert(currentRow+":"+currentCell+"> key="+item['item_key']+", value="+item['value']);
//Updated code as per comment.
grid.onCellChange.subscribe(function (e,args) {
console.log(args);
});
};
});
</script>
</head>
<body>
<div id="myGrid" style="height:10em;"> </div>
</body>
While I'm personally using the JSON serialize and submit in a hidden field approach from my previous answer another approach could be to trap the onCellChange event fired by SlickGrid after a cell value has changed and make an Ajax call to the server to save the changed value. This will result in lots of small Ajax requests to the server (which may increase load) but updates the server as soon as changes are made.

Categories