How to bind a list to a navigation property - odata

I have a master/detail application for directories and files. In my OData service i have a navigation property that leads from a directory to a set of files. I have a list in the detail view for the files of the directory. But i am having trouble binding it to the navigation property of the OData service
<mvc:View xmlns:core="sap.ui.core" xmlns:f="sap.ui.layout.form" xmlns:l="sap.ui.layout" xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" controllerName="FileUtility.view.Detail">
<Page id="detailPage" navButtonPress="onNavBack" showNavButton="{device>/isPhone}" title="Files">
<content>
<ObjectHeader iconActive="false" id="detailHeader" introActive="false" number="" numberUnit="" title="" titleActive="false">
<attributes id="detailAttributes">
<ObjectAttribute active="false" id="attribute" text="{i18n>detailText}"/>
</attributes>
<firstStatus id="detailStatus">
<ObjectStatus id="status" text=""/>
</firstStatus>
</ObjectHeader>
<List id="__list0" noDataText="Drop list items here" items="{path :'DirectorySet>/FileSet'}">
<items>
<ObjectListItem counter="0" id="__item5" showMarkers="false" title="{Name}" type="Active">
</ObjectListItem>
</items>
<core:ExtensionPoint name="extDetail"/>
</List>
</content>
<footer id="detailFooter">
<Toolbar id="detailToolbar">
<content>
<ToolbarSpacer id="toolbarSpacer"/>
<Button icon="sap-icon://action" id="actionButton" press="openActionSheet"/>
</content>
</Toolbar>
</footer>
</Page>
</mvc:View>
The items path is the source entity set name. Not sure where im supposed to get this name. The FileSet portion is from the navigation property. I am confused as how to map it in the view.
EDIT: i have removed the "item = { }" from the List tag and tried to bind it in the Detail.js file.
sap.ui.core.mvc.Controller.extend("FileUtility.view.Detail", {
onInit : function() {
this.oInitialLoadFinishedDeferred = jQuery.Deferred();
if(sap.ui.Device.system.phone) {
//Do not wait for the master when in mobile phone resolution
this.oInitialLoadFinishedDeferred.resolve();
} else {
this.getView().setBusy(true);
var oEventBus = this.getEventBus();
oEventBus.subscribe("Component", "MetadataFailed", this.onMetadataFailed, this);
oEventBus.subscribe("Master", "InitialLoadFinished", this.onMasterLoaded, this);
}
this.getRouter().attachRouteMatched(this.onRouteMatched, this);
},
onMasterLoaded : function (sChannel, sEvent) {
this.getView().setBusy(false);
this.oInitialLoadFinishedDeferred.resolve();
},
onMetadataFailed : function(){
this.getView().setBusy(false);
this.oInitialLoadFinishedDeferred.resolve();
this.showEmptyView();
},
onRouteMatched : function(oEvent) {
var oParameters = oEvent.getParameters();
var oView = this.getView();
var sEntityPath = "/" + oParameters.arguments.entity;
var oContext = new sap.ui.model.Binding(this.getView().getModel(), sEntityPath + '/FileSet');
this.getView().setBindingContext(oContext);
//var oList = oView.byId("__list0");
//oList.bindItems(sEntityPath + '/FileSet',sap.ui.getCore().byId(this.getView().byId('__item5').getId()));
//sap.ui.getCore().byId(this.getView().byId('__list0').getId()).bindItems(sEntityPath + '/FileSet',sap.ui.getCore().byId(this.getView().byId('__item5').getId()));
//this.bindView(sEntityPath);
jQuery.when(this.oInitialLoadFinishedDeferred).then(jQuery.proxy(function () {
// When navigating in the Detail page, update the binding context
if (oParameters.name !== "detail") {
return;
}
var oIconTabBar = oView.byId("idIconTabBar");
oIconTabBar.getItems().forEach(function(oItem) {
if(oItem.getKey() !== "selfInfo"){
oItem.bindElement(oItem.getKey());
}
});
//var oList = oView.byId("__list0");
//oList.bindItems(sEntityPath + '/FileSet');
//sap.ui.getCore().byId(this.getView().byId('__list0').getId()).bindItems(sEntityPath + '/FileSet',sap.ui.getCore().byId(this.getView().byId('__item0').getId()));
// Specify the tab being focused
var sTabKey = oParameters.arguments.tab;
this.getEventBus().publish("Detail", "TabChanged", { sTabKey : sTabKey });
if (oIconTabBar.getSelectedKey() !== sTabKey) {
oIconTabBar.setSelectedKey(sTabKey);
}
}, this));
},
bindView : function (sEntityPath) {
var oView = this.getView();
oView.bindElement(sEntityPath);
//Check if the data is already on the client
if(!oView.getModel().getData(sEntityPath)) {
// Check that the entity specified was found.
oView.getElementBinding().attachEventOnce("dataReceived", jQuery.proxy(function() {
var oData = oView.getModel().getData(sEntityPath);
if (!oData) {
this.showEmptyView();
this.fireDetailNotFound();
} else {
this.fireDetailChanged(sEntityPath);
}
}, this));
} else {
this.fireDetailChanged(sEntityPath);
}
},
showEmptyView : function () {
this.getRouter().myNavToWithoutHash({
currentView : this.getView(),
targetViewName : "FileUtility.view.NotFound",
targetViewType : "XML"
});
},
fireDetailChanged : function (sEntityPath) {
this.getEventBus().publish("Detail", "Changed", { sEntityPath : sEntityPath });
},
fireDetailNotFound : function () {
this.getEventBus().publish("Detail", "NotFound");
},
onNavBack : function() {
// This is only relevant when running on phone devices
this.getRouter().myNavBack("main");
},
onDetailSelect : function(oEvent) {
sap.ui.core.UIComponent.getRouterFor(this).navTo("detail",{
entity : oEvent.getSource().getBindingContext().getPath().slice(1),
tab: oEvent.getParameter("selectedKey")
}, true);
},
openActionSheet: function() {
if (!this._oActionSheet) {
this._oActionSheet = new sap.m.ActionSheet({
buttons: new sap.ushell.ui.footerbar.AddBookmarkButton()
});
this._oActionSheet.setShowCancelButton(true);
this._oActionSheet.setPlacement(sap.m.PlacementType.Top);
}
this._oActionSheet.openBy(this.getView().byId("actionButton"));
},
getEventBus : function () {
return sap.ui.getCore().getEventBus();
},
getRouter : function () {
return sap.ui.core.UIComponent.getRouterFor(this);
},
onExit : function(oEvent){
var oEventBus = this.getEventBus();
oEventBus.unsubscribe("Master", "InitialLoadFinished", this.onMasterLoaded, this);
oEventBus.unsubscribe("Component", "MetadataFailed", this.onMetadataFailed, this);
if (this._oActionSheet) {
this._oActionSheet.destroy();
this._oActionSheet = null;
}
}
});
I have added the binding code to the "onRouteMatched" method.
EDIT 2:
My binding in the controller:
var oList = oView.byId("__list0");
var oTemplate = this.getView().byId('__item5');
oList.bindItems(sEntityPath + '/FileSet',sap.ui.getCore().byId(oTemplate.getId()));
I am not getting any data back but the path in the SAPUI5 debugger is correct despite being marked as "invalid"
EDIT 3
I got it working using this code in the onRouteMatched function
var oList = oView.byId("__list0");
var oTemplate = oView.byId('__item5');
oTemplate.bindProperty('title', 'Name');
oList.bindItems(sEntityPath + '/FileSet', sap.ui.getCore().byId(oTemplate.getId()));

I think what you are missing is just setting the binding context on the detail view(Maybe you have have not seen the controller code).
Lets say you have 2 entities Folders and Files. Your Folders has a navigation property FileSet to Files.
Now you have Folders entity bound to the Master entity. But when you click on the Master and detail should load the files of A Folder so then you will have bind the detail view to the selected entry in master.
How can you get the selected entry in Master view??
In the event handler on action on Master you can call
this.getBindingContext().getPath()
and pass this over to the detail view. In the detail you can call
var oContext = new sap.ui.model.Binding(oModel,sPath)
//sPath is the path from master view , oModel is the oData model of your application
this.getView().setModel(oModel);
this.getView().setBindingContext(oContext);
//Now the FileSet of your list will fire and load the data in your list.
Hope this helps.

You are binding to a named model but are not referencing the "named" model in your code.
i.e. in your code you have
="{path :'DirectorySet>/FileSet'}">
yet in your controller none of your code references this model
e.g.
this.getView().getModel()
you should specify the name of the model (as you can have several models AND you appear to be binding to a named model.
If you initially named the model in question DirectorySet then specify that in the relevant operations - e.g.:
this.getView().getModel("DirectorySet")
I haven't been through all the code but this would be a good start.
EDIT:
Use the following to get the ACTUAL values:
jQuery.sap.log.setLevel(jQuery.sap.log.LogLevel['INFO']);
jQuery.sap.log.info("fully qualified path: " + sEntityPath + '/FileSet',sap.ui.getCore().byId(oTemplate.getId()));
Just want to be sure we are getting the full and correct binding path.

Related

How to view a single entry with oModel.read in UI5 Application

I want to display a single entry with oModel.read in my UI5 application and store it in a variable.
What i want to do is, to select a single entry of my Model and store it in a variable:
If i execute my code i get the following in the Browser-Console:
https://ibb.co/FmPNSPm
Here is my code (but is not working):
var hostPort = "";
var oDataPath = "/.../KOMMI_SERVICE/";
var sServiceUrl= hostPort + oDataPath;
var oModel = new sap.ui.model.odata.ODataModel (sServiceUrl, true);
var oJsonModel = new sap.ui.model.json.JSONModel();
var text123;
oModel.read("/Komm(ZSMATERIALTEXT ='"+text123+")",oEntry,null,false,
function(oData, oResponse){
},function(err){
console.log("err");
});
I think that this path "/Komm(ZSMATERIALTEXT ='"+text123+")" is not correct.
You can try using filter. For example:
var sPath = "/Komm";
var oFilter = [
new Filter("ZSMATERIALTEXT", "EQ", text123)
];
oModel.read(sPath, {
filters: oFilter,
success: function (oData, oResponse) {
// save variable
},
error: function (oError) {
// show error
}
});
Try this:
I think you're missing a " ' "...
oModel.read("/Komm(ZSMATERIALTEXT ='"+text123+"')",oEntry,null,false,
function(oData, oResponse){
},function(err){
console.log("err");
});
Or
oModel.read("/Komm", {
filters: [
new sap.ui.model.Filter("ZSMATERIALTEXT", sap.ui.model.FilterOperator.EQ, text123)
],
success: function (oData, oResponse) {
// do some...
},
error: function (oError) {
// error
}
});

showing document type alias or name of selected node in Multi node tree picker inside content tab in Umbraco V7

I'm looking for a way to show document type alias or name of selected content in Multinode tree picker inside content tab.
This will tremendously help me to quickly identify type of content attached. Also hovering a content added in MNTP inside content shows numeric path which is not quite useful, is there a way to show path names instead of id's?
Attaching image for reference.
Kindly suggest.
The easiest way to do this is to simply modify the MNTP property editor and have it show the extra data. It is however not really recommended to modify these files since it will be reverted every time you upgrade your Umbraco site.
There is a bit of a hacky workaround to achieve what you want however;
Angular has something called interceptors, allowing you to intercept and modify requests being made. By registrering an interceptor you could intercept the requests to contentpicker.html and redirect it to a contentpicker.html file located somewhere else - this means you will be able to just copy this file somewhere else and not modify the one in the umbraco folder being overwritten by upgrades.
Unfortunately in this case it isn't enough to just override the view being sent to the browser, since the document type alias is not actually available to that view - so the view would never be able to show the alias even if we modified it.
To work around that, you could create a copy of the contentpicker.controller.js file and create a modified version of that to use in your custom contentpicker.html view. This modified ContentPickerController would make sure to include the contentTypeAlias in the model being used in rendering the view.
All of this can be wrapped up as a "package" in the App_Plugins folder and it will be nicely separated away from being overwritten when you upgrade Umbraco, while being automatically loaded via the package.manifest file. The only caveat is that in case something is updated in the content picker code, you would have to manually merge those updates over into your custom content picker files - fortunately the content picker is rarely updated.
Place the following files in App_Plugins/CustomContentPicker/ folder and you should have what you want without actually modifying the core code:
package.manifest
{
"javascript": [
"~/App_Plugins/CustomContentPicker/customcontentpicker.controller.js"
]
}
customcontentpicker.html
<div ng-controller="Umbraco.PropertyEditors.CustomContentPickerController" class="umb-editor umb-contentpicker">
<ng-form name="contentPickerForm">
<ul class="unstyled list-icons"
ui-sortable
ng-model="renderModel">
<li ng-repeat="node in renderModel" ng-attr-title="{{model.config.showPathOnHover && 'Path: ' + node.path || undefined}}">
<i class="icon icon-navigation handle"></i>
<a href="#" prevent-default ng-click="remove($index)">
<i class="icon icon-delete red hover-show"></i>
<i class="{{node.icon}} hover-hide"></i>
{{node.name}}<br />
({{node.contentTypeAlias}})
</a>
<div ng-if="!dialogEditor && ((model.config.showOpenButton && allowOpenButton) || (model.config.showEditButton && allowEditButton))">
<small ng-if="model.config.showOpenButton && allowOpenButton"><a href ng-click="showNode($index)"><localize key="open">Open</localize></a></small>
<small ng-if="model.config.showEditButton && allowEditButton"><a href umb-launch-mini-editor="node"><localize key="edit">Edit</localize></a></small>
</div>
</li>
</ul>
<ul class="unstyled list-icons" ng-show="model.config.multiPicker === true || renderModel.length === 0">
<li>
<i class="icon icon-add blue"></i>
<a href="#" ng-click="openContentPicker()" prevent-default>
<localize key="general_add">Add</localize>
</a>
</li>
</ul>
<!--These are here because we need ng-form fields to validate against-->
<input type="hidden" name="minCount" ng-model="renderModel" />
<input type="hidden" name="maxCount" ng-model="renderModel" />
<div class="help-inline" val-msg-for="minCount" val-toggle-msg="minCount">
You need to add at least {{model.config.minNumber}} items
</div>
<div class="help-inline" val-msg-for="maxCount" val-toggle-msg="maxCount">
You can only have {{model.config.maxNumber}} items selected
</div>
</ng-form>
<umb-overlay
ng-if="contentPickerOverlay.show"
model="contentPickerOverlay"
view="contentPickerOverlay.view"
position="right">
</umb-overlay>
</div>
customcontentpicker.controller.js
angular.module('umbraco.services').config([
'$httpProvider',
function ($httpProvider) {
$httpProvider.interceptors.push(function ($q) {
return {
'request': function (request) {
var url = 'views/propertyeditors/contentpicker/contentpicker.html';
if (request.url.indexOf(url) !== -1) {
request.url = request.url.replace(url, '/App_Plugins/CustomContentPicker/customcontentpicker.html');
}
return request || $q.when(request);
}
};
});
}]);
// Below is contentpicker.controller.js modified
//this controller simply tells the dialogs service to open a mediaPicker window
//with a specified callback, this callback will receive an object with a selection on it
function customContentPickerController($scope, dialogService, entityResource, editorState, $log, iconHelper, $routeParams, fileManager, contentEditingHelper, angularHelper, navigationService, $location) {
function trim(str, chr) {
var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
return str.replace(rgxtrim, '');
}
function startWatch() {
//We need to watch our renderModel so that we can update the underlying $scope.model.value properly, this is required
// because the ui-sortable doesn't dispatch an event after the digest of the sort operation. Any of the events for UI sortable
// occur after the DOM has updated but BEFORE the digest has occured so the model has NOT changed yet - it even states so in the docs.
// In their source code there is no event so we need to just subscribe to our model changes here.
//This also makes it easier to manage models, we update one and the rest will just work.
$scope.$watch(function () {
//return the joined Ids as a string to watch
return _.map($scope.renderModel, function (i) {
return i.id;
}).join();
}, function (newVal) {
var currIds = _.map($scope.renderModel, function (i) {
return i.id;
});
$scope.model.value = trim(currIds.join(), ",");
//Validate!
if ($scope.model.config && $scope.model.config.minNumber && parseInt($scope.model.config.minNumber) > $scope.renderModel.length) {
$scope.contentPickerForm.minCount.$setValidity("minCount", false);
}
else {
$scope.contentPickerForm.minCount.$setValidity("minCount", true);
}
if ($scope.model.config && $scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) < $scope.renderModel.length) {
$scope.contentPickerForm.maxCount.$setValidity("maxCount", false);
}
else {
$scope.contentPickerForm.maxCount.$setValidity("maxCount", true);
}
});
}
$scope.renderModel = [];
$scope.dialogEditor = editorState && editorState.current && editorState.current.isDialogEditor === true;
//the default pre-values
var defaultConfig = {
multiPicker: false,
showOpenButton: false,
showEditButton: false,
showPathOnHover: false,
startNode: {
query: "",
type: "content",
id: $scope.model.config.startNodeId ? $scope.model.config.startNodeId : -1 // get start node for simple Content Picker
}
};
if ($scope.model.config) {
//merge the server config on top of the default config, then set the server config to use the result
$scope.model.config = angular.extend(defaultConfig, $scope.model.config);
}
//Umbraco persists boolean for prevalues as "0" or "1" so we need to convert that!
$scope.model.config.multiPicker = ($scope.model.config.multiPicker === "1" ? true : false);
$scope.model.config.showOpenButton = ($scope.model.config.showOpenButton === "1" ? true : false);
$scope.model.config.showEditButton = ($scope.model.config.showEditButton === "1" ? true : false);
$scope.model.config.showPathOnHover = ($scope.model.config.showPathOnHover === "1" ? true : false);
var entityType = $scope.model.config.startNode.type === "member"
? "Member"
: $scope.model.config.startNode.type === "media"
? "Media"
: "Document";
$scope.allowOpenButton = entityType === "Document" || entityType === "Media";
$scope.allowEditButton = entityType === "Document";
//the dialog options for the picker
var dialogOptions = {
multiPicker: $scope.model.config.multiPicker,
entityType: entityType,
filterCssClass: "not-allowed not-published",
startNodeId: null,
callback: function (data) {
if (angular.isArray(data)) {
_.each(data, function (item, i) {
$scope.add(item);
});
} else {
$scope.clear();
$scope.add(data);
}
angularHelper.getCurrentForm($scope).$setDirty();
},
treeAlias: $scope.model.config.startNode.type,
section: $scope.model.config.startNode.type
};
//since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the
// pre-value config on to the dialog options
angular.extend(dialogOptions, $scope.model.config);
//We need to manually handle the filter for members here since the tree displayed is different and only contains
// searchable list views
if (entityType === "Member") {
//first change the not allowed filter css class
dialogOptions.filterCssClass = "not-allowed";
var currFilter = dialogOptions.filter;
//now change the filter to be a method
dialogOptions.filter = function (i) {
//filter out the list view nodes
if (i.metaData.isContainer) {
return true;
}
if (!currFilter) {
return false;
}
//now we need to filter based on what is stored in the pre-vals, this logic duplicates what is in the treepicker.controller,
// but not much we can do about that since members require special filtering.
var filterItem = currFilter.toLowerCase().split(',');
var found = filterItem.indexOf(i.metaData.contentType.toLowerCase()) >= 0;
if (!currFilter.startsWith("!") && !found || currFilter.startsWith("!") && found) {
return true;
}
return false;
}
}
//if we have a query for the startnode, we will use that.
if ($scope.model.config.startNode.query) {
var rootId = $routeParams.id;
entityResource.getByQuery($scope.model.config.startNode.query, rootId, "Document").then(function (ent) {
dialogOptions.startNodeId = ent.id;
});
} else {
dialogOptions.startNodeId = $scope.model.config.startNode.id;
}
//dialog
$scope.openContentPicker = function () {
$scope.contentPickerOverlay = dialogOptions;
$scope.contentPickerOverlay.view = "treepicker";
$scope.contentPickerOverlay.show = true;
$scope.contentPickerOverlay.submit = function (model) {
if (angular.isArray(model.selection)) {
_.each(model.selection, function (item, i) {
$scope.add(item);
});
}
$scope.contentPickerOverlay.show = false;
$scope.contentPickerOverlay = null;
}
$scope.contentPickerOverlay.close = function (oldModel) {
$scope.contentPickerOverlay.show = false;
$scope.contentPickerOverlay = null;
}
};
$scope.remove = function (index) {
$scope.renderModel.splice(index, 1);
angularHelper.getCurrentForm($scope).$setDirty();
};
$scope.showNode = function (index) {
var item = $scope.renderModel[index];
var id = item.id;
var section = $scope.model.config.startNode.type.toLowerCase();
entityResource.getPath(id, entityType).then(function (path) {
navigationService.changeSection(section);
navigationService.showTree(section, {
tree: section, path: path, forceReload: false, activate: true
});
var routePath = section + "/" + section + "/edit/" + id.toString();
$location.path(routePath).search("");
});
}
$scope.add = function (item) {
var currIds = _.map($scope.renderModel, function (i) {
return i.id;
});
if (currIds.indexOf(item.id) < 0) {
item.icon = iconHelper.convertFromLegacyIcon(item.icon);
$scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon, path: item.path, contentTypeAlias: item.metaData.ContentTypeAlias });
}
};
$scope.clear = function () {
$scope.renderModel = [];
};
var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
var currIds = _.map($scope.renderModel, function (i) {
return i.id;
});
$scope.model.value = trim(currIds.join(), ",");
});
//when the scope is destroyed we need to unsubscribe
$scope.$on('$destroy', function () {
unsubscribe();
});
//load current data
var modelIds = $scope.model.value ? $scope.model.value.split(',') : [];
entityResource.getByIds(modelIds, entityType).then(function (data) {
//Ensure we populate the render model in the same order that the ids were stored!
_.each(modelIds, function (id, i) {
var entity = _.find(data, function (d) {
return d.id == id;
});
if (entity) {
entity.icon = iconHelper.convertFromLegacyIcon(entity.icon);
$scope.renderModel.push({ name: entity.name, id: entity.id, icon: entity.icon, path: entity.path, contentTypeAlias: entity.metaData.ContentTypeAlias });
}
});
//everything is loaded, start the watch on the model
startWatch();
});
}
angular.module('umbraco').controller("Umbraco.PropertyEditors.CustomContentPickerController", customContentPickerController);

Error in ODataMetadata.js SAP Hana Cloud

In the app that I am creating I have a home view when the createCaseButton is clicked the createCase view is opened, but when I click to open this view I am getting the following error please see attached image. I can't seem to figure out why it is doing this. Any help would be amazing
Errors in console
Here is my code:
CreateCase controller:
jQuery.sap.require("sap.ui.commons.MessageBox");
sap.ui.controller("casemanagement.CreateCase", {
onInit: function() {
var sOrigin = window.location.protocol + "//" + window.location.hostname
+ (window.location.port ? ":" + window.location.port : "");
var caseTrackerOdataServiceUrl = sOrigin + "/CaseTracker/#/case.svc";
var odataModel = new sap.ui.model.odata.ODataModel(caseTrackerOdataServiceUrl);
odataModel.setCountSupported(false);
this.getView().setModel(odataModel);
},
AddCase : function(aCaseType,aReason,aDateOpened,aEmployee,aDescription,aComment,aAttachment){
if(aCaseType != "" && aReason != "" && aDateOpened != "" && aEmployee != "" && aDescription != "" && aComment != "")
{
var Cases = {};
Cases.caseTypeId = aCaseType;
Cases.reasonId = aReason;
Cases.dateOpened = aDateOpened;
Cases.employeeId = aEmployee;
Cases.description = aDescription;
Cases.comments = aComment;
Cases.stageId = "open"
this.getView().getModel().create("/CreateCase", Cases, null, this.successMsg, this.errorMsg);
Cases = {};
this.router = sap.ui.core.UIComponent.getRouterFor(this);
this.router.navTo("CreateCasePage");
}
else
{
sap.ui.commons.MessageBox.alert("Please fill in all fields before submitting")
}
},
successMsg : function() {
sap.ui.commons.MessageBox.alert("Case has been successfully created");
},
errorMsg : function() {
sap.ui.commons.MessageBox.alert("Error occured when creating the case");
},
CreateCase View:
sap.ui.jsview("casemanagement.CreateCase", {
/** Specifies the Controller belonging to this View.
* In the case that it is not implemented, or that "null" is returned, this View does not have a Controller.
* #memberOf casemanagement.CreateCase.CreateCase
*/
getControllerName : function() {
return "casemanagement.CreateCase";
},
/** Is initially called once after the Controller has been instantiated. It is the place where the UI is constructed.
* Since the Controller is given to this method, its event handlers can be attached right away.
* #memberOf casemanagement.CreateCase.CreateCase
*/
createContent : function(oController) {
var aPanel = new sap.ui.commons.Panel({width:"810px"});
aPanel.setTitle(new sap.ui.core.Title({text:"Create New Case"}));
var aMatrix = new sap.ui.commons.layout.MatrixLayout({layoutFixed:true, width:"610px",columns:8});
aMatrix.setWidths("110px","100px","100px","300px");
var bMatrix = new sap.ui.commons.layout.MatrixLayout({layoutFixed:true,width:"610px", columns:2});
aMatrix.setWidths("110px","500px");
//first row of fields
var caseTypeLabel = new sap.ui.commons.Label({text:"Case Type"});
var caseTypeInput = new sap.ui.commons.TextField({width:"100%", id:"caseTypeId", value:''});
caseTypeLabel.setLabelFor(caseTypeInput);
aMatrix.createRow(caseTypeLabel,caseTypeInput);
//second row
var reasonLabel = new sap.ui.commons.Label({text:"Reason"});
var reasonInput = new sap.ui.commons.TextField({width:"100%", id:"reasonId", value:''});
reasonLabel.setLabelFor(reasonInput);
aMatrix.createRow(reasonLabel,reasonInput);
//third row
var dateOpenedLabel = new sap.ui.commons.Label({text:"Date Opened"});
var dateOpenedInput = new sap.ui.commons.TextField({widht:"100%", id: "dateOpenedId", value:''});
dateOpenedLabel.setLabelFor(dateOpenedInput);
aMatrix.createRow(dateOpenedLabel,dateOpenedInput)
//fourth row
var employeeLabel = new sap.ui.commons.Label({text:"Employee"});
var employeeIDInput = new sap.ui.commons.TextField({width:"100%", id:"employeeId",value:''});
var employeeNameInput = new sap.ui.commons.TextField({width:"100%"});
aMatrix.createRow(employeeLabel,employeeIDInput,employeeNameInput);
//fifth row
var descriptionLabel = new sap.ui.commons.Label({text:"Description"});
var descriptionInput = new sap.ui.commons.TextField({width:"100%",id:"descriptionId",value:''});
descriptionLabel.setLabelFor(descriptionInput);
aMatrix.createRow(descriptionLabel,descriptionInput)
aPanel.addContent(aMatrix);
var bPanel = new sap.ui.commons.Panel({width:"810px"});
bPanel.setTitle(new sap.ui.core.Title({text:"Actions"}));
bPanel.setCollapsed(false);
var cMatrix = new sap.ui.commons.layout.MatrixLayout({layoutFixed:true,width:"810px",columns:2});
cMatrix.setWidths("110px","700px");
var attachmentLabel = new sap.ui.commons.Label({text:"Attachments"});
var addAttachmentButton = new sap.ui.commons.FileUploader({
uploadUrl : "../../../../../upload",
name: "attachmentUploader",
uploadOnChange: true
});
var commentsLabel = new sap.ui.commons.Label({text:"Comments"});
var commentsInput = new sap.ui.commons.TextField({width:"95%",id:"commentsId",value:''});
var submitButton = new sap.ui.commons.Button({id:"createCaseButtonId",text:"Submit", press : function(){oController.AddCase(
sap.ui.getCore().getControl("caseTypeId").getValue(),
sap.ui.getCore().getControl("reasonId").getValue(),
sap.ui.getCore().getControl("dateOpenedId").getValue(),
sap.ui.getCore().getControl("employeeId").getValue(),
sap.ui.getCore().getControl("descriptionId").getValue(),
sap.ui.getCore().getControl("commentsId").getValue()
)}
});
cMatrix.createRow(attachmentLabel,addAttachmentButton);
cMatrix.createRow(commentsLabel,commentsInput);
cMatrix.createRow(submitButton);
bPanel.addContent(cMatrix);
var container = new sap.ui.layout.VerticalLayout({
content: [aPanel,bPanel]
});
return container;
},
});
Home controller:
sap.ui.controller("casemanagement.Home", {
/**
* Called when a controller is instantiated and its View controls (if
* available) are already created. Can be used to modify the View before it
* is displayed, to bind event handlers and do other one-time
* initialization.
*
* #memberOf casemanagement.Home.Home
*/
// onInit : function() {
//
// },
loadCreateCase : function() {
this.router = sap.ui.core.UIComponent.getRouterFor(this);
this.router.navTo("CreateCasePage");
},
loadSearchCase : function() {
this.router = sap.ui.core.UIComponent.getRouterFor(this);
this.router.navTo("SearchCasePage");
}
Home view:
sap.ui.jsview("casemanagement.Home", {
/** Specifies the Controller belonging to this View.
* In the case that it is not implemented, or that "null" is returned, this View does not have a Controller.
* #memberOf casemanagement.Home.Home
*/
getControllerName : function() {
return "casemanagement.Home";
},
/** Is initially called once after the Controller has been instantiated. It is the place where the UI is constructed.
* Since the Controller is given to this method, its event handlers can be attached right away.
* #memberOf casemanagement.Home.Home
*/
createContent : function(oController) {
var oPanelButtons = new sap.ui.commons.Panel({width:"600px",position:"center"});
oPanelButtons.setTitle(new sap.ui.core.Title({text:"Case Management"}));
var oMatrix = new sap.ui.commons.layout.MatrixLayout({layoutFixed: true, width:"400px",columns:4, position:"center"});
oMatrix.setWidths("100px","100px","100px","100px");
var createCaseButton = new sap.ui.commons.Button({id:'createId',text:"Create Case",press : function(){oController.loadCreateCase()}});
var searchButton = new sap.ui.commons.Button({text:"Search Cases",press : function(){oController.loadSearchCase()}});
var analyticsButton = new sap.ui.commons.Button({id:'analyticsId',text:"Case Analytics"});
var helpButton = new sap.ui.commons.Button({id:'helpId',text:"Help"});
oMatrix.createRow(createCaseButton,searchButton,analyticsButton,helpButton);
oPanelButtons.addContent(oMatrix);
var oPanelTable = new sap.ui.commons.Panel({width:"600px",position:"center"});
var oTable = new sap.m.Table({
inset: true,
headerText: "Open Cases Pending Your Action",
headerDesign : sap.m.ListHeaderDesign.Standard,
mode: sap.m.ListMode.None,
includeItemInSelection: true
});
oTable.addColumn(new sap.m.Column({
inset : true,
header: new sap.ui.commons.Label({text: "Case#"}),
}));
oTable.addColumn(new sap.m.Column({
inset : true,
header: new sap.ui.commons.Label({text: "Submitted By"}),
}));
oTable.addColumn(new sap.m.Column({
inset : true,
header: new sap.ui.commons.Label({text: "Created On"}),
}));
oTable.addColumn(new sap.m.Column({
inset : true,
header: new sap.ui.commons.Label({text: "Case Type"}),
}));
oPanelTable.addContent(oTable);
var container = new sap.ui.layout.VerticalLayout({
id: "container",
content:[oPanelButtons,oPanelTable]
});
return container;
},
});

knockout dynamic binding issue

Consider the following ViewModel that is generated through the knockout mapping plugin.
var originalData = {
"QuoteSelectedViewModel": {
"ProductName": "Select",
"CoverQuotesViewModel": [
{
"Code": 1,
"Label": "Première Assistance 24h/24 (GRATUITE)",
"IsMandatory": true,
"IsSelected": true,
"DependsOn": []
},
{
"Code": 2,
"Label": "Assistance PLUS 24h/24",
"IsMandatory": false,
"IsSelected": false,
"DependsOn": []
},
{
"Code": 8,
"Label": "Heurts Animaux / Force de la Nature",
"IsMandatory": false,
"IsSelected": false,
"DependsOn": [
2
]
},
]}
}
var viewModel = ko.mapping.fromJS(originalData);
ko.applyBindings(viewModel);
<div data-bind="with: QuoteSelectedViewModel">
selected quote is : <span data-bind="text: ProductName"></span>
<!-- ko foreach: CoverQuotesViewModel -->
<br/>
<div data-bind: if: IsVisible>
<input type="checkbox" data-bind="checked: IsSelected"></input>
<input type="text" data-bind="value: Label, enable: IsSelected"></input>
</div>
<!-- /ko -->
</div>
Now, I would like to hide the div when IsVisible returns false. IsVisible does not exist yet, and it should be a computed observable function on each element of the CoverQuotesViewModel array.
How do I generate this computed observable function on each element ?
Thanks
[EDIT]
I've added a jsfiddle here : http://jsfiddle.net/graphicsxp/fpKWM/
[EDIT2]
Actually knockout document is clear about how to do that:
Of course, inside the create callback you can do another call to
ko.mapping.fromJS if you wish. A typical use-case might be if you want
to augment the original JavaScript object with some additional
computed observables:
var myChildModel = function(data) {
ko.mapping.fromJS(data, {}, this);
this.nameLength = ko.computed(function() {
return this.name().length;
}, this); }
[EDIT]
Here's the full code following Paul's suggestion:
(getQuotesSuccess is an AJAX success handler)
viewModel.getQuotesSuccess = function (result) {
var myCoverQuotesViewModel = function (data, parent) {
var self = this;
ko.mapping.fromJS(data, {}, this);
self.IsVisible = ko.computed(function () {
var visible = true;
if (self.DependsOn().length > 0) {
$.each(self.DependsOn(), function (index, value) {
var dependency = viewModel.QuoteSelectedViewModel().CoverQuotesViewModel.filterByProperty("Code", value);
if (dependency().length > 0) {
visible = visible & dependency()[0].IsSelected();
} else {
visible = false;
}
});
}
return visible;
}, this);
}
var mapping = {
'CoverQuotesViewModel': {
create: function (options) {
return new myCoverQuotesViewModel(options.data, options.parent);
}
}
}
ko.mapping.fromJS(result, mapping, viewModel);
};
Ok, reverting back to my earlier answer, with your modifications, so anyone else looking at this answer actually gets the correct version!
You need to create a child viwe model, and use the mapping plugin to populate it automatically, then add in your computed observable:
function CoverQuotesViewModel(data)
{
var self = this;
ko.mapping.fromJS(data, {}, self);
// Copy the data to each property.
self.IsVisible = ko.computed(function()
{
// your logic for each quote
});
}
Then you need to use a create map for the mapping of the main view model, and in this you create your child view model:
var mapping = {
'CoverQuotesViewModel': {
create: function(options) {
var model = new CoverQuotesViewModel(options.data);
return model;
}
}
}
var viewModel = ko.mapping.fromJS(data, mapping);
You don't need to pass this into the computed, as you are referencing self, which is your stored version of this.

Setup and Teardown (and self invalidating) EmberJS Views

I would like to have the ability to setup and teardown functions for views inside EmberJS, for this example I will say displaying logs that are fetched via AJAX every 5 seconds, buts its a problem I encounter quite a lot
I have created a switchView method here which will handle the setup / teardown events, however right now it cant invalidate itself to show the updated information.
Em.Application.create({
wrapper: $('#content'),
currentView: null,
ready: function() {
this._super();
this.self = this;
Em.routes.add('/log/', this, 'showLogs');
Em.routes.add('*', this, 'show404');
},
switchView: function(name, view) {
if (this.currentView) {
$(document.body).removeClass('page-' + this.currentView.name);
if (this.currentView.view.unload) {
this.currentView.view.unload();
}
this.currentView.view.remove();
}
if (name) {
$(document.body).addClass('page-' + name);
}
if (view.load) {
view.load();
}
view.appendTo(this.wrapper);
this.currentView = {name: name, view: view};
}
});
var LogView = Ember.View.create({
templateName: 'logs',
logs: [],
load: function() {
var self = this;
this.interval = setInterval(function() {
self.fetchLogs.apply(self);
}, 5000);
this.fetchLogs();
},
unload: function() {
clearInterval(this.interval);
},
fetchLogs: function() {
var self = this;
$.get('/logs', function(data) {
self.logs = data.list;
});
}
});
I'm not 100% clear on what you're asking for, but you should look into willInsertElement, didInsertElement and willDestroyElement. These are all called relative to the view element's insertion and removal from the DOM.
You can do this at the end of the RunLoop:
Ember.run.schedule('timers', function () {
//update your widget
});
See http://blog.sproutcore.com/the-run-loop-part-1/. The same concept is present in EmberJS

Resources