I am utilizing VueJS and it's components to build a large series of datalists and selectors.. all with a submit button at the end of it when the form is validated...
so far I can make a datalist inside a component that renders options and has type completion.. works great! BUT when I attempted to turn the thing into a VueJS Component, and pass in the dataarray as a property... my list of options no longer render
Two Datalist elements...
Top one is the "raw" datalist, which works 100%
But when I goto the vue.js component version, nothing shown as an option...
it's just not there, when I mouse over like the first one...
The datalist VueJS Component
<template>
<div>
<input type="text" v-model="item" list="data_input" v-on:input="selectionChanged">
<datalist id="yourdatalist">
<option v-for="item in data_input">{{item}}</option>
</datalist>
</div>
</template>
<script>
export default {
name: 'Datalist',
props: ['inputDataList'],
data () {
return {
selection: '',
item:'',
data_input:this.inputDataList
}
},
methods: {
selectionChanged: function(element) {
console.log("selection = "+this.selection+", new value = " + element.target.value);
var newSelection = element.target.value;
if (newSelection != this.selection) {
// newSelection changes on every keystroke, so you must keep diffing it with your known data
for (var i=0; i<this.data_input.length; i++) {
if (this.data_input[i] == newSelection) {
this.selection = newSelection
console.log("selection = "+this.selection+" now");
this.$emit('selectionChanged', this.selection);
}
}
}
},
},
}
</script>
The calling component HTML code
<p>Examples of Datalists</p>
<input type="text" v-model="film" list="films" v-on:input="filmChanged">
<datalist id="films">
<option v-for="film in films">{{film}}</option>
</datalist>
<div v-if="focusedfilm">
<h6>You have picked {{focusedfilm}}</h6>
</div>
<br/>
<p>Examples of Child Component Datalist</p>
<Datalist :inputDataList="films"/>
Set the attribute 'list' equal to the attribute 'id' of the datalist.
Change
<datalist id="yourdatalist"> to <datalist id="data_input">
Regards
If Alfredo Lanzetta post his answer, you should accept his because he came with it first. I just want to explain why the solution works.
If you have the following code where you want a dropdrown list for an input field
<input type="text" v-model="item" list="data_input" v-on:input="selectionChanged">
<datalist id="yourdatalist">
<option v-for="item in data_input">{{item}}</option>
</datalist>
To correctly assign the datalist to the input field, the input field needs to have a link to said datalast. You can do that with the list property of the input field.
The way to link the two, is to set the list property of the input field to the id of the datalist. As you can see in example from your code, the datalist has the id yourdatalist but the input field has de list property set to data_input, thus it is looking for a datalist with the id data_input. Since there is no datalist with said id, you don't get to see that dropdrown list.
Related
I have a really long list in dropdown in my code (over3k+ options). Since my dropdown was so long I decided to use a datalist so the user can filter the dropdown easier with an input. This way they do not have to scroll through all 3k options.
I want to make it so the user can filter it, but CAN NOT enter/use a value that is not in the dropdown. I.E The input value will be an option from the dropdown or nothing. I'm not sure how to do this.
For instance they can type the colour John, but once they leave the input since John is not in the dropdown and they did not select it from the dropdown the value will be empty once they hit submit. However if they search blue and click blue, the input value will still be blue since it's in the datalist dropdown options.
Thanks in advance.
<form method = "POST">
<input class="form-control" type="text" id="color" list="colors_data" autocomplete = "off">
<datalist id="colors_data"style = "width:800px">
<option value="red"></option>
<option value="orange"></option>
<option value="green"></option>
<option value="blue"></option>
</datalist>
<input type = "submit" value = 'send_request'>
</form>
Grab an array of all the colours from the option values and check if your input value is included.
// Cache the elements
const options = document.querySelectorAll('option');
const form = document.querySelector('form');
const input = document.querySelector('#color');
const submit = document.querySelector('[type="submit"]');
// Add an click listener to the submit button
submit.addEventListener('click', handleSubmit, false);
// Iterate over the options with `map` and return a
// new array of colour values
const colors = Array.from(options).map(el => el.value);
function handleSubmit(e) {
// Prevent the form from submitting
e.preventDefault();
// Check to see if the input value is included
// in the colours array
if (colors.includes(input.value)) {
// If it is submit the form
console.log('Found');
// form.submit();
} else {
// Otherwise do nothing
console.log('Not found');
}
}
<input class="form-control" type="text" id="color" list="colors_data" autocomplete="off">
<datalist id="colors_data" style="width:800px">
<option value="red"></option>
<option value="orange"></option>
<option value="green"></option>
<option value="blue"></option>
</datalist>
<input type="submit" value='send_request'>
Note, if you didn't want to write out all that HTML you could build the options programmatically using an array, and using template strings to create the markup. Then you don't have to grab a separate array of colours to check against because you already have one.
const colors = ['red', 'orange', 'green', 'blue'];
// Iterate over the colour array to produce an array of strings
// that you then join into one string
function getOptions(arr) {
return arr.map(el => `<option value=${el}>${el}</option>`).join('');
}
// Grab the datalist element
const datalist = document.querySelector('datalist');
// And insert the options
datalist.insertAdjacentHTML('beforeend', getOptions(colors));
<datalist />
Additional documentation
Template literals
map and join
Array.includes and Array.from
querySelectorAll and querySelector
addEventListener and preventDefault
Let suppose that we have the following datalist, and a js variable var carID = '':
<input list="options" #change='${carID = e.target.value}'>
<datalist id="options">
<option value="ID_1">Ferrari</option>
<option value="ID_2">Lamborghini</option>
<option value="ID_3">Jeep</option>
</datalist>
I'd like to show ONLY the car names in my options, and NOT the option values (that are the IDs of the cars), and have the ID of the selected car (the value of the selected option) stored in the variable, not the car name.
I tried different solutions, I post 2 of them (one totally wrong and one right but not complete, I 've found this one in other stack overflow questions):
wrong: it simply doesn't work, e.target.carID is ''.
<input list="options" #change="${carID = e.target.carID}">
<datalist id="options">
<option carID="ID_1" value="Ferrari"></option>
<option carID="ID_2" value="Lamborghini"></option>
<option carID="ID_3" value="Jeep"></option>
</datalist>
Ok it's working, but what if I have 2 cars with the same name and different id? Yes, the second car is ignored and if I select the 2nd car I store the 1st car's ID.
<input id='inputID' list="options" #change='${this.getValue}'>
<datalist id="options">
<option data-value="ID_1" value="Ferrari"></option>
<option data-value="ID_2" value="Lamborghini"></option>
<option data-value="ID_3" value="Jeep"></option>
<option data-value="ID_4" value="Jeep"></option>
</datalist>
js:
getValue(){
let shownValue = this.shadowRoot.getElementById('inputID').value;
let rightValue =
this.shadowRoot.querySelector("#options[value='"+shownValue+"']").dataset.value;
carID = rightValue;
}
I cannot use JQuery. Do you have any solutions? Thanks!
Your code #change='${carID = e.target.carID}' cannot work, as the right hand side of the event handler binding is not callable. You need to wrap it inside an anonymous function, e.g. like so: #change=${(e) => { this.carID = e.target.value }}
That being said, this is what I understood you want to do:
Have a list, where the user can choose from.
In the list, only display the name of the car, not the ID.
Store the selected car's ID in carID, not the name.
I see two ways to do that.
Option 1: Use <select>
If the list of cars is fixed, I think you will be best served using a <select height="1"> element, resulting in a drop down box. Including the little event handler, it looks something like this:
<select #change=${(e) => { this.carID = e.target.value }}>
<option value="ID_1">Ferrari</option>
<option value="ID_2">Lamborghini</option>
<option value="ID_3">Jeep</option>
<option value="ID_4">Jeep</option>
</select>
This will display the text from the text content of the <option> elements, but set the value of the <select> from the <option>'s value attribute, and by the virtue of the onchange event handler will set the carID field on the element.
You can even have two cars with different IDs, but the same name. Note however, that your users would not know, if the display text is the same, which of the two "Jeep" entries to choose. So that might not be a good idea (but I don't know your full use case).
Option 2: Use <input> with <datalist>
Now, if the list of cars is not fixed, i.e. the users are allowed to enter arbitrary data and the selection list is not for limiting their choices, but to help them (prevent typos, speed-up entry) you can use an <input> with an associated <datalist>. But the popup will display both, the <option>'s value and text content (if they are both defined and different). If you insist on only showing the name of the car, not the ID, then the name has to go in the value attribute of the <option> (or the text content). While you could put the ID in the dataset, you really don't need to.
In any case you'll need to map the value string back to the ID through your own code. This will only work if "cars and names" is a one-to-one (aka bijective) mapping, so no two cars with the exact same name would be allowed. (Otherwise your code cannot know which one has been selected just by looking at the name.)
const CARS_BY_ID = {
ID_1: 'Ferrari',
ID_2: 'Lamborghini',
ID_3: 'Jeep',
}
class MyElem extends LitElement {
constructor() {
super();
this.carID = null;
}
render() {
return html`
<input list="myopts" #change=${this.carChanged}>
<datalist id="myopts">
${Object.values(CARS_BY_ID).map((name) => html`<option>${name}</option>`)}
</datalist>`;
}
carChanged(e) {
const name = e.target.value;
this.carID = null;
for (const [key, value] of Object.entries(CARS_BY_ID)) {
if (value === name) {
this.carID = key;
}
}
console.log(`this.carID = ${this.carID}`);
}
}
Note, that in this example the user can e.g. enter "Bugatti" and this.carID will be null.
Also note, that this.carID has not been registered as a lit-element property (it's not listed in static get properties), so there will be no update lifecycle triggered, and no re-rendering happens upon that change.
I'm using vue cli and I have a select to display a dropdown list of measurements. I’m wondering how I can update props when I select a measurement?
I have a user profile card that opens a modal with a chart displaying measurement for that user and when I'm in the modal I would like to select another measurement and display that instead.
So when I click on volume, the chart text changes to volume and displays volume data for that user.
I tried to do it for text of the chart via an event listener #change but the props are not passing down and changing the text:
Select: <select name="show" class="show" v-model="value" #change="newText($event)">
<option :value="{ text: 'Volume'}">Vol</option>
<option :value="{ text: 'Weight'}">Weight</option>
<option :value="{ text: 'Temperature'}">Temp</option>
</select>
<script>
newText: function(event) {
const text = this.value.text;
this.text = text;
},
<script>
Here is a codesandbox example.
Any help would be great, thanks!
You need to find the user object and render the value of that user. Here I find the clicked user using computed property inside the tab and render the value of that user. codesandbox .
Computed inside Tabs.vue :
computed: {
selectedUser() {
if (this.name) {
let name = this.name;
return this.users.find(e => e.name == name);
} else return {}
}
}
I have set up a collection as the source for my data in a Select2 multiple select as so:
<template name="mySubjectsSelector">
<div>
<select id="mySubjects" data-placeholder="Choose your subjects" multiple>
{{#each subjects}}
<option>{{this.name}}</option>
{{/each}}
</select>
</div>
</template>
And the .js:
Template.mySubjectsSelector.subjects = function () {
console.log("Fetching subjects...");
return Subjects.find({}).fetch();
};
Template.mySubjectsSelector.rendered = function () {
console.log("mySubjectsSelector is rendered!");
$('#mySubjects')
.select2({
width: "300px"
});
};
Whenever the Subjects collection is updated, it reactively re-renders the template, and that is fine, that's how I want it to behave. The unwanted behaviour, is that it will select the first option in the list when it is re-rendered, on ALL clients!
How can I avoid the selecting of the first item when it reloads/rerenders?
What I did is create a blank first option.
<template name="mySubjectsSelector">
<div>
<select id="mySubjects" data-placeholder="Choose your subjects" multiple>
<option></option>
{{#each subjects}}
<option>{{this.name}}</option>
{{/each}}
</select>
</div>
</template>
There's probably a smarter way of doing this, but it worked for me.
EDIT:
The more involved, Meteor-specific technique I have used to preserve values of the Select2 controls, by storing the values in a Session variable. Something like this:
Template.template.rendered = function () {
// assumes 1) all Select2 controls are originally <select> tags
// and 2) all Select tags have ids assigned
$('select').each(function (i) {
if (Session.get('preserve-' + this.id)) {
$(this).val(Session.get('preserve-' + this.id));
}
});
$('.select2').select2({ /* select2 code here */ })
.on('change', function (e) {
if (e.val !== "") {
Session.set("preserve-" + e.target.id, e.val")
}
});
};
However: I may have not understood the question exactly. If you are only asking how using Select2 to allow unselected answers, try a placeholder and allowclear. From the docs:
$("#e2").select2({
placeholder: "Select a State",
allowClear: true
});
Have you tried specifying a rule to preserve the <select> and <option> elements?
Template.mySubjectsSelector.preserve({
'select[id]': function (node) { return node.id; },
'option': function (node) { return node.id; }
});
Also see the documentation.
Another problem could be that the <option> elements don't have ids. Also from the documentation:
By default, new Meteor apps automatically include the preserve-inputs
package. This preserves all elements of type input, textarea, button,
select, and option that have unique id attributes or that have name
attributes that are unique within an enclosing element with an id
attribute. To turn off this default behavior, simply remove the
preserve-inputs package.
<td>
<select name="ad_category" id = "ad_category" onchange="select_sub_cat(this.value)" >
<option value="#"></option>
<option value="jobs" id="jobs">Jobs</option>
<option value="sales" id="for_sale">For sale</option>
<option value="services" id="services">Services</option>
<option value="real_estate" id="real_e">Real estate/housing</option>
</select>
<span id="cat_help">Help</span>
</td>
IN the above code , in <a href=""> I want to pass the id or any information of the option selected , so that clicking on help will show only the help for the particular option . But my question is is it possible to get the id of the option selected ?
You should be using a button or some other element that doesn't suggest navigation. An inline handler might be:
<... onclick="alert(document.getElementById('ad_category').value);" ...>
More generally, once you have a reference to the select element:
var select = document.getElementById('ad_category');
you can access various properties defined by the HTMLSelectElement interface:
select.selectedIndex // index of selected option
select.options // collection of all options
select.options[select.selectedIndex] // the selected option (if there is one)
and so on.
Edit
You might also want to implement a more generic help system based on class values. Give your form controls a class depending on the help that should be shown. Then the help button can just get the previous form control, grab its class and show it.
e.g.
<style type="text/css">
.helpLink {
color: #CC00FF;
cursor: pointer;
}
</style>
<script type="text/javascript">
var showHelp = (function() {
var help = {
firstName: 'Enter your first name',
lastName: 'Enter your last name'
}
return function (el) {
var helpType;
var node;
do {
el = el.previousSibling;
} while (el && el.nodeType != 1)
if (el) {
helpType = el.className.match(/(^|\s)help-\w+/);
if (helpType) {
helpType = helpType[0].replace('help-','');
// Show help
alert(help[helpType]);
}
}
}
}());
</script>
<form name="form0" action="">
first name: <input type="text" class="help-firstName" name="firstName">
<span class="helpLink" onclick="showHelp(this)">?</span>
<br>
last name: <input type="text" class="help-lastName" name="lastName">
<span class="helpLink" onclick="showHelp(this)">?</span>
<br>
</form>
The above is just a trivial demo.
Yes, you can get the selected option's id:
//Place in event handler
var element = document.getElementById("ad_category");
element.options[element.selectedIndex].id
Related SO post.
If you are using jQuery, you can use the change() function on the selector, to let you know when the selector changes, and capture the ID of the selected item.
Once you have that, you can use jQuery's attr on the anchor to change the href.
Yes it is and you even have several options how to get the job done.
Since the select has an ID, you can get the value like this:
var select = document.getElementByID('ad_category'),
value = select.value;
alert(value);
But also, since the select is a sibling to the parent of the a element, you can also find it like this:
// This example is assuming quite a lot, so it's not really the best option and is
// provided merely for entertainment or trivia.
// Namely, this code requires that it is run in context of the a-element
// (means: 'this' refers to the 'a' -element)
// and also that the markup is exactly as in the example because of traversal.
var select = this.parentNode.previousElementSibling,
value = select.value;
alert(value);