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.
Related
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.
My markup looks something like this
<select ng-model="search.parameters.selectedOptionId" ng-options="lookup.id as lookup.lookupValue for lookup in lookups.options" custom-attribute-directive>
<option value="" selected>All</option>
</select>
From the customAttributeDirective, I need to access the selected option value that is displayed in the dropdown (i.e. lookup.lookupValue). I've tried accessing the $viewValue on the ngModel, but it is set to the lookup.id (which I assume is the fault of the way ng-options is set up). I cannot modify the implementation of the markup due to the circumstances that I am implementing the directive against, so the problem must be solved there.
Bind the lookup.lookupValue for display and lookup.lookupValue for backend Id too. In that case you will get display text with ng-model
<select ng-model="search.parameters.selectedOptionId" ng-options="lookup.lookupValue as lookup.lookupValue for lookup in lookups.options" custom-attribute-directive>
<option value="" selected>All</option>
Found my answer. It turns out directives can access the dom directly.
link: function (scope, element, attrs, controller) {
scope.getViewValue = function () {
if (angular.isDefined(scope.overrideTagDisplay)) {
return scope.overrideTagDisplay;
}
else if (element[0].nodeName == "SELECT") {
return element.children()[element[0].selectedIndex].text;
}
else {
return ngModel.$viewValue;
}
}
scope.initialize()
}
EDIT: I found later that this method has the issue that if javascript modifies the model value somewhere else in the code and then this piece immediately runs, it does not pick up the new value because the DOM hasn't updated (this includes on page load and initial setting). I ended up passing the list of lookups into the directive as well and got around the lack of standardization in those lookups by having an optional "comparator key" that would defaultly look something like
var comparitorKey = { id: "id", lookupValue: "lookupValue"};
which is then utilized like this
var id = ngModel.$modelValue;
var filtered = scope.lookupList.filter(function (lookup) { return lookup[scope.comparitorKey.id] == id });
return filtered.length > 0 ? filtered[0][scope.comparitorKey.lookupValue] : null;
the idea being that someone could pass in an alternative like
var comparitorKey = {id: "userId", lookupValue: "userName" };
Simple array in an component, that it will be display in a selector tag. I'm not being able to make it to work. The firstSelector doesn't display anything.
In the component.js:
sortPropertiesAsc: ['value:asc'],
selectors: ['model.modelObjects', 'model.location'],
firstSelector: function(){
const firstGroupObjects = this.get('selectors')[0];
return Ember.computed.sort(firstGroupObjects, 'sortPropertiesAsc');
}.property(),
In the component.hbs
<select onchange={{ action 'selectBrand' value="target.value" }}>
<option value="" >Select company</option>
{{#each firstSelector as |company|}}
<option value={{company.id}} selected={{eq brand company.id}}> {{company.value}}</option>
{{/each}}
</select>
if I write the firstSelector like this in the component.hbs, it will work:
firstSelector: Ember.computed.sort('model.modelObjects', 'sortPropertiesAsc'),
How can I write it like the other way(as a function)
I'm not sure all that's at play in your setup, but one thing I noticed off the bat was if you want your computed property to fire based on changes to selectors, you'd want something like this.
firstSelector: Ember.computed('selectors', function() {
const firstGroupObjects = this.get('selectors.firstObject');
return Ember.computed.sort(firstGroupObjects, 'sortPropertiesAsc');
})
If sortPropertiesAsc changes values you'd want to add it in too
firstSelector: Ember.computed('selectors', 'sortPropertiesAsc', function() {
const firstGroupObjects = this.get('selectors.firstObject');
return Ember.computed.sort(firstGroupObjects, 'sortPropertiesAsc');
})
I am building a drop down list in Meteor using a helper function. However, users will need to be able to update the form in the future so I need the form to repopulate with all the previously selected values based on data in Mongo. I am able to populate textboxes and text areas in my form with my collection data but I have been unable to set the value in the drop down list to what is being stored in my Mongo collection.
The solution I have now is close, I think. It works if a user refreshes the page when viewing a specific record but navigating to the template using iron:router the helper function is called prior to the template being fully rendered, leaving the selected value in the drop down blank. If I move the logic to an OnRendered block, then I am unable to get to this.source to dynamically pick up the value from the collection.
Does anyone have any ideas on how I might be able to populate the selected value of a drop down list based on the value that is stored to the collection? Thanks in advance!
<template name="leadForm">
<form id="newLeadForm">
<select class="form-control" name= "leadSource" id="leadSource">
<option disabled="disabled" selected="selected">Please Select</option>
{{#each categories}}
<option value="{{this}}">{{this}}</option>
{{/each}}
</select>
{{setDropdownValue}}
</form>
</template>
Template.leadForm.helpers({
'categories': function(){
return ["Option1", "Option2", "Option3"]
},
'setDropdownValue': function(){
$('#leadSource').val(this.source);
}
});
You don't need to set the dropdown with DOM manipulation, just let the templates do it for you. When the DB query changes, meteor will rerender the template for you.
template:
<template name="leadForm">
<form id="newLeadForm">
<select class="form-control" id="leadSource">
<option disabled="disabled">Please Select</option>
{{#each categories}}
<option value="{{this.option}}" {{isSelected this.option}}>{{this.option}}</option>
{{/each}}
</select>
</form>
<br/>
<button id='addOption'>Add a new option to Drowdown</button>
</template>
js: (I'm using the anti:fake package to generate data - meteor add anti:fake)
Options = new Mongo.Collection("options");
Selected = new Mongo.Collection("selected");
if (Meteor.isClient) {
Template.leadForm.helpers({
'categories': function(){
return Options.find();
},
'isSelected': function(option){
var selected = Selected.findOne('SELECTED') ? Selected.findOne('SELECTED').selected : '';
return option === selected ? 'selected' : '';
},
});
Template.leadForm.events({
'click #addOption': function () {
Options.insert({option: Fake.sentence(3)});
},
'change #leadSource': function(event, template){
Selected.update('SELECTED', {selected: event.target.value});
}
})
}
if (Meteor.isServer) {
// code to run on server at startup
Meteor.startup(function () {
if(Selected.find().count() === 0)
Selected.insert({_id: 'SELECTED', selected: ''});
if(Options.find().count() === 0) {
Options.insert({option: Fake.sentence(3)});
Options.insert({option: Fake.sentence(3)});
Options.insert({option: Fake.sentence(3)});
Options.insert({option: Fake.sentence(3)});
Options.insert({option: Fake.sentence(3)});
}
});
}
meteorpad example
I am building an app using Meteor and I have a dropdown list that is populated by mongodb values. Is there a way I can return the option value id when I select one of the DDL items? Here is my current html:
<td>
<select id="clientsSelect" name="clients">
<option disabled selected> Select Client </option>
{{#each users}}
<option value="{{this._id}}">{{this.profile.companyName}} - {{this._id}}</option>
{{/each}}
</select>
</td>
My templates.js:
Template.adminTemplates.events({
"change #clientsSelect": function(event, template){
var selectValue = template.$("#clientsSelect").id();
console.log('select: ' + selectValue)
}
});
Based on the code above, i specifically want to to return the option value's this._id. I tried template.$("#clientsSelect").id() but this returns Uncaught TypeError: template.$(...).id is not a function.
Can someone help? Thanks!
I'm not 100% sure why this works, but the solution is:
var selectValue = $("#clientsSelect").val()
This will return the selected value's ID attribute, not the select value's text that shows up in the drop down list.
You can also use:
Template.adminTemplates.events({
"change #clientsSelect": function(event, template){
var selectValue = event.target.value;
console.log('select: ' + selectValue)
}
});