I need to highlight a few of the table's cell border colors on the basis of a condition and pre-fill it with some negative value. Then I need to SAVE/POST this value to proceed further.
View.xml
<t:Column width="100px">
<Label text="ActualQty"/>
<t:template>
<Input id="idInput" value="{ parts: [ {path: 'viewData>ACT_QTY'}, {path: 'viewData>MTART'} ], formatter: '._formatter.defaultInput' }">
<customData>
<core:CustomData key="colorclass" value="{path: 'viewData>MTART', formatter: '._formatter.formatCell'}" writeToDom="true"/>
</customData>
</Input>
</t:template>
</t:Column>
Formatter.js
formatCell: function (iValue) {
try {
iValue.toString();
} catch (err) {
iValue = "foo";
}
return iValue.toString();
},
defaultInput: function (iValue, iValue1) {
if (iValue !== 0 && iValue1 === "HALB") {
iValue = "-1";
return iValue;
} else {
return iValue;
}
}
style.css
div[data-colorclass="HALB"] {
border: 4px solid #fdf6b1 !important;
}
Highlighting and the default value is appearing. But inside the controller, the input value is not coming.
If I remove parts and pass single input param to formatter function, it's working. But I need both the values to built my logic.
Update
Now I am using Composite Binding to make the binding as Two-way.
View.xml
<Input id="idInput" value="{ parts: [ {path: 'viewData>ACT_QTY'}, {path: 'viewData>MTART'} ], type: '._Compound', formatter: '._formatter.defaultInput' }">
Compound.js
sap.ui.define([
"sap/ui/model/CompositeType",
"dismantling/bom/integration/model/type/Compound"
], CompositeType => CompositeType.extend('Compound', {
constructor: function () {
CompositeType.apply(this, arguments);
this.bParseWithValues = true; // make 'parts' available in parseValue
},
formatValue: iValue => iValue,
parseValue: bValue => bValue,
validateValue: vValue => { /*validate...*/ },
}));
In the controller file, I am passing Compound type as _Compound. I am not getting any errors in the console.
Still, I am not able to get the formatter passed value inside the controller.
Your formatter is correct. Make sure sure that the values are actually accessible under your binding path and that the formatter is accessible to the view. If both these things are ensured, your function will work just fine.
Related
I am trying to get an access and change localModel>PROPERTY_VALUE in my controller. This was implemented before, not by me. So I am stuck here. I know, there is setProperty() that I could use, but still don't know how.
<xx:Repeater items="{
path: 'localModel>/reportData/',
filters: {
path: 'TYPE',
operator: 'EQ',
value1: 'CONCLUSION'
},
templateShareable: false
}">
<TextArea value="{localModel>PROPERTY_VALUE}" rows="8" width="100%"/>
</xx:Repeater>
controller.js
ReportService.getReportData(oDataModel, caseUuid).then(function (data) {
that.localModel.setProperty('/reportData/', data);
});
const interpretationTextArea = this.localModel.getProperty('/reportData').find(it => it.PROPERTY_NAME === 'CONCLUSION');
if (interpretationTextArea) {
interpretationTextArea.PROPERTY_VALUE = 'Bla' + interpretationTextArea.PROPERTY_VALUE;
}
Here is how data looks like inside:
I have a problem to display data in my detail page. I've tried almost everything but its dosnt work. On main page everything looks fine. Routing work (display proper ID on network address).
Details.controller.js :
return Controller.extend("sapProject.controller.Details", {
onInit: function () {
var oTable = this.getView().byId("details");
var oModel = new sap.ui.model.json.JSONModel();
oModel.loadData("model/Object.json");
oTable.setModel(oModel);
var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
oRouter.getRoute("Details").attachMatched(this._onRouteMatched, this);
},
_onRouteMatched : function (oEvent) {
var oArgs, oView;
oArgs = oEvent.getParameter("arguments");
oView = this.getView();
oView.bindElement({
path : "/Objects(" + oArgs.employeeId + ")",
events : {
dataRequested: function () {
oView.setBusy(true);
},
dataReceived: function () {
oView.setBusy(false);
}
}
});
},
and this is my Details.view.xml:
<Page
id="details"
title="{i18n>EmployeeDetailsOf} {FirstName} {LastName}"
showNavButton="true"
navButtonPress="onBack"
class="sapUiResponsiveContentPadding">
<content>
<Panel
width="auto"
class="sapUiResponsiveMargin sapUiNoContentPadding">
<headerToolbar >
<Toolbar>
<Title text="{i18n>EmployeeIDColon} {EmployeeID}" level="H2"/>
<ToolbarSpacer />
</Toolbar>
</headerToolbar>
<content>
<f:SimpleForm>
<f:content>
<Label text="{i18n>FirstName}" />
<Text text="{FirstName}" />
<Label text="{i18n>LastName}" />
</f:content>
</f:SimpleForm>
</content>
</Panel>
</content>
</Page>
I think you are binding an empty model to your detail view because probably the loadData function is not completed when you set the model on the Table.
Try to load your json file in the manifest (best option) or differ the setModel on the _onRouteMatched function (although I don't see any table in your detail view).
EDIT:
You can also use this code after oModel.loadData("model/Object.json");
oModel.attachEventOnce("requestCompleted", function(oEvent) {
// Here your file is fully loaded
});
Firstly I recommend you to bind like this:
var sObjectPath = this.getModel().createKey("Objects", {
ID: oArgs.employeeId
});
this._bindView("/" + sObjectPath);
...
}
_bindView: function (sObjectPath) {
//Todo: Set busy indicator during view binding
this.getView().bindElement({
path: sObjectPath,
parameters: {
},
events: {
change: this._onBindingChange.bind(this),
dataRequested: function () {
}.bind(this),
dataReceived: function () {
}.bind(this)
}
});
},
Secondly check if oArgs.employeeId has a valid value and also if the model is loaded with data, easily set a brekapoint or write console.log(this.getView().getModel().oData).
I have an "Add New…" screen with multiple sap.m.Input fields. Everything is working. I submit the form and the values are stored in the DB. But once I re-open this "Add New…" screen, I get the form with previously entered values.
Currently, I can solve the issue iterating over all sap.m.Input fields with sap.ui.core.Element, resetting the values:
Element.registry.forEach(el => {
if (el.isA("sap.m.Input") && el.sId.includes(inputFieldsMask)) {
sap.ui.getCore().byId(el.sId).setValue("");
}
});
Where inputFieldsMask is a mask for all input fields of the relevant screen.
As far as I understand, Element.registry.forEach iterates over all controls in the app, therefore I'm not sure that, from a performance point of view, it's an optimal approach to clean up the fields.
Is there a better way to reset input fields from the previously entered values?
There are several ways to reset the control values depending on what kind of approach you took to create the new entry. Generally, we can make use of the following APIs:
Pass the context to the target container.
In case of working with client-side models: targetContainer.bindElement("newItemPath").
Otherwise, call myV2ODataModel.createEntry("/ThatSet", {...}), which returns a new context, and then pass it to targetContainer.setBindingContext(context, "modelName").
This resolves all the relative bindings in the target container.
<user enters some values and submits ...>
targetContainer.unbindElement("modelName") after the edit was successfully stored.
By unbinding element, relatively bound control values are reset automatically.
Example (using client-side model):
sap.ui.getCore().attachInit(() => sap.ui.require([
"sap/ui/core/mvc/XMLView",
"sap/ui/model/json/JSONModel",
"sap/base/util/uid",
], (XMLView, JSONModel, createPseudoUniqueID) => XMLView.create({
definition: `<mvc:View xmlns:mvc="sap.ui.core.mvc" height="100%">
<App xmlns="sap.m">
<Page backgroundDesign="List" title="Resetting inputs via client-side Model and Context">
<headerContent>
<Button id="addBtn" text="Add Item" type="Emphasized" />
</headerContent>
<List id="myList" growing="true" items="{
path: '/myItems',
key: 'key',
templateShareable: false
}">
<StandardListItem title="{value}" info="Key: {key}"/>
</List>
</Page>
<dependents>
<Dialog id="myDialog"
icon="sap-icon://ui-notifications"
title="New Item"
draggable="true"
class="sapUiResponsiveContentPadding"
>
<Input id="myInput"
placeholder="<New value>"
valueLiveUpdate="true"
value="{
path: 'value',
type: 'sap.ui.model.type.String',
constraints: {
minLength: 1
}
}"
/>
<beginButton>
<Button
text="Submit"
enabled="{= !!%{value} && !%{messages>/}.length}"
/>
</beginButton>
</Dialog>
</dependents>
</App>
</mvc:View>`,
models: {
undefined: new JSONModel({
"myItems": [],
}),
"messages": sap.ui.getCore().getMessageManager().getMessageModel()
},
afterInit: function() {
sap.ui.getCore().getMessageManager().registerObject(this, true);
this.byId("addBtn").attachPress(handleAddPress.bind(this));
this.byId("myInput").attachSubmit(handleSubmit.bind(this));
this.byId("myDialog").setEscapeHandler(onESCPress.bind(this))
.attachAfterClose(onAfterClose.bind(this))
.getBeginButton().attachPress(handleSubmit.bind(this));
function handleAddPress(event) {
const dialog = this.byId("myDialog");
const listBinding = this.byId("myList").getBinding("items");
listBinding.suspend(); // Do not update the list yet
this._currentItems = this.getModel().getProperty("/myItems"); // temp in case user cancels
dialog.getModel().setProperty("/myItems", this._currentItems.concat({})); // new empty item
dialog.bindElement("/myItems/" + listBinding.getLength()); // enable data synchronization via TwoWay binding
dialog.open();
}
function onESCPress(promise) {
const model = this.getModel();
model.setProperty("/myItems", this._currentItems, /*context*/null, /*async*/true);
return promise.resolve(); // continue closing dialog
}
function onAfterClose(event) {
handleAfterClose(event.getSource(), this.byId("myList").getBinding("items"));
}
function handleAfterClose(dialog, listBinding) {
dialog.unbindElement(); // reset data
dialog.setBusy(false);
listBinding.resume();
}
function handleSubmit() {
const dialog = this.byId("myDialog");
if (!dialog.getBeginButton().getEnabled()) return; // something is wrong
dialog.setBusy(true);
if (!this._isStillRequesting) {
this._isStillRequesting = true;
/* send request */setTimeout(mySuccessHandler.bind(this), 3000)
};
}
function mySuccessHandler(newKeyFromServer = createPseudoUniqueID()) {
const dialog = this.byId("myDialog");
this._isStillRequesting = false;
if (!dialog.isOpen()/* request was aborted e.g. by pressing ESC */) {
return; // exit early
}
const context = dialog.getBindingContext();
const value = context.getProperty("value");
dialog.getModel().setProperty(context.getPath("key"), newKeyFromServer);
dialog.close();
sap.ui.require([
"sap/m/MessageToast"
], MT => window.requestAnimationFrame(() => MT.show(`${value} created`)));
}
},
}).then(view => view.placeAt("content"))));
<script id="sap-ui-bootstrap" src="https://ui5.sap.com/resources/sap-ui-core.js"
data-sap-ui-libs="sap.ui.core,sap.m"
data-sap-ui-theme="sap_fiori_3"
data-sap-ui-async="true"
data-sap-ui-compatversion="edge"
data-sap-ui-excludejquerycompat="true"
data-sap-ui-xx-waitForTheme="init"
></script>
<body id="content" class="sapUiBody sapUiSizeCompact"></body>
As explained above, binding and unbinding element also applies to server-side models such as v2.ODataModel.
Benefits
✅ Reduced overload: no need to iterate over all existing controls. Reset only those automatically that need to be reset.
✅ Control agnostic: does not rely on control specific APIs such as myInput.setValue, mySwitch.setState, etc..
✅ Reduced maintenance costs: no need to maintain list of model properties in controller that application needs to reset manually.
Best practice is to use a model to store your application data and to bind any input field to that model. I added an example here. For the sake of simplicity the model data is cleared when the button is pressed.
In a real world application you would place any setup of the model to the onRouteMatched handler to ensure that the data is in an initial state.
onRouteMatched : function(event) {
this.getView().getModel().setData({
"firstName": "",
"lastName": ""
});
}
Bind all your control values to a model. Then reset this model after you've successfully saved the data.
Example:
control1.bindProperty("value", "/controlValues/control1Value"); // Binding
// control1.bindProperty("value", "/controlValues/name");
// <Input value="{/controlValues/name}" /> // <-- ideal binding in xml view
this.getView().getModel().setProperty("/controlValues", this.resetFormData()); // Clear Model
resetFormData: function () {
var emptyControlValues = {
"control1Value": "", // "name": "", <-- bind to control
"control2Value": 0, // "age": 0,
"control3Value": "", // "address": "",
"control4Value": "" // "tel": ""
};
return emptyControlValues;
};
I'm using b-form-select with server-side generated option tags:
<b-form-select :state="errors.has('type') ? false : null"
v-model="type"
v-validate="'required'"
name="type"
plain>
<option value="note" >Note</option>
<option value="reminder" >Reminder</option>
</b-form-select>
When no data is set for this field I want to auto-select the first option in the list.
Is this possible? I have not found how to access the component's options from within my Vue instance.
your v-model should have the value of the first option.
example
<template>
<div>
<b-form-select v-model="selected" :options="options" />
<div class="mt-3">Selected: <strong>{{ selected }}</strong></div>
</div>
</template>
<script>
export default {
data() {
return {
selected: 'a',
options: [
{ value: null, text: 'Please select an option' },
{ value: 'a', text: 'This is First option' },
{ value: 'b', text: 'Selected Option' },
{ value: { C: '3PO' }, text: 'This is an option with object value' },
{ value: 'd', text: 'This one is disabled', disabled: true }
]
}
}
}
</script>
You can trigger this.selected=${firstOptionValue} when no data is set.
what if we don't know what the first option is. The list is generated?
if you have dynamic data, something like this will work.
<template>
<div>
<b-form-select v-model="selected" :options="options" />
<div class="mt-3">Selected: <strong>{{ selected }}</strong></div>
</div>
</template>
<script>
export default {
data() {
return {
selected: [],
options: [],
};
},
mounted: function() {
this.getOptions();
},
methods: {
getOptions() {
//Your logic goes here for data fetch from API
const options = res.data;
this.options = res.data;
this.selected = options[0].fieldName; // Assigns first index of Options to model
return options;
},
},
};
</script>
If your options are stored in a property which is loaded dynamically:
computed property
async computed (using AsyncComputed plugin)
through props, which may change
Then you can #Watch the property to set the first option.
That way the behavior of selecting the first item is separated from data-loading and your code is more understandable.
Example using Typescript and #AsyncComputed
export default class PersonComponent extends Vue {
selectedPersonId: string = undefined;
// ...
// Example method that loads persons data from API
#AsyncComputed()
async persons(): Promise<Person[]> {
return await apiClient.persons.getAll();
}
// Computed property that transforms api data to option list
get personSelectOptions() {
const persons = this.persons as Person[];
return persons.map((person) => ({
text: person.name,
value: person.id
}));
}
// Select the first person in the options list whenever the options change
#Watch('personSelectOptions')
automaticallySelectFirstPerson(persons: {value: string}[]) {
this.selectedPersonId = persons[0].value;
}
}
I'm trying to adapt a working form I found for polymer 1.0 in order to work on polymer 2.0 but these two way data bindings don't seem to work.
My template:
<paper-input>
<label>Username</label>
<iron-input bind-value={{formData.username}}><input id="username" type="text" value="{{formData::input}}"></iron-input>
</paper-input>
<paper-input>
<label>Password</label>
<iron-input bind-value={{formData.password}}><input id="password" type="password" value="{{formData::input}}"></iron-input>
</paper-input>
<div class="wrapper-btns">
<paper-button raised class="primary" on-tap="postLogin">Log In</paper-button>
<paper-button class="link" on-tap="postRegister">Sign Up</paper-button>
</div>
My actions:
class MyLogin extends Polymer.Element {
static get is() { return 'my-login'; }
static get properties() {
return {
storedUser: Object,
error: String,
formData: {
type: Object,
value: {},
},
}
}
_setReqBody() {
console.log(this.formData) // <--- THIS LINE!
this.$.registerLoginAjax.body = this.formData;
}
postLogin() {
this.$.registerLoginAjax.url = 'http://localhost:3001/sessions/create';
this._setReqBody();
this.$.registerLoginAjax.generateRequest();
}
}
The indicate line will always print undefined though. What am I doing wrong?
Here's the full code: https://github.com/lpfjustino/PolymerQuickstart/blob/master/web/src/my-login.html
And here's the code mine is based on:
https://auth0.com/blog/build-your-first-app-with-polymer-and-web-components/
paper-input doesn't need any label nor iron-input inside it by default. That only goes for paper-input-container. So the following would be okay
<paper-input label="Username" value="{{formData.username}}"></paper-input>
However if you insist on using paper-input-container instead, then
<paper-input-container>
<label slot="label">Username</label>
<iron-input bind-value="{{formData.username}}" slot="input">
<!-- You don't need to add two-way binding for your input element here
since `iron-input` already handles that for you using its `bind-value`
attribute. -->
<input />
</iron-input>
</paper-input-container>
Also, when you declare an Object property, you should initialize it using a function to insure that each element instance would have its own copy of the property.
static get properties() {
return {
...
formData: {
type: Object,
value: function() {
return {}; // or return { username: '', password: '' }
},
},
};
}