Ember simple array to display in component - javascript

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');
})

Related

How to display only the text in datalist and not the value?

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.

proper way to implement dropdowns in ng2?

What is the proper way to implement dropdowns in ng2? I did some googling but I wasn't finding a lot of consistency.
My search.component.ts has a searchMetadata property which gets set via ngOnInit().
searchMetadata.Authors looks like [{Id=1, Value="Bob Smith"},{Id=2, Value="Mary Jones"}]
The final ng2 dropdown should be something like this:
<select #ddlAuthor (change)="updateSearchResults()">
<option value="1">John Smith</option>
<option value="2">Jane Doe</option>
<option value="3">John Denver</option>
</select>
What is the proper way to implement ng2 for this in the component.html?
You can use *ngFor to iterate over an array/collection populate the value and text accordingly. I've updated the answer to simulate a delay of 3000ms before setting the values to mimic an HTTP API call.
HTML:
<select #ddlAuthor (change)="updateSearchResults($event)">
<option *ngFor="let option of options" [value]="option.Id">{{option.Value}}</option>
</select>
TS:
export class App {
options: any[];
constructor() {}
ngOnInit() {
// simulated delay for API call
Observable
.of([
{Id:1, Value:"Bob Smith"},
{Id:2, Value:"Mary Jones"}
]).delay(3000).subscribe(values => {
this.options = values;
});
}
updateSearchResults($event) {
console.log($event);
}
}
Here is a plunker demonstrating the functionality.
Note: The array you referenced in your question [{Id=1, Value="Bob Smith"},{Id=2, Value="Mary Jones"}] is not valid JavaScript syntax. Objects are structured as { key: value }, it would need be a colon ":" character between the key and value rather than a equal sign "=" like in C# Object Initialization. Having the "=" will cause errors in your Angular application.
Hopefully that helps!
This works:
<select (change)="updateSearchResults()" #ddlAuthor>
<option *ngFor="let author of blogSearchMetadata.Authors">{{author.Value}}</option>
</select>

React Warning: flattenChildren(...): Encountered two children with the same key

Could someone please explain how to fix this error
Warning: flattenChildren(...): Encountered two children with the same
key
I have replicated my code below, but for some reason CodePen is not showing the error.
var FilterOptions = React.createClass({
changeOption: function(type, e) {
var val = e.target.value;
this.props.changeOption(val, type);
},
render: function() {
return (
<div className="filter-options">
<div className="filter-option">
<select id="product" name="Product" value={this.props.product} onChange={this.changeOption.bind(this, 'product')}>
<option value=''>Product</option>
{this.props.productOptions.map(function(option) {
return (<option key={option} value={option}>{option}</option>)
})}
</select>
</div>
</div>
);
}
});
Codepen
As a secondary question, I am pretty sure my reset is supposed to reset the values of the select boxes but this is also not working and just resetting the rendered results - not sure if this is related to the first problem?
Any help much appreciated
It is not a good idea to use the index as the key. A key is the only thing React uses to identify DOM elements. What happens if you push an item to the list or remove something in the middle? If the key is same as before React assumes that the DOM element represents the same component as before. But that is no longer true. From: https://medium.com/#robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318
It is much better to use a unique string from each item you are mapping over as the key. Something like <option key={value.id}> or if a key does not exist, create a unique identifier by doing something like <option key={value.name + value.description}>.
Adding the index as value fixed this. Thanks #azium for your sugegstion.
<select id="product" name="Product" value={this.props.product} onChange={this.changeOption.bind(this, 'product')}>
<option value=''>Product</option>
{this.props.productOptions.map(function(option, value) {
return (<option key={value} value={option}>{option}</option>)
})}
</select>
I'm a big fan of using key by combining index with some constant value rather than using key={value.name + value.description}:
key={'some-constant-value'+index}
This is because I can pass the key knowingly which compoent is it for. For eg. <ComponentA key={'compoent-a-'+i} />. Also, I follow this approach is because simple html convention matches like we give id="my-some-of-the-id" or something.
So, even if you want to use name and description as the key, you may use like this rather:
key={'some-constant-'+value.name+'-'+value.description}
This is just an opinion. Though, I follow html convention when writing props value.
actually you need to specify to each children a unique key,so for that you need to create another key,for example if you are getting data from the database so for that create a new column for example (id) and then add value of that column to your div or what matter you are looping on as a key
var FilterOptions = React.createClass({
changeOption: function(type, e) {
var val = e.target.value;
this.props.changeOption(val, type);
},
render: function() {
return (
<div className="filter-options">
<div className="filter-option">
<select id="product" name="Product" value={this.props.product} onChange={this.changeOption.bind(this, 'product')}>
<option value=''>Product</option>
{this.props.productOptions && this.props.productOptions.map(function(option) {
return (<option key={option.id} value={option}>{option}</option>)
})}
</select>
</div>
</div>
);
}
});
i hope this help anyone in the future.

Meteor how to access different documents in a form

I have a simple form that uses a couple different helpers :
<select class="form-control">
{{#each openTables}}
<option>Table {{tableNumber}}</option>
{{/each}}
</select>
<select id="working-servers" class="form-control">
{{#each workingServers}}
<option>{{name}}</option>
{{/each}}
</select>
<input id="num-of-guests-select" type="text" value="" name="num-of-guests-select">
openTables and workingServers are global helpers that access different collections
Template.registerHelper('openTables', () => {
let openTables = CurrentTables.find({occupied : false});
if(openTables) {
return openTables;
}
})
Template.registerHelper('workingServers', () => {
let servers = Servers.find({working : true});
if(servers) {
return servers;
}
});
My question is basically : I am trying to update a document in that CurrentTables collection with the information from the form.
Template.newTableModal.events({
'click #sendTable' : function(event, template) {
event.preventDefault();
event.stopPropagation();
}
});
In that event function, how do I access the data context of those select boxes? For example,
{{#each workingServers}}
<option>{{name}}</option>
{{/each}}
each of the objects in workingServers has an ID that I want to be able to access in that event function :
CurrentTables.update(tableId?, {$set: {"serverId" : ??, "currentGuests" : ??}});
How do I access those documents in relation to the document in the form when I make it? Rather, how do I get that serverId from the document selected in that workingServers loop.
Is there a better way to do this kind of thing because I need to be able to do similar forms in the future? I mean I know I could take the name value ,
$("#working-servers").val() and look up in the Servers collection to find the ID that matches but that seems really bad.
<select>
<option value="1">Volvo</option>
<option value="2">Saab</option>
<option value="3">Opel</option>
<option value="4">Audi</option>
</select>
value is the returned value when selected, the text part inside the tag is the displayed value.
So, use your id in value and that's what you'll get when selecting
{{#each workingServers}}
<option value={{serverId}}>{{name}}</option>
{{/each}}

Dynamic data collection with Select2 and Meteor.js?

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.

Categories