OOUI's OO.ui.ComboBoxInputWidget allows to set an array of
OO.ui.MenuOptionWidget objects in its menu.items config field.
Such an item can have a label and a data field. The data field can
be of type object 1.
Now, if I use a data field of typeobject the value of the
OO.ui.ComboBoxInputWidget will be "[Object object]", as it tries to cast
the data value to a string when a user selects an option item.
So it looks like OO.ui.ComboBoxInputWidget allows only data of type
string in its options. Is that correct?
That would also mean that there is no "label/data" mechanism of the input
field itself. If I've got the following options
[
{ label: "Rot", data: "red" },
{ label: "Gelb", data: "yellow" },
{ label: "GrĂ¼n", data: "green" }
]
and the user selects the option with label "Gelb" the input field shows
"yellow", not "Gelb". The code example in the official documentation shows this behavior [2]. Did I miss something? Is it possible to show a
label to the user but retrieve the data (object) when calling
getValue on such a field?
1 https://doc.wikimedia.org/oojs-ui/master/js/#!/api/OO.ui.MenuOptionWidget-cfg-data
[2] https://doc.wikimedia.org/oojs-ui/master/js/#!/api/OO.ui.ComboBoxInputWidget
This question was originally posted on the wikitech-l mailing list. You can find the thread here: http://markmail.org/message/fesegc3yljqcytzt
In general, the 'data' property for items inside the GroupElement can be strings or objects, as they represent some state of your item. In OO.ui.mixin.GroupElement, the method getItemFromData can then return a specific item based on its data property. If you use an Object for the data, OOUI will use its OO.getHash() to basically stringify your object, so it can make sure it retrieves the right one.
The property 'data' actually comes all the way up the hierarchy chain from OO.ui.Element (if you look at that method, the description of that parameter is the same) -- and at that level, it definitely allows for any sort of data, be it a string or an object.
However, when dealing with specific cases, like that of the ComboBoxInputWidget (the terminology of "input widget" usually suggests something inside a form in OOjs-UI) means that putting the data as an object doesn't make sense usually. Unless your use case requires something very different, we usually want ComboBoxInputWidget to have the 'value' => 'label' pair, so it uses its 'data' property as the 'value' and expects a string.
As for the second part of your question, I am not 100% sure I understood it (please correct me if so) but from what I understand, if you set your OO.ui.MenuOptionWidget items with their data as the value ('red' / 'yellow' / 'green' etc) and the label as the mw.msg that the user sees, then it should work out of the box.
So if you look at the example given in the docs for ComboBoxInputWidget you can set your item's data to the color (value) and the label to the word you want to display, and when the user picks an option, the label needs to show in the ComboBoxInputWidget.
Be aware, though, that if you listen to 'choose' or 'select' event from this input widget, you get the selected item, so if you're projecting that choice into some other input, you should ask for the label (item.getLabel()) and not the data.
Thanks for your explanations.
It looks like OOUI does not support something like that (yet).
An example: You've got a option value "Q7186" and a label value "Marie
Curie". When the user selects "Marie Curie" the user interface will
say: "Q7186". A user might be confused.
I know that some UI frameworks handle this by showing the label of
the selected item to the user in a DIV element or something else and
storing the actual value in an internal variable. When it comes to form
submission and the need to provide an actual "value string" they use a
hidden field and maybe a valueField config option in case the "value"
is not a string but an object (thus allowing to set the string value of
the hidden field from a field within the value object).
Maybe that's something for future development.
Related
I'm using jQuery 3.3.1 and jQuery-ui 1.12.1.
I have an input field where an user should choose among objects of an array, previously retrieved with an ajax call and stored in a variable.
The documentation of the source option of autocomplete says that if a function is provided to filter the terms, it should return an array of strings to make the user choose from.
I'd like to separate the view (the string that I display to the user) from the data, the object beneath it. I've seen that I can return an array of objects {label: 'string to display', value: the_object} from the source option, and the label is correctly displayed, but when the user selects it, it turns into an anonymous string [object Object].
Having the label string in the input field after the selection would be great, but it could be ok just showing the terms as they are, without any replacement; otherwise I'd like to have the field cleared, and I thought to clear it with:
select: function(event, ui) {
var _content = ui.item; // Store the content of the field somewhere.
$(event.target).val('');
}
but .val('') doesn't work, nor does .val(null).
Here's a test fiddle: https://jsfiddle.net/xpvt214o/16284/
Have the elements_that_match object's value be the string you want, not an object.
elements_that_match.push({"label" : _label, "value" : element_obj});
to
elements_that_match.push({"label" : _label, "value" : element_obj.name + ', ' + element_obj.country });
https://jsfiddle.net/xpvt214o/16303/
The solution I found, inspired by #CertainPerformance answer, is to store the original dictionary object with a different key, such as orig_obj, so that I can then work directly on it in the select method.
In this way, the label string is displayed when the user selects the label, but we can manipulate the object in its original form.
Fiddle: https://jsfiddle.net/xpvt214o/16356/
I have a form that includes a SELECT element. I load the possible values in my controller from a function that returns the data from a database. I want to group the options by a group name.
I have the list of options loading properly showing the grouping. My problem, is the initial value is not displaying - this is based on a data model. It shows as blank. If I choose any option in the list, it does properly display.
I followed the example from this solution Populate a Dropdown list by grouping using AngularJs
From the various other examples that I have seen, this should work...I'm guessing it is some little thing I accidentally overlooked.
This loads the possible values for the drop down:
$http.get("api/getIndustry").success(function(data){
$rootScope.industryData = [];
$.each(data, function (i, data) {
$rootScope.industryData.push({
group: data.group,
id: data.id,
text: data.text
});
});
});
For now, I am trying to initially set a selected value (eventually it will be set from reading a record):
$scope.example3model = {group: 'Energy and Natural Resources', id: '25', text: 'Utilities'};
And this is a portion of my view.
<td colspan="4" ng-hide="editableForm.$visible">
<select ng-model="example3model" class="form-control input-md" ng-options="industry.text group by industry.group for industry in industryData" >
</select></br>
{{example3model}} <- did this to see what was chosen
</td>
I'm not sure what else to try to get this to work...the only problem I see right now is that the list is not showing the 'default' value of what is initially in example3model (so the list shows as blank). If I choose a value in the list it is displayed correctly.
Any suggestions would be greatly appreciated.
The problem is that you're trying to set the initial value to an object literal, and even though it may look the same as one inside the select options it is not.
This happens because of how Javascript and AngularJS both work to set that initial object-value (note that this wouldn't happen if options was an array of primitives such as numbers and strings): {} and {} look the same from a human perspective, but they're clearly not the same in JS, try doing this in the browser console:
{} == {}
// this will be false
{ a: 1 } == { a: 1 }
// this will be false as well
Now, what Angular does behind the scenes is checking if the ngModel matches any reference inside ngOptions, that's why we need to set the initial value specifically referenced from the options array.
The initialization, in your example, must be something like this in the specific case you provided (note that I'll be hard-coding the id to match the needs of your post, but you could change it to match whatever you need)
const defaultId = 25;
$scope.example3model = $rootScope.industryData.find(data => +data.id === defaultId)
Now the ngModel value is pointing to the referenced array object that we want.
* Take a look at the official documentation about complex models for ngOptions
[note that this will not work if none of the objects in the ngOptions array has that defaulted id as it will not match any of them]
I need to use partialUpdateObject from the Algolia Javascript SDK to delete an attribute from an object that is already in the index.
My scenario is that I have a description field on my object that is optional. If the user originally sets a description and then later deletes it, I want to remove the description altogether from the object.
I do not want to overwrite the whole object (without the description attribute) because I have some count attributes on the object that I do not want to have to recalculate.
As far as I can tell in the documentation there isn't a way to do it and my workaround is to set the description as an empty string. Is that the recommended approach?
You're right: you cannot totally remove the attribute from an object with a partialUpdateObject operation. As a work-around you can set it to null, maybe that's enough for your use-case?
If you really want to delete the field you can :
Get your object with the search function
Store all fields values
Update (not partial update) your object without passing the field you want to delete
I have a complex object which contains some nested arrays of objects.
Inside one of those inner objects is a value which is the id for an item in another list.
The list is just a look-up of Codes & Descriptions and looks like the following:
[
{ "id": 0, "value": "Basic"},
{ "id": 1, "value": "End of Month (EOM)"},
{ "id": 2, "value": "Fixed Date"},
{ "id": 3, "value": "Mixed"},
{ "id": 4, "value": "Extra"}
]
However, I only carry the value in the nested object.
The Select Option list (drop list) will display all of the values in the previous list so the user can make his/her selection.
Binding Via ng-model
I then bind the value returned from the Select/Option directly to the nested object.
That way, when the user makes a selection my object should be updated so I can just save (post) the entire object back to the server.
Initialization Is The Problem
The selection does work fine and I can see that the values are all updated properly in my nested object when a user selects. However, I couldn't get the UI (select/option) to be initialized to the proper value when I retrieved the (nested) object from the server.
Input Type Text Was Binding Properly
My next step was to add an text box to the form, bind it to the same ng-model and see if it got initialized. It did.
This is a large project I was working on so I created a plnkr.co and broke the problem down. You can see my plnkr in action at: http://plnkr.co/edit/vyySAmr6OhCbzNnXiq4a?p=preview
My plunker looks like this:
Not Initialized
I've recreated the exact object from my project in Sample 1 and as you can see the drop list is not selected properly upon initialization since the value(id) is actually 3, but the drop list doesn't show a selected value.
Keep In Mind: They Are Bound And Selecting One Does Update Values
If you try the plunker you will see that the values are bound to the select/option list because even in the samples which do not initialize properly, when you select an item the other bound items are instantly updated.
Got It Working : Hack!
I worked with it a long time and kept created fake objects to see which ones work and which don't.
It only works, once I changed the value object to one that looks like the following:
$scope.x = {};
$scope.x.y = 3;
Now, I can bind x.y (ng-model="x.y") to select/option and it initializes the select/option list as you would expect. See Sample 2 in the plunker and you will see that "mixed" (id value 3) is chosen as expected.
Additional One Works
I also learned that the following will work:
$scope.lastObj = {};
$scope.lastObj.inner = [];
$scope.lastObj.inner.push(3);
More Nesting
In that case I can bind lastObj.inner to the select/option list and again you can see in Example 3 that it still works. That is an object which contains an array which contains the value.
Minimal Nesting That Fails
However, Sample 4 in the plunker displays the final amount of nesting which does not work with the AngularJS binding.
$scope.thing = {};
$scope.thing.list=[];
$scope.thing.list.push({"item":"3"});
This is an object which contains an array which contains an object with a value. It fails to bind properly on the select/option but not the text box.
Can Anyone Explain That, Or Is It A Bug, Or Both?
Can anyone explain why the select/option fails to bind / initialize properly in this case?
A Final Note
Also, be strong and do not try to explain why you think the nesting of my objects should be different. That's not under discussion here, unless you can tell me that JavaScript itself does not support that behavior.
However, if you can explain that Angular cannot handle this deep of nesting and why, then that is a perfectly valid answer.
Thanks for any input you have.
You are messed up with primitive types. It means you should insert
$scope.vm.currentDocument.fieldSets[0].fields.push({"value":3});
instead of
$scope.vm.currentDocument.fieldSets[0].fields.push({"value":"3"});
Note the difference of {"value":3} and {"value":"3"}
First one defines an object with property "value" with Integer type, and the second one defines an object with property "value" with String type. As Angular checks type match, it becomes that ("3" === 3) evaluates as false, this is why angular cant find selected option.
This is how it supposed to work.
Also note that - as Armen points out - objects are passed by reference as opposed to primitives which are pass-by-value.
Because of this fact, normally initializing a select box via ngModel from JSON (say, from a $resource record) you will need to set the model value to the specific array element/object property that is being internally checked for equality by Angular to the elements in the ngOptions (or repeated options elements with ng-values assigned to the same record objects). No two distinct objects in JS are considered equal, even if they have identical property names/values.
Angular has one way around this: use the "track by" clause in your ngOptions attribute. So long as you have a guaranteed-unique value (such as a record index from a db) Angular will check the value of the property between the model value and the records in ngOptions.
See https://docs.angularjs.org/api/ng/directive/select for more.
I have a select box pull down that I'm populating with a JSON list returned from a stored procedure, but unfortunately when I update the linked object I need to return the selected text of the pulldown, not the selected index like one would think (poor database design, but I'm stuck with it for now and cannot change it).
Does anyone have any ideas what I can do to keep the selected text synced with the appropriate javascript object's attribute?
You could keep both, the value and the text, if you use subscribers.
For instance, if each of your javascript objects look like this:
var optionObject = {
text:"text1"
value: 1
}
Then your binding would look like:
Where 'OptionsObjects' is a collection of optionObject and selectedOption
has two observable properties: text and value.
Finally you subscribe to the value property of the selectedOption:
viewModel.selectedOption.value.subscribe(function(newValue){
var optionText = viewModel.OptionsObjects[newValue].text;
viewModel.selectedOption.text(optionText);
});
Then if you want to see the new selected option text when the value is changed,
you could have a binding as follows:
<span data-bind:"text:selectedOption.text"></span>
In your particular case you would return selectedOption.text().
So yes, you got what I was getting at. Use the text as the value for the select options rather than using an index. The value really should be something useful, I can't think of any case where I've ever used an index. A number sure, but a number that relates to the application's models in some way (like an id from a database), not to the number of items in the select box.
Well done.