oData Model declared in component.js - where are my data? - odata

I have an application which is working fine but has a slight problem, oData in certain cases are not ready when the first view is loaded. The app uses 2 data models, both of them declared in manifest.json, one anonymous for the main work and another one named "f4" for handling of some entities used in certain formatters and dropdowns. Data for the latter model are getting loaded via a series of read operations in the init function of the root view. Leaving aside the issue mentioned in the beginning, everything else works.
Thanks to some expert advice here, found the cause of my problem. Router gets initialized before loading of data, so I decided to move the read ops from the root view to component.js.
Here comes the problem. Model is known to the app but seems to be empty from data. I know that data are getting loaded because I debugged the application and observed the app passing from all the read success functions before successfully initializing the router. Read in another post that doing this in onInit isn't the best possible practice, so I moved again functionality from the onInit to onAfterRendering function as advised but results are still the same, no data.
So the question is: How can I access an oData model, get the required entries and made them known to the app BEFORE router initialization?
Component.js
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/Device",
"kristal/apps/agreements/model/models",
"kristal/apps/agreements/controller/ErrorHandler"
], function (UIComponent, Device, models, ErrorHandler) {
"use strict";
// Promise vars
var oModelTypeDataDeferred = jQuery.Deferred();
var oModelStatusDataDeferred = jQuery.Deferred();
var oModelActionTypeDataDeferred = jQuery.Deferred();
var oModelRoleDataDeferred = jQuery.Deferred();
var oModelRefDataDeferred = jQuery.Deferred();
var oModelExtOrgDataDeferred = jQuery.Deferred();
var oModelInvolvementDataDeferred = jQuery.Deferred();
return UIComponent.extend("kristal.apps.agreements.Component", {
metadata : {
manifest: "json"
},
/**
* The component is initialized by UI5 automatically during the startup of the app and calls the init method once.
* In this function, the FLP and device models are set and the router is initialized.
* #public
* #override
*/
init : function () {
// call the base component's init function
UIComponent.prototype.init.apply(this, arguments);
// initialize the error handler with the component
this._oErrorHandler = new ErrorHandler(this);
// set the device model
this.setModel(models.createDeviceModel(), "device");
// set the FLP model
this.setModel(models.createFLPModel(), "FLP");
// Initialize additional data - UITGKA
var oModel = this.getModel();
var sPath = "/Agreement_TypesSet";
oModel.read(sPath, {
success: function(oData, oResponse) {
oModelTypeDataDeferred.resolve();
},
error: function(oError) {
jQuery.sap.log.error("Error", oError);
}
});
sPath = "/Agreement_StatusesSet";
oModel.read(sPath, {
success: function(oData, oResponse) {
oModelStatusDataDeferred.resolve();
},
error: function(oError) {
jQuery.sap.log.error("Error", oError);
}
});
sPath = "/Action_TypesSet";
oModel.read(sPath, {
success: function(oData, oResponse) {
oModelActionTypeDataDeferred.resolve();
},
error: function(oError) {
jQuery.sap.log.error("Error", oError);
}
});
sPath = "/Role_TypesSet";
oModel.read(sPath, {
success: function(oData, oResponse) {
oModelRoleDataDeferred.resolve();
},
error: function(oError) {
jQuery.sap.log.error("Error", oError);
}
});
sPath = "/Reference_TypesSet";
oModel.read(sPath, {
success: function(oData, oResponse) {
oModelRefDataDeferred.resolve();
},
error: function(oError) {
jQuery.sap.log.error("Error", oError);
}
});
sPath = "/External_OrganizationsSet";
oModel.read(sPath, {
success: function(oData, oResponse) {
oModelRefDataDeferred.resolve();
},
error: function(oError) {
jQuery.sap.log.error("Error", oError);
}
});
sPath = "/Involvement_TypesSet";
oModel.read(sPath, {
success: function(oData, oResponse) {
oModelInvolvementDataDeferred.resolve();
},
error: function(oError) {
jQuery.sap.log.error("Error", oError);
}
});
var readyToGo = function() {
jQuery.sap.log.error("Ready", "f4");
this.getRouter().initialize();
};
jQuery.when(oModelTypeDataDeferred, oModelStatusDataDeferred, oModelActionTypeDataDeferred, oModelRoleDataDeferred, +
oModelRefDataDeferred, oModelExtOrgDataDeferred, oModelInvolvementDataDeferred).done().then( jQuery.proxy(readyToGo, this) );
},
/**
* The component is destroyed by UI5 automatically.
* In this method, the ErrorHandler is destroyed.
* #public
* #override
*/
destroy : function () {
this._oErrorHandler.destroy();
// call the base component's destroy function
UIComponent.prototype.destroy.apply(this, arguments);
},
/**
* This method can be called to determine whether the sapUiSizeCompact or sapUiSizeCozy
* design mode class should be set, which influences the size appearance of some controls.
* #public
* #return {string} css class, either 'sapUiSizeCompact' or 'sapUiSizeCozy' - or an empty string if no css class should be set
*/
getContentDensityClass : function() {
if (this._sContentDensityClass === undefined) {
// check whether FLP has already set the content density class; do nothing in this case
if (jQuery(document.body).hasClass("sapUiSizeCozy") || jQuery(document.body).hasClass("sapUiSizeCompact")) {
this._sContentDensityClass = "";
} else if (!Device.support.touch) { // apply "compact" mode if touch is not supported
this._sContentDensityClass = "sapUiSizeCompact";
} else {
// "cozy" in case of touch support; default for most sap.m controls, but needed for desktop-first controls like sap.ui.table.Table
this._sContentDensityClass = "sapUiSizeCozy";
}
}
return this._sContentDensityClass;
}
});
}
);
Root view controller (app.controller.js)
sap.ui.define([
"kristal/apps/agreements/controller/BaseController",
"sap/ui/model/json/JSONModel"
], function (BaseController, JSONModel) {
"use strict";
return BaseController.extend("kristal.apps.agreements.controller.App", {
onInit : function () {
var oViewModel,
fnSetAppNotBusy,
iOriginalBusyDelay = this.getView().getBusyIndicatorDelay();
oViewModel = new JSONModel({
busy : true,
delay : 0
});
this.setModel(oViewModel, "appView");
fnSetAppNotBusy = function() {
oViewModel.setProperty("/busy", false);
oViewModel.setProperty("/delay", iOriginalBusyDelay);
};
this.getOwnerComponent().getModel().metadataLoaded().
then(fnSetAppNotBusy);
// apply content density mode to root view
this.getView().addStyleClass(this.getOwnerComponent().getContentDensityClass());
},
onBeforeRendering : function() {
}
});
}
);
Worklist Controller (Worklist.controller.js)
sap.ui.define([
"kristal/apps/agreements/controller/BaseController",
"sap/ui/model/json/JSONModel",
"sap/ui/core/routing/History",
"kristal/apps/agreements/model/formatter",
"sap/ui/model/Filter",
"sap/ui/model/FilterOperator"
], function (BaseController, JSONModel, History, formatter, Filter, FilterOperator) {
"use strict";
return BaseController.extend("kristal.apps.agreements.controller.Worklist", {
formatter: formatter,
/* =========================================================== */
/* lifecycle methods */
/* =========================================================== */
/**
* Called when the worklist controller is instantiated.
* #public
*/
onInit : function () {
var oViewModel,
iOriginalBusyDelay,
oTable = this.byId("table");
// Put down worklist table's original value for busy indicator delay,
// so it can be restored later on. Busy handling on the table is
// taken care of by the table itself.
iOriginalBusyDelay = oTable.getBusyIndicatorDelay();
// keeps the search state
this._oTableSearchState = [];
// Model used to manipulate control states
oViewModel = new JSONModel({
worklistTableTitle : this.getResourceBundle().getText("worklistTableTitle"),
saveAsTileTitle: this.getResourceBundle().getText("worklistViewTitle"),
shareOnJamTitle: this.getResourceBundle().getText("worklistViewTitle"),
shareSendEmailSubject: this.getResourceBundle().getText("shareSendEmailWorklistSubject"),
shareSendEmailMessage: this.getResourceBundle().getText("shareSendEmailWorklistMessage", [location.href]),
tableNoDataText : this.getResourceBundle().getText("tableNoDataText"),
tableBusyDelay : 0
});
this.setModel(oViewModel, "worklistView");
// Make sure, busy indication is showing immediately so there is no
// break after the busy indication for loading the view's meta data is
// ended (see promise 'oWhenMetadataIsLoaded' in AppController)
oTable.attachEventOnce("updateFinished", function(){
// Restore original busy indicator delay for worklist's table
oViewModel.setProperty("/tableBusyDelay", iOriginalBusyDelay);
});
// Initialize column sorters
this._IDSorter = new sap.ui.model.Sorter("AgrId", false);
this._TypeSorter = new sap.ui.model.Sorter("AgrTypeid", false, function(oContext) {
var target = oContext.getProperty("ct>AgrTypeid");
if (target.length === 0) {
return {
key: "",
text: "No Category"
};
} else {
return {
key: formatter.textAgreementType(target),
text: formatter.textAgreementType(target)
};
}
});
this._PriceSorter = new sap.ui.model.Sorter("Price", false);
this._StatusSorter = new sap.ui.model.Sorter("AgrStatid", false, function(oContext) {
var target = oContext.getProperty("ct>AgrStatid");
if (target.length === 0) {
return {
key: "",
text: "No Category"
};
} else {
return {
key: formatter.textStatus(target),
text: formatter.textStatus(target)
};
}
});
// filter bar stuff
this.oFilterBar = null;
var sViewId = this.getView().getId();
this.oFilterBar = sap.ui.getCore().byId(sViewId + "--filterBar");
this.oFilterBar.registerFetchData(this.fFetchData);
this.oFilterBar.registerApplyData(this.fApplyData);
this.oFilterBar.registerGetFiltersWithValues(this.fGetFiltersWithValues);
this.fVariantStub();
//this.onToggleSearchField();
this.oFilterBar.fireInitialise();
this._sHeader = this.oFilterBar.getHeader();
},
/* =========================================================== */
/* event handlers */
/* =========================================================== */
/**
* Triggered by the table's 'updateFinished' event: after new table
* data is available, this handler method updates the table counter.
* This should only happen if the update was successful, which is
* why this handler is attached to 'updateFinished' and not to the
* table's list binding's 'dataReceived' method.
* #param {sap.ui.base.Event} oEvent the update finished event
* #public
*/
onUpdateFinished : function (oEvent) {
// update the worklist's object counter after the table update
var sTitle,
oTable = oEvent.getSource(),
iTotalItems = oEvent.getParameter("total");
// only update the counter if the length is final and
// the table is not empty
if (iTotalItems && oTable.getBinding("items").isLengthFinal()) {
sTitle = this.getResourceBundle().getText("worklistTableTitleCount", [iTotalItems]);
} else {
sTitle = this.getResourceBundle().getText("worklistTableTitle");
}
this.getModel("worklistView").setProperty("/worklistTableTitle", sTitle);
},
/**
* Event handler when a table item gets pressed
* #param {sap.ui.base.Event} oEvent the table selectionChange event
* #public
*/
onPress : function (oEvent) {
// The source is the list item that got pressed
this._showObject(oEvent.getSource());
},
/**
* Event handler for navigating back.
* It there is a history entry or an previous app-to-app navigation we go one step back in the browser history
* If not, it will navigate to the shell home
* #public
*/
onNavBack : function() {
var sPreviousHash = History.getInstance().getPreviousHash(),
oCrossAppNavigator = sap.ushell.Container.getService("CrossApplicationNavigation");
if (sPreviousHash !== undefined || !oCrossAppNavigator.isInitialNavigation()) {
history.go(-1);
} else {
oCrossAppNavigator.toExternal({
target: {shellHash: "#Shell-home"}
});
}
},
/**
* Event handler when the share in JAM button has been clicked
* #public
*/
onShareInJamPress : function () {
var oViewModel = this.getModel("worklistView"),
oShareDialog = sap.ui.getCore().createComponent({
name: "sap.collaboration.components.fiori.sharing.dialog",
settings: {
object:{
id: location.href,
share: oViewModel.getProperty("/shareOnJamTitle")
}
}
});
oShareDialog.open();
},
onSearch : function (oEvent) {
/* if (oEvent.getParameters().refreshButtonPressed) {
// Search field's 'refresh' button has been pressed.
// This is visible if you select any master list item.
// In this case no new search is triggered, we only
// refresh the list binding.
this.onRefresh();
} else { */
var oFilter;
var oTableSearchState = [];
//var sQuery = oEvent.getParameter("query");
var sSearchKey = this.getView().byId("application-agreements-display-component---worklist--searchField").getValue();
var sStatusKey = this.getView().byId("application-agreements-display-component---worklist--cbStatus").getSelectedKey();
var sTypeKey = this.getView().byId("application-agreements-display-component---worklist--cbType").getSelectedKey();
//if (sQuery && sQuery.length > 0) {
//oTableSearchState = [new Filter("AgrId", FilterOperator.Contains, sQuery)];
//var oFilter = new Filter("AgrId", FilterOperator.Contains, sQuery);
if (sSearchKey !== "") {
oFilter = new Filter("AgrId", FilterOperator.Contains, sSearchKey);
oTableSearchState.push(oFilter);
}
if (sStatusKey !== "") {
oFilter = new Filter("AgrStatid", FilterOperator.EQ, sStatusKey);
oTableSearchState.push(oFilter);
}
if (sTypeKey !== "") {
oFilter = new Filter("AgrTypeid", FilterOperator.EQ, sTypeKey);
oTableSearchState.push(oFilter);
}
//}
this._applySearch(oTableSearchState);
//}
},
/**
* Event handler for refresh event. Keeps filter, sort
* and group settings and refreshes the list binding.
* #public
*/
onRefresh : function() {
var oTable = this.byId("table");
oTable.getBinding("items").refresh();
},
// Custom sorters
onSortID : function(){
this._IDSorter.bDescending = !this._IDSorter.bDescending;
this.byId("table").getBinding("items").sort(this._IDSorter);
},
onSortType : function(){
this._TypeSorter.bDescending = !this._TypeSorter.bDescending;
this.byId("table").getBinding("items").sort(this._TypeSorter);
},
/*onSortComment : function(){
this._CommentSorter.bDescending = !this._CommentSorter.bDescending;
this.byId("table").getBinding("items").sort(this._CommentSorter);
},*/
onSortPrice : function(){
this._PriceSorter.bDescending = !this._PriceSorter.bDescending;
this.byId("table").getBinding("items").sort(this._PriceSorter);
},
onSortStatus : function(){
this._StatusSorter.bDescending = !this._StatusSorter.bDescending;
this.byId("table").getBinding("items").sort(this._StatusSorter);
},
/* =========================================================== */
/* internal methods */
/* =========================================================== */
/**
* Shows the selected item on the object page
* On phones a additional history entry is created
* #param {sap.m.ObjectListItem} oItem selected Item
* #private
*/
_showObject : function (oItem) {
this.getRouter().navTo("object", {
objectId: oItem.getBindingContext().getProperty("AgrId")
});
},
/**
* Internal helper method to apply both filter and search state together on the list binding
* #param {object} oTableSearchState an array of filters for the search
* #private
*/
_applySearch: function(oTableSearchState) {
var oTable = this.byId("table"),
oViewModel = this.getModel("worklistView");
oTable.getBinding("items").filter(oTableSearchState, "Application");
// changes the noDataText of the list in case there are no filter results
if (oTableSearchState.length !== 0) {
oViewModel.setProperty("/tableNoDataText", this.getResourceBundle().getText("worklistNoDataWithSearchText"));
}
},
// Filter bar stuff
onToggleSearchField: function(oEvent) {
var oSearchField = this.oFilterBar.getBasicSearch();
if (!oSearchField) {
var oBasicSearch = new sap.m.SearchField({
showSearchButton: false
});
} else {
oSearchField = null;
}
this.oFilterBar.setBasicSearch(oBasicSearch);
oBasicSearch.attachBrowserEvent("keyup", jQuery.proxy(function(e) {
if (e.which === 13) {
this.onSearch();
}
}, this));
},
onToggleShowFilters: function(oEvent) {
var bFlag = this.oFilterBar.getShowFilterConfiguration();
this.oFilterBar.setShowFilterConfiguration(!bFlag);
},
onToggleHeader: function(oEvent) {
var sHeader = "";
if (this.oFilterBar.getHeader() !== this._sHeader) {
sHeader = this._oHeader;
}
this.oFilterBar.setHeader(sHeader);
},
onChange: function(oEvent) {
this.oFilterBar.fireFilterChange(oEvent);
},
onClear: function(oEvent) {
var oItems = this.oFilterBar.getAllFilterItems(true);
for (var i = 0; i < oItems.length; i++) {
var oControl = this.oFilterBar.determineControlByFilterItem(oItems[i]);
if (oControl) {
var sType = oControl.getMetadata().getName();
if (sType === "sap.m.ComboBox") {
oControl.setSelectedKey("");
} else {
oControl.setValue("");
}
}
}
},
_showToast: function(sMessage) {
jQuery.sap.require("sap.m.MessageToast");
sap.m.MessageToast.show(sMessage);
},
onCancel: function(oEvent) {
this._showToast("cancel triggered");
},
onReset: function(oEvent) {
this._showToast("reset triggered");
},
/*onSearch: function(oEvent) {
this._showToast("search triggered");
},*/
onFiltersDialogClosed: function(oEvent) {
this._showToast("filtersDialogClosed triggered");
},
fFetchData: function() {
var sGroupName;
var oJsonParam;
var oJsonData = [];
var oItems = this.getAllFilterItems(true);
for (var i = 0; i < oItems.length; i++) {
oJsonParam = {};
sGroupName = null;
if (oItems[i].getGroupName) {
sGroupName = oItems[i].getGroupName();
oJsonParam.group_name = sGroupName;
}
oJsonParam.name = oItems[i].getName();
var oControl = this.determineControlByFilterItem(oItems[i]);
if (oControl) {
oJsonParam.value = oControl.getValue();
oJsonData.push(oJsonParam);
}
}
return oJsonData;
},
fApplyData: function(oJsonData) {
var sGroupName;
for (var i = 0; i < oJsonData.length; i++) {
sGroupName = null;
if (oJsonData[i].group_name) {
sGroupName = oJsonData[i].group_name;
}
var oControl = this.determineControlByName(oJsonData[i].name, sGroupName);
if (oControl) {
oControl.setValue(oJsonData[i].value);
}
}
},
fGetFiltersWithValues: function() {
var i;
var oControl;
var aFilters = this.getFilterGroupItems();
var aFiltersWithValue = [];
for (i = 0; i < aFilters.length; i++) {
oControl = this.determineControlByFilterItem(aFilters[i]);
if (oControl && oControl.getValue && oControl.getValue()) {
aFiltersWithValue.push(aFilters[i]);
}
}
return aFiltersWithValue;
},
fVariantStub: function() {
var oVM = this.oFilterBar._oVariantManagement;
oVM.initialise = function() {
this.fireEvent("initialise");
this._setStandardVariant();
this._setSelectedVariant();
};
var nKey = 0;
var mMap = {};
var sCurrentVariantKey = null;
oVM._oVariantSet = {
getVariant: function(sKey) {
return mMap[sKey];
},
addVariant: function(sName) {
var sKey = "" + nKey++;
var oVariant = {
key: sKey,
name: sName,
getItemValue: function(s) {
return this[s];
},
setItemValue: function(s, oObj) {
this[s] = oObj;
},
getVariantKey: function() {
return this.key;
}
};
mMap[sKey] = oVariant;
return oVariant;
},
setCurrentVariantKey: function(sKey) {
sCurrentVariantKey = sKey;
},
getCurrentVariantKey: function() {
return sCurrentVariantKey;
},
delVariant: function(sKey) {
if (mMap[sKey]) {
delete mMap[sKey];
}
}
};
}
});
}
);
Worklist.view.xml
<mvc:View
controllerName="kristal.apps.agreements.controller.Worklist"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
xmlns:semantic="sap.m.semantic"
xmlns:fb="sap.ui.comp.filterbar"
xmlns:core="sap.ui.core"
xmlns:footerbar="sap.ushell.ui.footerbar">
<semantic:FullscreenPage
id="page"
navButtonPress="onNavBack"
showNavButton="true"
title="{i18n>worklistViewTitle}">
<semantic:content>
<fb:FilterBar id="filterBar" header="{i18n>worklistListFilters}" enableBasicSearch="true"
reset="onReset" search="onSearch" clear="onClear" cancel="onCancel"
filtersDialogClosed = "onFiltersDialogClosed"
showRestoreButton="true" showClearButton="true" showCancelButton="true">
<fb:filterItems>
<!--fb:FilterItem name="A" label="Delivery Date">
<fb:control>
<DatePicker change="onChange"/>
</fb:control>
</fb:FilterItem-->
<fb:FilterItem name="sSearch" label="Search Agreement">
<fb:control>
<Input
id="searchField"
tooltip="{i18n>worklistSearchTooltip}"
width="auto"/>
</fb:control>
</fb:FilterItem>
<fb:FilterItem name="A" label="{i18n>tableTypeColumnTitle}" labelTooltip="Agreement Types">
<fb:control>
<ComboBox id="cbType" items="{
path: 'f4>/Agreement_TypesSet',
sorter: { path: 'AgrTypeid' }
}">
<items>
<core:Item key="{f4>AgrTypeid}" text="{f4>AgrTypetxt}"/>
</items>
</ComboBox>
</fb:control>
</fb:FilterItem>
<fb:FilterItem name="B" label="{i18n>tableStatusColumnTitle}" labelTooltip="Statuses">
<fb:control>
<ComboBox id="cbStatus" items="{
path: 'f4>/Agreement_StatusesSet',
sorter: { path: 'AgrStatid' }
}">
<items>
<core:Item key="{f4>AgrStatid}" text="{f4>AgrStattxt}"/>
</items>
</ComboBox>
</fb:control>
</fb:FilterItem>
</fb:filterItems>
</fb:FilterBar>
<Table
id="table"
width="auto"
class="sapUiResponsiveMargin"
items="{
path: '/AgreementsSet',
sorter: { path: 'AgrId' }
}"
noDataText="{worklistView>/tableNoDataText}"
busyIndicatorDelay="{worklistView>/tableBusyDelay}"
growing="true"
growingScrollToLoad="true"
updateFinished="onUpdateFinished">
<headerToolbar>
<Toolbar>
<Title id="tableHeader" text="{worklistView>/worklistTableTitle}"/>
<ToolbarSpacer />
<!--SearchField
id="searchField"
tooltip="{i18n>worklistSearchTooltip}"
search="onSearch"
width="auto">
</SearchField-->
</Toolbar>
</headerToolbar>
<columns>
<Column id="nameColumn">
<header>
<Toolbar>
<Text text="{i18n>tableNameColumnTitle}" id="nameColumnTitle"/>
<Button
icon="sap-icon://sort"
press="onSortID" />
</Toolbar>
</header>
</Column>
<Column id="typeColumn">
<header>
<Toolbar>
<Text text="{i18n>tableTypeColumnTitle}" id="nameTypeTitle"/>
<Button
icon="sap-icon://sort"
press="onSortType" />
</Toolbar>
</header>
</Column>
<Column id="commentColumn">
<header>
<Toolbar>
<Text text="{i18n>tableCommentColumnTitle}" id="nameCommentTitle"/>
<!--Button
icon="sap-icon://sort"
press="onSortComment" /-->
</Toolbar>
</header>
</Column>
<Column id="unitNumberColumn" hAlign="Right">
<header align="Right">
<Toolbar>
<Text text="{i18n>tableUnitNumberColumnTitle}" id="unitNumberColumnTitle"/>
<Button
icon="sap-icon://sort"
press="onSortPrice" />
</Toolbar>
</header>
</Column>
<Column id="statusColumn" hAlign="Right">
<header align="Right">
<Toolbar>
<Text text="{i18n>tableStatusColumnTitle}" id="nameStatusTitle"/>
<Button
icon="sap-icon://sort"
press="onSortStatus" />
</Toolbar>
</header>
</Column>
</columns>
<items>
<ColumnListItem
type="Navigation"
press="onPress">
<cells>
<ObjectIdentifier
title="{AgrId}"
text="{Shorttitle}">
</ObjectIdentifier>
<Text text="{
path: 'AgrTypeid',
formatter: '.formatter.textAgreementType'
}"/>
<Text text="{AgrComment}"/>
<ObjectNumber
number="{
path: 'Price',
formatter: '.formatter.numberUnit'
}"
unit="{Curr}"/>
<ObjectStatus
text="{
path: 'AgrStatid',
formatter: '.formatter.textStatus'
}"
state="{
path: 'AgrStatid',
formatter: '.formatter.stateStatus'
}"
/>
</cells>
</ColumnListItem>
</items>
</Table>
</semantic:content>
<semantic:sendEmailAction>
<semantic:SendEmailAction id="shareEmail" press="onShareEmailPress"/>
</semantic:sendEmailAction>
<semantic:shareInJamAction>
<semantic:ShareInJamAction id="shareInJam" visible="{FLP>/isShareInJamActive}" press="onShareInJamPress"/>
</semantic:shareInJamAction>
<semantic:saveAsTileAction>
<footerbar:AddBookmarkButton id ="shareTile" title="{worklistView>/saveAsTileTitle}" />
</semantic:saveAsTileAction>
</semantic:FullscreenPage>

As well as the earlier responses let me add that I usually use the metadataLoaded promise to ensure the model is fully instantiated.
init : function () {
...
sap.ui.getCore().getModel()..metadataLoaded().then(this._onMetadataLoaded.bind(this));
},
_onMetadataLoaded: function() {
...
}

The question has been revised in such a level that doesn't reflect the initial problem, which was mistaken use of the additional named model in component.js. The way question looks now, the problem is just the var oModel = this.getModel(); statement in component.js where the model name is missing.
Thank you #BoghyonHoffmann not only for providing the solution but for being so patient in the last days and guiding me all along as well. It's not only about the thing working but also about the lots of mistaken perceptions that were clarified here, regarding the use of the data model, cheers!

Related

How can I pass reference data from C# controller into a knockout view model so it can resolve an ID to it's text value

I have c# classes that need to be passed into a knockout view model for look up purposes. Show a user status of 1000 or a role of 14038 is useless.
I need to be able to resolve those values to their text representation.
I've got some "reference data" populated in a database. On top of that are some T4 transformations which translate the reference data into C# code, for example:
public static class UserStatus
{
#region Members
public const string ClassName = "UserStatus";
public const int Pending = 1000;
public const int Active = 1001;
public const int Inactive = 1002;
public const int Deleted = 1003;
#endregion
}
This class is then used throughout the code to assign values to a User class, instead of saying user.UserStatus = 1000 it's user.UserStatus = UserStatus.Pending.
Now onto the issue...
CURRENT ISSUE
I've got a page that lists users in the system and one of the columns in the list is the user's status. Well that status passed from in the object is 1000 not "Pending". What I'd like to do is be able to resole 1000 to "Pending" in knockout. Problem is because knockout is executed on the client side it has no knowledge of my C# classes. Ideally I'd like to be able to pre-poplate a list in my controller with the possible values of a UserStatus, pass that into my knockout view model and have it loop through the possible statuses and resolve it based on that specific users status.
HTML Code
<tbody style="display: none;" class="table-body" data-bind="visible: true, foreach: { data: viewData.ClientGroups, as: 'ClientGroup' }">
<tr>
<td><span data-bind="html: ClientGroup.Name()"></span></td>
<td>TODO: Bind Client</td>
<td><span data-bind="html: ClientGroup.StatusText()"></span></td>
<td><span data-bind="html: ClientGroup.CreatedOnText()"></span></td>
</tr>
</tbody>
Creating and binding the knockout view model.
var viewData = {};
require(['main'], function () {
require(['message', 'viewModel/clientGroupViewModel', 'viewModel/clientGroupDetailsViewModel'],
function (message, clientGroupViewModel, clientGroupDetailsViewModel) {
$(document).ready(function () {
// enable ko punches
ko.punches.enableAll();
var json = #Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model, Newtonsoft.Json.Formatting.None, new IHtmlStringConverter()));
// I'd like to be able to create something here and pass it to my model.
var lookupValues = { blah }
viewData = new clientGroupViewModel(json, lookupValues);
var zdm = new clientGroupDetailsViewModel(json, lookupValues );
ko.applyBindings(viewData, document.getElementById('clientGroupAdmin'));
ko.applyBindings(zdm, document.getElementById('detailsModal'));
});
});
});
In the knockout view model
_self.StatusText = ko.computed(function () {
console.log('user status');
if (ko.utils.arrayFirst(UserStatus, function (value) {
console.log(value);
return value.Id == _self.UserStatus();
}));
return 'false';
});
Please read my extra info questions but this is one way how I'd go about it..
console.clear();
var $fakeAsync = function (api, result) {
var isEmptyCall = _.isNull(api) || api.length === 0;
dfd = $.Deferred(function () {
setTimeout(function () {
dfd.resolve(isEmptyCall ? null : result);
}, isEmptyCall ? 0 : 50);
});
return dfd.promise();
};
var $fakeData = [
{Id: 1, Name: 'foo', UserStatus: 1000},
{Id: 2, Name: 'bar', UserStatus: 1001},
{Id: 3, Name: 'stack', UserStatus: 1000},
{Id: 4, Name: 'overflow', UserStatus: 1002}
]
var $fakeUserStates = {
1000: 'pending',
1001: 'ready',
1002: 'cancelled',
1003: 'banned'
};
function MyViewModel() {
var self = this;
self.userStates = [];
self.loadMapping = function () {
return $fakeAsync('api/fakemappingcall', $fakeUserStates).done(function (result) {
console.log('mapping gathered');
self.userStates = result;
});
};
self.loadData = function () {
return $fakeAsync('api/fakedatacall', $fakeData);
};
self.method = function () {
self.loadData().done(function (result){
_.each(result, function (r) { _.extend(r, { 'UserStatusText': self.userStates[r['UserStatus']] }) });
console.log(result);
});
};
self.loadMapping().done(function () {
self.method();
});
};
ko.applyBindings(new MyViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

How to draw a Highcharts in opened modal window by using ui-bootstrap directive?

My code is
factory('modalWindowFactory', function ($modal) {
var modalWindowController = _modalWindowController;
return {
show: function (title, name,data,confirmCallback, cancelCallback) {
// Show window
var modalInstance = $modal.open({
animation : true,
templateUrl: '/static/views/common/modal-view.html',
controller: modalWindowController,
size: 'lg',
resolve: {
title: function () {
return title;
},
name :function(){
return name;
},
data :function(){
return data;
}
}
});
the modal controller is
function _modalWindowController ($scope, $modalInstance, title,name,data){
$scope.title = "";
if (title) {
$scope.title = title;
}
$scope.name = name;
$scope.data = data;
$scope.chart = function(title,name,data){
$('#detailChart').highcharts({
...
})
};
//this line does not work!
$($scope.chart($scope.title,$scope.name,$scope.data));
$scope.confirm = function () {
$modalInstance.close();
};
$scope.cancel = function () {
$modalInstance.dismiss();
};
}
I bind a click function to open this modal,but when I click to open the modal window,there is only Text without any highcharts,my highchart code also worked alone,http://jsfiddle.net/3a33rqdv/
I don't know what to do now
I resolved it by using $ModalInstance.rendered.then,it's a promise type,thank you all
take reference from following code and use same logic in your directive:
$('[data-toggle=popover]').popover({
content: "<div id='container' style='min-width: 300px;display:none;
height: 200px; margin: 0'> <div id='Austin' style='width: 300px; height:
200px;'></div></div>",
html: true
})
.click(function() {
var chart_data = getChartData();
var chart = new Highcharts.Chart( chart_data );
$('#container').show();
});
Here is my fiddle for refrence

asp.net mvc 4 bootstrap jquery combobox autocomplete

I'm having problems with the rendering of combobox in my project.
The autocomplete functionality works correctly. But you can not click to select the items in the combobox. Also the items are badly formed, and a descriptive label appears at the bottom.
Print:
http://tinypic.com/r/2jebtau/8
View:
<div class="col-lg-10">
#Html.DropDownList("IdProcedimento", (IEnumerable<SelectListItem>)ViewBag.PriorityID, new { #class = "form-control", id = "combobox" })
</div>
Script:
(function ($) {
$.widget("custom.combobox", {
_create: function () {
this.wrapper = $("<span>")
.addClass("custom-combobox")
.insertAfter(this.element);
this.element.hide();
this._createAutocomplete();
this._createShowAllButton();
},
_createAutocomplete: function () {
var selected = this.element.children(":selected"),
value = selected.val() ? selected.text() : "";
this.input = $("<input>")
.appendTo(this.wrapper)
.val(value)
.attr("title", "")
.addClass("custom-combobox-input ui-widget ui-widget-content ui-state-default ui-corner-left")
.autocomplete({
delay: 0,
minLength: 0,
source: $.proxy(this, "_source")
})
.tooltip({
tooltipClass: "ui-state-highlight"
});
this._on(this.input, {
autocompleteselect: function (event, ui) {
ui.item.option.selected = true;
this._trigger("select", event, {
item: ui.item.option
});
},
autocompletechange: "_removeIfInvalid"
});
},
_createShowAllButton: function () {
var input = this.input,
wasOpen = false;
$("<a>")
.attr("tabIndex", -1)
.tooltip()
.appendTo(this.wrapper)
.button({
icons: {
primary: "ui-icon-triangle-1-s"
},
text: false
})
.removeClass("ui-corner-all")
.addClass("custom-combobox-toggle ui-corner-right")
.mousedown(function () {
wasOpen = input.autocomplete("widget").is(":visible");
})
.click(function () {
input.focus();
// Close if already visible
if (wasOpen) {
return;
}
// Pass empty string as value to search for, displaying all results
input.autocomplete("search", "");
});
},
_source: function (request, response) {
var matcher = new RegExp($.ui.autocomplete.escapeRegex(request.term), "i");
response(this.element.children("option").map(function () {
var text = $(this).text();
if (this.value && (!request.term || matcher.test(text)))
return {
label: text,
value: text,
option: this
};
}));
},
_removeIfInvalid: function (event, ui) {
// Selected an item, nothing to do
if (ui.item) {
return;
}
// Search for a match (case-insensitive)
var value = this.input.val(),
valueLowerCase = value.toLowerCase(),
valid = false;
this.element.children("option").each(function () {
if ($(this).text().toLowerCase() === valueLowerCase) {
this.selected = valid = true;
return false;
}
});
// Found a match, nothing to do
if (valid) {
return;
}
// Remove invalid value
this.input
.val("")
this.element.val("");
this._delay(function () {
this.input.tooltip("close").attr("title", "");
}, 2500);
this.input.data("ui-autocomplete").term = "";
},
_destroy: function () {
this.wrapper.remove();
this.element.show();
}
});
})(jQuery);
$(function () {
$("#combobox").combobox();
});
Try to add these files:
jquery-ui.css // this is for styling
jquery-1.10.2.js
jquery-ui.js"
And the following css classes are also required:
.custom-combobox {
position: relative;
display: inline-block;
}
.custom-combobox-toggle {
position: absolute;
top: 0;
bottom: 0;
margin-left: -1px;
padding: 0;
/* support: IE7 */
*height: 1.7em;
*top: 0.1em;
}
.custom-combobox-input {
margin: 0;
padding: 0.3em;
}
And check if "form-control" class is having efects.
You have downloaded this from here
Have you changed the paths of images(icons) which are being used by the plugins in CSS.

How to Upload files in SAPUI5?

How to upload file in SAP Netweaver server using SAPUI5? I tried to upload file using FileUploader but did not get the luck if any one can help it will be very appreciated.
Thanks in Advance
Nothing was added to the manifest nor the component nor index files. It is working for me, you just need to change the number of columns to whatever you want to fit your file.
UploadFile.view.xml
<VBox>
<sap.ui.unified:FileUploader id="idfileUploader" typeMissmatch="handleTypeMissmatch" change="handleValueChange" maximumFileSize="10" fileSizeExceed="handleFileSize" maximumFilenameLength="50" filenameLengthExceed="handleFileNameLength" multiple="false" width="50%" sameFilenameAllowed="false" buttonText="Browse" fileType="CSV" style="Emphasized" placeholder="Choose a CSV file"/>
<Button text="Upload your file" press="onUpload" type="Emphasized"/>
</VBox>
UploadFile.controller.js
sap.ui.define(["sap/ui/core/mvc/Controller", "sap/m/MessageToast", "sap/m/MessageBox", "sap/ui/core/routing/History"], function(
Controller, MessageToast, MessageBox, History) {
"use strict";
return Controller.extend("cafeteria.controller.EmployeeFileUpload", {
onNavBack: function() {
var oHistory = History.getInstance();
var sPreviousHash = oHistory.getPreviousHash();
if (sPreviousHash !== undefined) {
window.history.go(-1);
} else {
var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
oRouter.navTo("admin", true);
}
},
handleTypeMissmatch: function(oEvent) {
var aFileTypes = oEvent.getSource().getFileType();
jQuery.each(aFileTypes, function(key, value) {
aFileTypes[key] = "*." + value;
});
var sSupportedFileTypes = aFileTypes.join(", ");
MessageToast.show("The file type *." + oEvent.getParameter("fileType") +
" is not supported. Choose one of the following types: " +
sSupportedFileTypes);
},
handleValueChange: function(oEvent) {
MessageToast.show("Press 'Upload File' to upload file '" + oEvent.getParameter("newValue") + "'");
},
handleFileSize: function(oEvent) {
MessageToast.show("The file size should not exceed 10 MB.");
},
handleFileNameLength: function(oEvent) {
MessageToast.show("The file name should be less than that.");
},
onUpload: function(e) {
var oResourceBundle = this.getView().getModel("i18n").getResourceBundle();
var fU = this.getView().byId("idfileUploader");
var domRef = fU.getFocusDomRef();
var file = domRef.files[0];
var reader = new FileReader();
var params = "EmployeesJson=";
reader.onload = function(oEvent) {
var strCSV = oEvent.target.result;
var arrCSV = strCSV.match(/[\w .]+(?=,?)/g);
var noOfCols = 6;
var headerRow = arrCSV.splice(0, noOfCols);
var data = [];
while (arrCSV.length > 0) {
var obj = {};
var row = arrCSV.splice(0, noOfCols);
for (var i = 0; i < row.length; i++) {
obj[headerRow[i]] = row[i].trim();
}
data.push(obj);
}
var Len = data.length;
data.reverse();
params += "[";
for (var j = 0; j < Len; j++) {
params += JSON.stringify(data.pop()) + ", ";
}
params = params.substring(0, params.length - 2);
params += "]";
// MessageBox.show(params);
var http = new XMLHttpRequest();
var url = oResourceBundle.getText("UploadEmployeesFile").toString();
http.onreadystatechange = function() {
if (http.readyState === 4 && http.status === 200) {
var json = JSON.parse(http.responseText);
var status = json.status.toString();
switch (status) {
case "Success":
MessageToast.show("Data is uploaded succesfully.");
break;
default:
MessageToast.show("Data was not uploaded.");
}
}
};
http.open("POST", url, true);
http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
http.send(params);
};
reader.readAsBinaryString(file);
}
});
});
After researching a little more on this issue I finally solved this issue by myself I placed a file controller and a uploader in php which return the details related to files further, we can use it to upload it on server.
Here is the code I have used.
fileUpload.html
<!DOCTYPE html>
<html><head>
<meta http-equiv='X-UA-Compatible' content='IE=edge' />
<title>Hello World</title>
<script id='sap-ui-bootstrap' src='http://localhost/resources/sap-ui-core.js' data-sap-ui-theme='sap_goldreflection'
data-sap-ui-libs='sap.ui.commons'></script>
<script>
var layout = new sap.ui.commons.layout.MatrixLayout();
layout.setLayoutFixed(false);
// create the uploader and disable the automatic upload
var oFileUploader2 = new sap.ui.commons.FileUploader("myupload",{
name: "upload2",
uploadOnChange: true,
uploadUrl: "uploader.php",
uploadComplete: function (oEvent) {
var sResponse = oEvent.getParameter("response");
if (sResponse) {
alert(sResponse);
}
}});
layout.createRow(oFileUploader2);
// create a second button to trigger the upload
var oTriggerButton = new sap.ui.commons.Button({
text:'Trigger Upload',
press:function() {
// call the upload method
oFileUploader2.upload();
$("#myupload-fu_form").submit();
alert("hi");
}
});
layout.createRow(oTriggerButton);
layout.placeAt("sample2");
</script>
</head>
<body class='sapUiBody'>
<div id="sample2"></div>
</body>
</html>
uploader.php
<?php
print_r($_FILES);
?>
It would be good if we can see your code.
This should work.
var layout = new sap.ui.commons.layout.MatrixLayout();
layout.setLayoutFixed(false);
// create the uploader and disable the automatic upload
var oFileUploader2 = new sap.ui.commons.FileUploader({
name : "upload2",
uploadOnChange : false,
uploadUrl : "../../../upload"
});
layout.createRow(oFileUploader2);
// create a second button to trigger the upload
var oTriggerButton = new sap.ui.commons.Button({
text : 'Trigger Upload',
press : function() {
// call the upload method
oFileUploader2.upload();
}
});
layout.createRow(oTriggerButton);
layout.placeAt("sample2");

Add a additional <li> tag to the end of rails3-jquery-autocomplete plugin

I'm trying to add an addition tag to the end of the autocomplete list.
$('#address ul.ui-autocomplete').append("<li>Add Venue</li>");
I'm trying to figure out where I can place the code above to add the extra li to the autocomplete list.
Any help will be deeply appreciated.
This is the rails3-jquery-autocomplete file.
source: function( request, response ) {
$.getJSON( $(e).attr('data-autocomplete'), {
term: extractLast( request.term )
}, function() {
$(arguments[0]).each(function(i, el) {
var obj = {};
obj[el.id] = el;
$(e).data(obj);
});
response.apply(null, arguments);
});
},
open: function() {
// when appending the result list to another element, we need to cancel the "position: relative;" css.
if (append_to){
$(append_to + ' ul.ui-autocomplete').css('position', 'static');
}
},
search: function() {
// custom minLength
var minLength = $(e).attr('min_length') || 2;
var term = extractLast( this.value );
if ( term.length < minLength ) {
return false;
}
},
focus: function() {
// prevent value inserted on focus
return false;
},
select: function( event, ui ) {
var terms = split( this.value );
// remove the current input
terms.pop();
// add the selected item
terms.push( ui.item.value );
// add placeholder to get the comma-and-space at the end
if (e.delimiter != null) {
terms.push( "" );
this.value = terms.join( e.delimiter );
} else {
this.value = terms.join("");
if ($(this).attr('data-id-element')) {
$($(this).attr('data-id-element')).val(ui.item.id);
}
if ($(this).attr('data-update-elements')) {
var data = $(this).data(ui.item.id.toString());
var update_elements = $.parseJSON($(this).attr("data-update-elements"));
for (var key in update_elements) {
$(update_elements[key]).val(data[key]);
}
}
}
var remember_string = this.value;
$(this).bind('keyup.clearId', function(){
if($(this).val().trim() != remember_string.trim()){
$($(this).attr('data-id-element')).val("");
$(this).unbind('keyup.clearId');
}
});
$(this).trigger('railsAutocomplete.select');
return false;
}
});
}
Solved it with this.
$('#address').bind('autocompleteopen', function(event,data){
$('<li id="ac-add-venue">Add venue</li>').appendTo('ul.ui-autocomplete');
});

Resources