SAPUI5 Expression Binding - data-binding

Is it possible to bind control property to model with dynamic property name, stored, for instance, within another model field? I thought we can use SAPUI5 Expression Binding for this purpose, but it doesn't work: binding in trace window is broken and expression seems to be not evaluated at all.
XML View
<TextArea value="{= ${StackOverflow>/bindTextAreaTo} }" />
Controller
oModel = this.getView().getModel("StackOverflow");
/*
* The model have two properties: question and comment
* I want value of TextArea to be bound to one of them based on some condition
*/
oModel.setProperty("/question", "");
oModel.setProperty("/comment", "");
oModel.setProperty("/bindTextAreaTo",
bAsk ? "StackOverflow>/question" : "StackOverflow>/comment" );

No, that's currently not possible.
However, there is a simple workaround for what you want to do (see below).
Basically, you create a view model and set some boolean value on the model. This flag is then used in your expression binding to define "dynamically" what property of what model shall be used...
XMLView
<TextArea value="{= ${view>/ask} ? ${StackOverflow>/question} : ${StackOverflow>/comment} }" />
Controller
var oModel = this.getView().getModel("StackOverflow");
oModel.setProperty("/question", "");
oModel.setProperty("/comment", "");
//...
var oViewModel = new sap.ui.model.json.JSONModel();
this.getView().setModel(oViewModel, "view);
//...
oViewModel.setProperty("/ask", bAsk);

Related

How to correctly bind a form element to newly created oData Entity in SAPUI5?

I have implemented an oData Service into my SAPUI5 application.
On pressing a button the oData Model createEntry() is triggered which returns a Context with the newly created entity.
As per the SAPUI5 documentation - Documentation (Creating Entities) it should be enough to call setBindingContext(oContext) on my form to bind my new entity to my form.
But no matter how I try, I can't seem to get the binding working. The input fields remain empty (although the entity has set properties).
Do I need to keep a special syntax in mind when trying to do this kind of binding with an oData Model?
My form:
<f:SimpleForm id="form" editable="true" layout="ResponsiveGridLayout" title="Address" labelSpanXL="3" labelSpanL="3" labelSpanM="3"
labelSpanS="12" adjustLabelSpan="false" emptySpanXL="4" emptySpanL="4" emptySpanM="4" emptySpanS="0" columnsXL="1" columnsL="1" columnsM="1"
singleContainerFullSize="false">
<f:content>
<Label text="Recno"/>
<sf:SmartField id="recno" value="{recno}"/>
</f:content>
</f:SimpleForm>
My method that opens my dialog containing the form:
openCreateDialog: function() {
this.getOwnerComponent().getModel("oDataModel").refreshSecurityToken(function() {
var oContext = this.getOwnerComponent().getModel("oDataModel").createEntry("/head", {
properties: {
recno: "100"
}
});
this.byId("form").setBindingContext(oContext);
}.bind(this), function(data) {
console.log(data);
}, true);
this.byId("CreateHeadDialog").open();
},
EDIT
I already tried {oDataModel>xxx} but this doesnt work.
On the other hand, after I made my oDataModel a nameless model it seems to work fine. The smartfield correctly shows the right value, but changes I make in the UI are not applied to the property my smartfield is bound to.
When I submit pending changes of my oDataModel, all properties of my entity remain undefined, although I filled the smartfields with correct values.
You are using a named model: your model name is "oDataModel" evidenced by this in your code:
.getModel("oDataModel")
When using binding syntax in your XML View you need to prefix the field with the named model name as below:
<sf:SmartField id="recno" value="{oDataModel>(your-path-to-object)recno}"/>
If it's not in a list then you would need to reference the field as below (or navigate according to the position of recno in your json hierarchy:
<sf:SmartField id="recno" value="{oDataModel>/recno}"/>

Create variable in view and assign

I need to create a string variable in my View and assign to a hidden input which is bound to a property on my strongly typed model
I guess it is something like
#Html.HiddenFor(m=>m.nodelist, new {#value = myVar})
however not working
Don't try to be setting the #value attribute on the html helper. That's not how those helpers have been designed to work. They have been designed to bind to the value of the corresponding property on the view model you passed to this view.
So it's as simple as:
#Html.HiddenFor(m => m.nodelist)
and in the controller action rendering this view you would assign the corresponding property on the view model to the desired value:
public ActionResult Index()
{
MyViewModel model = ...
model.nodelist = someVariable;
return View(model);
}
If on the other hand this variable is actually a javascript variable then you should obviously use javascript to assign it to the hidden field:
<script type="text/javascript">
var myVar = 'some value';
$('#nodelist').val(myVar);
</script>
or if you are not using jQuery:
<script type="text/javascript">
var myVar = 'some value';
document.getElementById('nodelist').value = myVar;
</script>
use simple html hidden field and keep it's name same as property name.
This works perfectly
#{
string val=myVar;
}
<input type="hidden" name="nodelist" value="#val" />
Is myVar a javascript variable?
You could assign your hidden field an ID property.
#Html.HiddenFor(m=>m.nodelist, new {#id = "hdnNodeList"})
and using javascript or jquery you could do the following
$('#hdnNodeList').val(myVar);
or if myVar is a C# variable you could try the following (this will render an html output, although you could do it here, its better to assign a value to m.nodelist in your controller if possible)
$('#hdnNodeList').val('#myVar');

asp.net 4.0: is there any equivalent of ClientIDMode for the names of INPUTs?

I have an asp:ListView whose ClientIDMode is set to Predictable. Its ItemTemplate contains an asp:textbox.
The ID of the textbox is acting as I expect it to, but its name is still using what looks like an AutoID-style algorithm:
<input name="lvFields$ctrl0$tbVal" id="lvFields_tbVal_somekey" type="text"/>
Is there a way for me to cause the name of the input to act like the ID does?
(Edit in response to questions below:)
The Name of the input element is what's in the POST data, so if a postback alters the list to which the ListView is bound (for example, exchanging two elements) the values from the textboxes end up associated with the wrong keys, because the framework is correlating them based on the Name and not the ID.
You can change the name of an Input by using the method from the following post but modifying it slightly:
how to remove 'name' attribute from server controls?
I over-rode the RenderChildren method on a Page control as I just wanted full control of the HTML for a few controls:
protected override void RenderChildren(HtmlTextWriter writer)
{
var unScrewNamesRender = new RenderBasicNameHtmlTextWriter(writer);
base.RenderChildren(unScrewNamesRender);
}
private class RenderBasicNameHtmlTextWriter : HtmlTextWriter
{
public RenderBasicNameHtmlTextWriter(TextWriter writer) : base(writer) { }
public override void AddAttribute(HtmlTextWriterAttribute key, string value)
{
if (key == HtmlTextWriterAttribute.Name && value.Contains("POLine"))
{
value = value.Substring(value.LastIndexOf("$") + 1);
}
base.AddAttribute(key, value);
}
}
You do need to know what you're doing if you attempt this, WebForms will think the control is missing so you can't use it in any postbacks. For my purposes, where I wanted to add an arbitrary number of multiple lines either server or client-side without having to deal with .Net Ajax controls, it works fine.
I'm pretty sure you can't change the name, especially when you modify the ClientIDMode. As an alternative, you can add a Title attribute. VS will flag this as unknown in the server side code, but it renders correctly in the HTML. If you're doing some client-side manipulation, you can address the input as such:
<script type="text/javascript">
$(document).ready(function () {
$('input:text[title="TextBoxName"]').datepicker();
});
</script>
As far as I know, there is no way to change the name of the input element. The name corresponds to the UniqueID property, which is generated by the system, and which you have no control over. Seems you have to find a way to achieve what yo want using only the control ID.
Both names are using the predictable pattern; originally, name also equaled ct100_ct100 etc. From what I see, that's a predictable name. Client ID value will always use _ between control prefixes and Unique ID (name attrib) will always use $. The two will always match, except for a few controls that leverage name for something (radiobuttonlist uses for grouping).
HTH.
I had the exact same problem once and had to use one of these properties exposed in "System.Web.UI.Control" to get clientside control name in server side.
Play around with these properties and construct the "Name" in server side yourself and use Request.Form("NameHere")
Me.ClientIDSeparator
Me.IdSeparator
Me.UniqueID
A jquery solution
function removeNameAttribute() {
$('input, select').each(function () {
$(this).removeAttr("name");
});
}
//Use a HtmlGenericControl
HtmlGenericControl input = new HtmlGenericControl("input");``
input.ID = "lvFields_tbVal_somekey";
input.Attributes.Add("name", "tbVal");
input.Attributes.Add("type", "text");
input.ClientIDMode = ClientIDMode.Static;

Why does the CheckBoxFor render an additional input tag, and how can I get the value using the FormCollection?

In my ASP.NET MVC app, I am rendering a checkbox using the following code:
<%= Html.CheckBoxFor(i=>i.ReceiveRSVPNotifications) %>
Now, I see that this renders both the checkbox input tag and a hidden input tag. The problem that I am having is when I try retrieve the value from the checkbox using the FormCollection:
FormValues["ReceiveRSVPNotifications"]
I get the value "true,false". When looking at the rendered HTML, I can see the following:
<input id="ReceiveRSVPNotifications" name="ReceiveRSVPNotifications" value="true" type="checkbox">
<input name="ReceiveRSVPNotifications" value="false" type="hidden">
So the FormValues collection seems to join these two values since they have the same name.
Any Ideas?
Have a look here:
http://forums.asp.net/t/1314753.aspx
This isn't a bug, and is in fact the same approach that both Ruby on
Rails and MonoRail use.
When you submit a form with a checkbox, the value is only posted if
the checkbox is checked. So, if you leave the checkbox unchecked then
nothing will be sent to the server when in many situations you would
want false to be sent instead. As the hidden input has the same name
as the checkbox, then if the checkbox is unchecked you'll still get a
'false' sent to the server.
When the checkbox is checked, the ModelBinder will automatically take
care of extracting the 'true' from the 'true,false'
I had the same problem as Shawn (above). This approach might be great for POST, but it really sucks for GET. Therefore I implemented a simple Html extension that just whips out the hidden field.
public static MvcHtmlString BasicCheckBoxFor<T>(this HtmlHelper<T> html,
Expression<Func<T, bool>> expression,
object htmlAttributes = null)
{
var result = html.CheckBoxFor(expression).ToString();
const string pattern = #"<input name=""[^""]+"" type=""hidden"" value=""false"" />";
var single = Regex.Replace(result, pattern, "");
return MvcHtmlString.Create(single);
}
The problem I now have is that I don't want a change to the MVC framework to break my code. So I have to ensure I have test coverage explaining this new contract.
I use this alternative method to render the checkboxes for GET forms:
/// <summary>
/// Renders checkbox as one input (normal Html.CheckBoxFor renders two inputs: checkbox and hidden)
/// </summary>
public static MvcHtmlString BasicCheckBoxFor<T>(this HtmlHelper<T> html, Expression<Func<T, bool>> expression, object htmlAttributes = null)
{
var tag = new TagBuilder("input");
tag.Attributes["type"] = "checkbox";
tag.Attributes["id"] = html.IdFor(expression).ToString();
tag.Attributes["name"] = html.NameFor(expression).ToString();
tag.Attributes["value"] = "true";
// set the "checked" attribute if true
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
if (metadata.Model != null)
{
bool modelChecked;
if (Boolean.TryParse(metadata.Model.ToString(), out modelChecked))
{
if (modelChecked)
{
tag.Attributes["checked"] = "checked";
}
}
}
// merge custom attributes
tag.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
var tagString = tag.ToString(TagRenderMode.SelfClosing);
return MvcHtmlString.Create(tagString);
}
It's similar to Chris Kemp's method, which is working fine, except this one does not use the underlying CheckBoxFor and Regex.Replace. It's based on the source of the original Html.CheckBoxFor method.
I think that the simplest solution is to render the INPUT element directly as follows:
<input type="checkbox"
id="<%=Html.IdFor(i => i.ReceiveRSVPNotifications)%>"
name="<%=Html.NameFor(i => i.ReceiveRSVPNotifications)%>"
value="true"
checked="<%=Model.ReceiveRSVPNotifications ? "checked" : String.Empty %>" />
In Razor syntax it is even easier, because the 'checked' attribute is directly rendered with a "checked" value when given a 'true' server-side value.
Here is the source code for the additional input tag. Microsoft was kind enough to include comments that address this precisely.
if (inputType == InputType.CheckBox)
{
// Render an additional <input type="hidden".../> for checkboxes. This
// addresses scenarios where unchecked checkboxes are not sent in the request.
// Sending a hidden input makes it possible to know that the checkbox was present
// on the page when the request was submitted.
StringBuilder inputItemBuilder = new StringBuilder();
inputItemBuilder.Append(tagBuilder.ToString(TagRenderMode.SelfClosing));
TagBuilder hiddenInput = new TagBuilder("input");
hiddenInput.MergeAttribute("type", HtmlHelper.GetInputTypeString(InputType.Hidden));
hiddenInput.MergeAttribute("name", fullName);
hiddenInput.MergeAttribute("value", "false");
inputItemBuilder.Append(hiddenInput.ToString(TagRenderMode.SelfClosing));
return MvcHtmlString.Create(inputItemBuilder.ToString());
}

Flex ComboBox, default value and dataproviders

I have a Flex ComboBox that gets populated by a dataprovider all is well...
I would now like to add a default " -- select a item --" option at the 0 index, how can I do this and still use a dataprovider? I have not seen any examples of such, but I can't imagine this being hard...
If you don't need the default item to be selectable you can use the prompt property of ComboBox and set the selectedIndex to -1. That will show the string you set propmt to as the selected value until the user chooses another. It will not appear in the list of options, however.
I came across this problem today and wanted to share my solution.
I have a ComboBox that has an ArrayCollection containing Objects as it's dataprovider. When the application runs, it uses a RemoteObject to go out and get the ArrayCollection/Objects. In my event handler for that call I just have it append another object to the beginning of the ArrayCollection and select it:
var defaultOption:Object = {MyLabelField: "Select One"};
myDataProvider.addItemAt(defaultOption, 0);
myComboBox.selectedIndex = 0;
This is what my ComboBox looks like for reference:
<mx:ComboBox id="myComboBox" dataProvider="{myDataProvider}" labelField="MyLabelField" />
The way I've dealt with this in the past is to create a new collection to serve as the data provider for the combobox, and then I listen for changes to the original source (using an mx.BindingUtils.ChangeWatcher). When I get such a notification, I recreate my custom data provider.
I wish I knew a better way to approach this; I'll monitor this question just in case.
This can be used following code for selected default value of combobox
var index:String = "foo";
for(var objIndex:int = 0; objIndex < comboBox.dataProvider.length; objIndex++) {
if(comboBox.dataProvider[objIndex].label == index)
{
comboBox.selectedIndex = objIndex;
break;
}
}
<mx:ComboBox id="comboBox" dataProvider="{_pageIndexArray}" />

Resources