Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"nullable" option for sap.m.Select and similar controls #409

Closed
themasch opened this issue Apr 20, 2015 · 31 comments
Closed

"nullable" option for sap.m.Select and similar controls #409

themasch opened this issue Apr 20, 2015 · 31 comments

Comments

@themasch
Copy link
Contributor

I'm not 100% sure that this is not a duplicate but couldn't find a matching issue so I'll create a new one. If this has been discussed earlier: sorry!

I've seen many cases now where we'd like to be able to tell a sap.m.Select that there should be a (default selected, always top-of-the-list) empty option. Selecting this would set selectedItem and selectedKey to null, trigger the change event etc. The OData annotations and metadata allows to mark a field as "nullable" which doesn't work well with current selects.
Currently our workaround is to include these empty options in the Model used for the binding but this introduces a lot of runtime overhead and code.

The API could look somewhat like that:
Add two new properties:

  • boolean nullable default false
  • string nullableText default "" // someone should be able to come up with a better name..

Somewhere between binding and rendering (updateItems?) there would be an additional "virtual" Item created that uses the nullableText as a text and null (or the value of another property?) as its key.

This way selects wont always select the first data element and would require the user to pick the correct one. Additionally optional selects would be more easy to implement.

Using a combobox to fake that doesn't feel good because of the input-behavior.

@thomaskoetter
Copy link
Contributor

We've had similar requests and such an item is already on the backlog. For the Select, we'll probably only be able to include a "dumb" option which adds an "empty" value independent of any settings on the OData service. This is because the control is unaware of the metadata of the data-binding.

I can't give a concrete timeline when this will likely be implemented.

@jorgt
Copy link

jorgt commented Jul 8, 2015

I second this request.

@Frank683
Copy link

Frank683 commented Sep 2, 2015

I also support this request. This is really needed!

@arleytm
Copy link
Member

arleytm commented Sep 3, 2015

@themasch

I've seen many cases now where we'd like to be able to tell a sap.m.Select that there should be a (default selected, always top-of-the-list) empty option. Selecting this would set selectedItem and selectedKey to null, trigger the change event etc.

It is possible, if the empty option is in the data model.

Currently our workaround is to include these empty options in the Model used for the binding but this introduces a lot of runtime overhead and code.

Could you explain why this introduces a lot of runtime overhead and code?

Somewhere between binding and rendering (updateItems?) there would be an additional "virtual" Item created that uses the nullableText as a text and null (or the value of another property?) as its key.

We should design for change, adding features to a control that only work in a particular context (OData) is not a good idea.

@Frank683
Copy link

Frank683 commented Sep 3, 2015

@ArleyTriana

It is possible, if the empty option is in the model.

True. But what if you're consuming OData services which you haven't authored and are not under your control? All the hassle with manipulating the collection before it is rendered...a waste of time in my oppinion.

On other frameworks (C++ MFC, .NET) this option is also available.

We should design for change and limit platform dependencies, adding features to a control that only work in a particular context (OData) is not a good idea.

Could you explain why this enhancement would only work for OData models?

@arleytm
Copy link
Member

arleytm commented Sep 3, 2015

@Frank683

True. But what if you're consuming OData services which you haven't authored and are not under your control? All the hassle with manipulating the collection before it is rendered...a waste of time in my oppinion.

I agree on the problem, but I'm not sure there is a reasonable way to fix that. Currently, it is not possible to add a new entity to the model via addAggregation() e.g.:

oSelect.addItem(new sap.ui.core.Item({
    text: ""
}));

Could you explain why this enhancement would only work for OData models?

In the initial request, the OData annotation approach is mentioned. If you consume an OData service, which is not under your control, you would not be able to set the nullable property to true. That is also why I think this feature should not dependent on a setting on the OData service.

@themasch
Copy link
Contributor Author

themasch commented Sep 7, 2015

I've seen many cases now where we'd like to be able to tell a sap.m.Select that there should
be a (default selected, always top-of-the-list) empty option. Selecting this would set
selectedItem and selectedKey to null, trigger the change event etc.

It is possible, if the empty option is in the model.

But "empty option in the model" is a really bad design. My model should represent my data. The option I'm asking for in this issue is a "default null, please pick something" option which has nothing to do with my business data but is a artifact just needed to get the UI right.
When you got a select field and don't have a reasonable default item to select its not always a clever way to just select the option that's - be it randomly or by sort - the upper most one. You may want to force the user to think about this box and select the correct option.
The only way to do this is to fall back to a null-default.

You're right when saying that this should not depend on a setting on the OData service. But why would only work in OData services? Whether I use a JSON, XML, OData or WhateverModel does not change the data this model should represent. The 'default-null' option is not part of my business data but may very well be part of my UI. That's why I want to define it in the UI.

Currently our workaround is to include these empty options in the Model used for the binding
but this introduces a lot of runtime overhead and code.

IMHO this is not a workaround. Could you explain why this introduces a lot of runtime overhead and code?

Well, If you use an ODataModel and want these null-options (which, again, are not part of your business data) you have to copy this list to (for ex.) a JSONModel, add the null-option and use this for data binding.
This takes away a huge benefit of the whole data-binding-greatness and adds a lot of messy code to your controllers. More bugs, more work, more stress, less "we're done".

@arleytm
Copy link
Member

arleytm commented Sep 8, 2015

@themasch

From the architectural perspective, IMHO it not a good idea to mix dummy data (data that is not in the model) with real data in the same aggregation. Having an aggregated data item that is not in the model is an ugly and bug-prone thing. All required code to work around this mismatch will most likely cause issues again in the future, plus it is confusing. Therefore, I am not sure we want to go there.

Possible alternatives:

  • Add the option to the business data model on the back-end side.
  • Add the option to the data model by calling the method addItem() (currently not possible in UI5).
  • We can provide a forceSelection property (default true), setting it to false would not preselect an item. Would this solve the problem?

@themasch
Copy link
Contributor Author

themasch commented Sep 8, 2015

@ArleyTriana
The last option would be more or less what I suggested in my first post.

Again what I wanted to solve with issue are the following two cases:
a) Force the user to select a option by hand, not selecting a (random) default.
b) For optional selects, allow null as a value.

The difference between both cases would be, technical, that in (a) null is the default but not valid, in (b) it may be the default and is valid.

For (a), adding it to the model is, IMHO, not a solution at all. Its not an option, its just something that represents that nothing is selected yet.

For (b) there may be cases where it would be possible to add it to the model but again, "nothing" (as in null) doesn't make sense as part of your business data.

I somewhat feel that we are talking about different things, maybe a small (made up) example can explain my thoughts:
Case (a): Imagine a "Add Product" form. Each product needs to belong to a "Category". If you auto-select the first Category this could lead to wrong associations because the user might overlook the field and, because it is already filled with a valid value, the form validation wouldn't stop the product from beeing created. An Item with text="Please select a Category" and key="null" would solve this, but you don't want a "Please select a Category"-Category in your CategorySet.

Case (b): Imagine a "Create new Project" mask that lets you select a "Project Leader" but its optional. The list would be filled with all Employees (filtered by something) so you can select a leader. But the option "no leader" should never ever be part of you EmployeeCollection. Right?

I might add that the use cases of (a) might be rare (and example sounds awkward) but i've definitely seen uses for both of the cases.

Back to your question: Yes, the "forceSelection" option would help, solving at least the (a)-cases.

@DerGuteWolf
Copy link

I also second this (I wanted to write an issue on this myself).
And a forceSelection option would be not enough IMHO. If the property is semantically nullable the user needs to be able to revert to the nothing selected state after making a selection by mistake.
ABAP Dynpro uses for this a text input with an value help/suggestion and a validation (with restriction to valid values and the empty value) which could also be made in OPENUI5 but seems overly complex to me (esp. as there is no easy way to specify such a validation in an xml view, otherwise see the Category Field in https://sapui5.netweaver.ondemand.com/sdk/explored.html#/sample/sap.ui.comp.sample.smartfield/preview).

@themasch
Copy link
Contributor Author

@DerGuteWolf 👍

@Frank683
Copy link

@ArleyTriana

Just tried with forceSelection=false in 1.34.6 and obviously nothing is selected at first, but when the user has selected an entry, the selection can't be removed anymore.
For me this is only half a solution.

@IcyT
Copy link

IcyT commented Jun 2, 2016

I'm also interested in this feature. I had this requirement already several times in customer projects.

@VictorStep
Copy link

Even though this solution is not great, I managed to get the empty field stick by adding both, the forceSelection=false property, and also in the controller's onInit function (I used the Select element):

		var codeField = this.getView().byId("codeField");
		setTimeout(function() {
			codeField.insertItem(new sap.ui.core.ListItem({text: '', key: undefined}), 0);
		}, 1000);

If the forceSelection=false is left out, the field will load either too early or too late to the drop down, which will cause the wrong selection to be visible. Hope it helps someone.

@aes421
Copy link

aes421 commented Jan 19, 2018

I'm interested in seeing this implemented as well. It would be very useful for our business needs.

@ghost
Copy link

ghost commented Jul 26, 2018

I would be interested in a feature for this for Select controls as well. I was able to get around this by creating my own control ZNullableSelect to contain a placeholder. It uses forceSelection=false and adds a placeholder property which can use binding or set directly as string. I haven't tested if it works with two way binding but it works for the most part


sap.ui.define([
    "sap/m/Select",    
    "sap/m/SelectRenderer",
    "sap/ui/core/Item",
    "sap/ui/core/ListItem"
], function (Select, SelectRenderer, Item, ListItem) {
    "use strict";
    return Select.extend("sap.m.ZNullableSelect", {

      metadata: {            
      	properties: {
      		forceSelection: {        			
      			defaultValue: false
      		},
      		placeholder: {
      			type: "String",
      			defaultValue: ""
      		}
      	},
  			aggregations : {
  			},
  			events : {
  			}
      },
    	
    	init: function() {
    		Select.prototype.init.apply(this, arguments);
    	},
    	
      onAfterRendering: function() {
      	Select.prototype.onAfterRendering.apply(this, arguments);      

      	// ** Get placeholder value
      	var placeholderBinding, path, model, placeholder;
      	
      	if (typeof this.getPlaceholder() === "undefined") {
      		placeholderBinding = this.getBinding("placeholder");  
      		model = placeholderBinding.getModel();
      		path = placeholderBinding.getPath();
      		placeholder = model.getProperty(path);
      	} else {
      		placeholder = this.getPlaceholder();
      	}
      	
      	// ** Create Placeholder item if not created already
      	var itemsBindingInfo = this.getBindingInfo("items");
      	console.log(itemsBindingInfo);
      	var itemsTemplate;
      	if (typeof itemsBindingInfo === "undefined") {
      		itemsTemplate = new Item({
      			key: "",
      			text: ""
      		});
      	} else {
      		itemsTemplate = itemsBindingInfo.template;
      	}
      	
      	if (typeof this._placeholderItem === "undefined" || this.indexOfItem(this._placeholderItem) === -1) {

      		if (itemsTemplate instanceof ListItem) {
      		
      			this._placeholderItem = new ListItem({
  	      		key: "",
  	      		text: placeholder,
  	      		additionalText: ""
  	      	});
      			
      		} else if (itemsTemplate instanceof Item) {
      			
      			this._placeholderItem = new Item({
  	      		key: "",
  	      		text: placeholder  	      		
  	      	});
      			
      		}

	      	this.insertItem(this._placeholderItem, 0);
      	} 
      	
      	// ** Make sure placeholder label is there by default
      	var bindingItems = this.getBinding("items");
      	var label = $("#" + this.sId + "-label");
      	bindingItems.attachDataReceived(function(oEvent) {      
      		        	
        	if (label.text() === "") {      		
        		label.text(placeholder);
        	}
        	
      	});
      	
      	if (label.text() === "") {      		
      		label.text(placeholder);
      	}
      	
      },
       	
    	renderer: function(oRm, oControl) {      		
    		SelectRenderer.render(oRm,oControl);  
    	}
      
    });
    
});

If using JS. Import with "sap.m.ZNullableSelect" and create a new ZNullableSelect. For Example:

new ZNullableSelect({		
        placeholder: "-- Please select a item --",
        items: {
            path: "/items",
            template: new Item({
                key: "{key}",
                text: "{text}"
            })
       }
});

@stephania87
Copy link

If I understand correctly the whole thread and comment, the desired feature is to have a dummy item with “empty value” in the sap.m.Select control’s options.

Questions to everyone:

  1. Since the “empty value” does not belong to the original data, how it will be validated and stored in case of two way binding.
    2.What would be the key that suits all data scenarios? Keep in mind that the sap.m.Select requires a valid selectedKey, considered with the container’s data.
  2. What is the issue with using an Input/ComboBox? How about a CheckBox that just says pass “nothing” as selection, and it will be up to the data handler to interpret the Boolean as “empty value” in the backend.

@DerGuteWolf
Copy link

1: null (and this thus will only make sense for nullable bindings), just look at the title and description of this issue....
2: I don't think this makes sense for the ComboBox style.

@ghost
Copy link

ghost commented Jul 30, 2018

@stephania87

  1. It should be validated and stored as a null value
  2. The key value should be a null, empty string or whatever 'blank' value that makes sense
  3. A ComboBox is not preferred here because the idea is to have a user forced to select or not select a null item without using input like behavior

The alternative way is to insert a null value to a model and bind it to the select control. If using an OData model you could add a null value to the odata webservice read set and use a formatter function to swap the null Item text with a placeholder

@stephania87
Copy link

The Select control lists what is in data, and if you add validation that selectedKey must be set, the user will set it just to pass the screen (I've seen it and I've done it :D)

The default value of selectedKey is empty string, if the property is not set or updated via interaction.

In different cases will appear issues like not knowing that data already has a record with empty string as key, or that a scenario relies on the default value of the select.
Also the scenario looks like a data inconsistency, because a field is declared as predefined value allowing null, yet the predefined values do not include a null record.

That is why an Input with valueHelpOnly or ComboBox with validation appears better in my mind.

@DerGuteWolf
Copy link

This nonsense, how should an odata query returning the valid values include an null in the returned values? Note that null means no value set!

@themasch
Copy link
Contributor Author

Just adding my 👍 here again. My opinion from my last comment still holds as this is only partially solved.

@sebastianomarchesini
Copy link

sebastianomarchesini commented Feb 20, 2019

I'm here for the same "issue".
But solve with forceSelection=false ;)

@seiupo
Copy link

seiupo commented Aug 19, 2019

A search for this issue got me to this page and I have to say I agree that there should be a default, empty option for a Select.
I recently got complaints from end users that once they've selected an option in a Select, they have no way of bringing the Select to the initial state (note: the Select is not mandatory input).

In case they've selected something in a non-mandatory Select, they can't revert this.
If I were to compare this to other forms of input (e.g.: Input, checkbox), a user has a way to bring the input to its initial state. That is, unfortunately, not valid for UI5 Selects.

Regarding sending null options in OData requests, imagine you're working with an SAP BE system and you have the Select binded to an Entity where the key of the Select is also the key of the Entity.
You cannot have a null value for an Entity's key in SAP Gateway (as far as I know)...

Regarding using a combo box, according to SAP Fiori Guidelines, those should be used in case you have very large set of options. For smaller sets, a Select is recommended.

Cheers,
SePo

@flovogt
Copy link
Member

flovogt commented Mar 3, 2022

Long ongoing discussion. I've created an internal incident 2270008434 to bring this topic up again.

@flovogt
Copy link
Member

flovogt commented Mar 17, 2022

This enhancement request will be covered in backlog item BGSOFUIPIRIN-5647.

@jdichev
Copy link
Contributor

jdichev commented Apr 18, 2022

Hello @themasch and @DerGuteWolf,

Is this still a relevant issue? Can you confirm?

Thanks and best regards,
Jordan

@themasch
Copy link
Contributor Author

@jdichev I cannot say much to this, as I have not touched OpenUI for a few years now, nor any other frontend code for that matter ;)
So.. dont know?

@DerGuteWolf
Copy link

@jdichev IMHO this is still relevant!

@jdichev
Copy link
Contributor

jdichev commented Apr 21, 2022

@DerGuteWolf thanks. We're looking into the issue

@nnaydenow
Copy link
Contributor

Hi @DerGuteWolf,

sap.m.Select control is design to allow user to choose from a list of options. As native select when sap.m.Select is used there should be always selected option even if the value of selected option is null. We recommend to add additional option to the list with empty value and text that encourage user to do a selection (please check https://experience.sap.com/internal/fiori-design-web/select/#option-list1). forceSelection property is not a solution for this issue and as you can see our documentation we recommend to use it for better interoperability with data binding not for initial empty state. If you want to have initially empty value for sap.m.Select we recommend you to replace it sap.m.ComboBox.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests