How to retrieve view's ID with application prefix? - sapui5

To retrieve prefixed ID of a control we can use view's method CreateID. However, there's no such a method in class sap.ui.core.Core. So how do I know viewID in the line
sap.ui.getCore().byId(viewID)

The Core is common to all the applications you are loading into your HTML document. An specific view of an specific application can have a different ID depending on how many different views you already loaded. The CreateID method makes sense to create an ID prefixed by the relevant View ID, but the view IDs are not prefixed but just autogenerated.
So, to get the ID of the current view, just do this.getView() in its controller and get the ID from the returned object.
If you want to know the ID of a different view to the one you are visualizing/loading, then you need to navigate through all the views in your DOM and design your own way to identify it.
However I don't recommend you to modify a View from a controller of another view. Each View should be modified by the logic in its controller only, and no by other controllers, to maintain the principles of the MVC architecture
EDIT:
How to access your component:
If you bind the context of your app execution with the Push.js script, then use this.getOwnerComponent().
If you don't, then you need to access your Component from the core with sap.ui.getCore().getComponent('componentID')
Now the problem is how to know the componentID. Well, I see two options here, give an ID to your component, or map the autogenerated component IDs in a Core variable.
How to retrieve a specific Component:
Option 1: Define a Component ID
Whenever you initialize it, usually in the index.html file, you can do it like this:
<script>
sap.ui.getCore().attachInit(function() {
sap.ui.component({
id: "myComponentID",
async: true,
name: "myNamespace", // The namespace you defined in the bootstrap tag and points to the folder where the Component.js file is stored
manifestFirst: true
}).then(function(oNewComponent){
new sap.m.Shell({
app: new sap.ui.core.ComponentContainer({
component: oNewComponent,
height : "100%"
})
}).placeAt("content");
})
});
</script>
With this option you can retrieve your Component as sap.ui.getCore().getComponent('myComponentID') and of course whatever model registered in it with sap.ui.getCore().getComponent('myComponentID').getModel('modelName')
Option 2: Map the autogenerated components IDs with your identifiers in a Core variable
I don't like to change the Components ID, and there are some times when you have no chance to do it. So in the past I did it like in the following snippet (probably not the best/elegant solution out there, but it works good)
Component.js
/**
* The component is initialized by UI5 automatically during the startup of the app and calls the init method once.
* #public
* #override
*/
init: function() {
//Map Component ID in a Core variable
if(!sap.ui.getCore()._data_oLoadedComponents){
sap.ui.getCore()._data_oLoadedComponents = {};
}
sap.ui.getCore()._data_oLoadedComponents.myCustomComponentId = this.getId(); // myCustomComponentId is the ID you want to use for this specific app
// END: Map Component ID in a Core Variable
// call the base component's init function
UIComponent.prototype.init.apply(this, arguments);
// set the device model
this.setModel(models.createDeviceModel(), "device");
}
So now you can access any of your components getting the autogenerated ID from your mapping variable like this:
sap.ui.getCore().getComponent(sap.ui.getCore()._data_oLoadedComponents.myCustomComponentId)
and also access the model like this:
sap.ui.getCore().getComponent(sap.ui.getCore()._data_oLoadedComponents.myCustomComponentId).getModel('modelName')

Related

Best practice to lazy load data on tab click

Within the onBeforeRendering() function of a view how should I determine if a specific node is present in the model and modify the model to include the node if not? This question is about lazy loading of data to the model for data that will be infrequently accessed by users but would have a performance penalty if loaded with initial model data.
My use case has a list page leading to a detail page for whatever item in the list the use clicks. On the detail page is a set of tabs which expose sub-details related to the selected detail. Specifically a long text description of a the brief for a task.
The tab control leads via manifest.json style routing to display a view in the tabs content area.
This is my current working code which is within the onBeforeRendering event of the view controller:
onBeforeRendering: function(oEvent){
var sPath = this.getView().getBindingContext("Projects").getPath(); // something like /task/6
console.log('Path='+sPath)
var oModel = this.getView().getModel("Projects");
var oTask = oModel.getProperty(sPath + "/brief");
if (oTask) { // looks like /brief exists so must already have loaded the brief
// nothing to do
console.log('Use existing data')
}
else { // /brief not yet present so we need to load it up
console.log('Load new data')
oModel.setProperty(sPath + "/brief", "This is the brief...") // replace with loaddata() from server, use attachRequestCompleted to call function to modify model.
}}
Question - is this the correct approach?
Edit: Based on discussion in this question I modified my code to use an event that fires per display of the view. onBeforeRendering turned out to run without much apparent predictability - which I am sure it has but in any case I wanted a one-per-display event. Also, I fleshed out the code further but retained the basic structure and it appears to do what I wanted.
This is a valid approach. But you should think aboute following use case: What happens if the data you loaded have been changed at the backend? The JSONModel does not give you any support here as it acts dumb data store only.

Is it possible to have XML Fragments IDs prefixed with its view's ID?

When a XML view is declared, all of its controls IDs are prefixed by the ID of the view itself.
In order to get any control inside the controller it's necessary to use:
this.byId()
... where this points to the controller by default.
I already know that there is
sap.ui.getCore().byId()
as well which can be used to retrieve a control defined in a JS View or created without a view prefix.
I declared a XML fragment with a dialog and a Text control which will contain a text defined by my controller. I noticed that the ID I defined inside the fragment is not prefixed with the view's ID.
My question is: Is it possible to have XML Fragments IDs prefixed with its view's ID (then I could use this.byId instead of sap.ui.getCore) ?
I checked and this appears to be happening only when you are adding the fragment from a controller. If the fragment is defined in the static time in the xml view the ID's of the content derive their name from the view.
The way to get over this is to ensure your fragment ID is derived from your view.
The code would be something like this in your controller.
oPage.addContent(new sap.ui.xmlfragment(this.createId("idFragment"), "fragmentcreation.SampleFragment"));
IdFragment = ID for your fragment
fragmentcreation.SampleFragment = Name of your fragment(fragmentcreation is the folder)

SAPUI5 - Remove oData Model bound to view

I am new to SAPUI5 and I am very much in need of your help.
I have created an XML view and bound an OData model to it like below. Now all the controls in the view got bound and I am able to see the data.
this.getView().setModel(oModel);
Now I have a requirement in which I have to completely remove this binding from the view. I destroyed the oModel and removed all its bindings. And now when I refresh the view, the data still exists in the screen. I want all the data from the view to disappear.
oModel.destroy();
oModel.refresh();
this.getView().getModel().refresh(true);
I assume that you're using an sap.ui.model.odata.ODataModel to retrieve odata, then done binding using, for example, a JSON model, like so,
var oJson = new sap.ui.model.json.JSONModel({data : oData);
/* Here 'data' is any name that you provide to identify your data model,
'oData' is the data that is received as response from the sap.ui.model.odata.ODataModel */
And set this model to the view like you have mentioned,
this.getView().setModel();
Now, to remove this data from the view you can try the following,
oJson.setData({data : null}, true);
This would set the previously set oData to null and therefore all data binding from the controls in the view would be removed(changed to null).
I believe oJson.refresh() is optional, but you can try adding that as well if the change is not reflected.
Its an approach until may be you find something better :) Here is the documentation for the APIs that I've used.
If you are using
var dataModel = new sap.ui.model.json.JSONModel();
dataModel.setData(oDataResponse); //Data returned from oData.Read()
this.getView().setModel(dataModel, "dataModel");
Retrieve the model, Set the model to null, Update the binding
var dataModel = this.getView().getModel("dataModel");
if(dataModel){
dataModel.setData(null);
dataModel.updateBindings(true);
}

Durandal - dispose of viewmodel after completion of registration process

Just wondering if anyone knows a good/simple approach using Durandal to disposing of or re-initializing a viewmodel once it becomes invalid?
I have a registration form that I could 're-initialize' manually after a user has completed the form and registered successfully, but I'd prefer to just dispose of it so that Durandal creates a new registraion view/view model when that particular route is accessed again.
If your viewmodel module returns a function rather than an object, it will create a new one each time rather than reusing the 'singleton' object. See the Module Values section of Creating a Module.
Updated link for the Durandal Module constructor function information: Module Values
You can split the difference:
var cache;
var ctor = function () {
if (cache) return cache;
// init logic
cache = this;
}
Just replace the if(cache) check with whatever "do I need a new thing or not" logic you like.
If you're using routing, simply redirect the user to an instance-based module (one that returns a constructor function). The user will most likely click or touch a button that signifies that he is done with the registration form. That would be the redirect action.
If you're using composition, you would still create an instance-based module. Then, you would use dynamic composition to swap it in once the user signified he was done with the registration form.
Dynamic composition is where the view and/or model attributes on a Durandal composition are, themselves, observables, referencing something like the following in the viewModel:
this.currentView = ko.observable('');
this.currentModel = ko.observable('');
Then, in your HTML:
<div>
<div data-bind="compose: {view: currentView(), model: currentModel())"></div>
</div>
When the user clicks "Done", or something to that effect, functions on your viewModel might look something like:
ctor.prototype.done = function () {
this.setCurrentView('viewmodels/registrationForm.html');
this.setCurrentModel('viewmodels/registrationForm.js');
}
ctor.prototype.setCurrentView = function (view) {
this.currentView(view);
}
ctor.prototype.setCurrentModel = function (model) {
this.currentModel(model);
}
Either one of the approaches above will create the registrationForm only when it's needed.
With Durandal 2.0, you can use the deactivate callback within the composition lifecycle. Here is some documentation http://durandaljs.com/documentation/Hooking-Lifecycle-Callbacks

Router, model, or view, which one is the initiator

I am trying to be as lazy loading as possible,
But, I am puzzle on how to start, here is my "sequenced comprehension":
Objective: Create a contacts page with contacts existing on the server
Step1.0: To use the router: the <div id="contacts"> must exists to trigger a rule, so I stepped back to (Step0.9),
Step0.9: Created this div in the body. Fine, the router find the #contacts, Oh, but this is a view, ok, stepped back to (Step0.8).
Step0.8: Erase the div created in the body and replace it by a view instead:
contactsView = Backbone.View.extend
tagName: 'div',
id: 'contacts'
To be lazy loading, this view should only be created when the #contact is trigger in my router table, but I just removed it from by body, it does exist anymore, I am back to Step1.0 ???
Some tutorials found, shows global variable settings... Please, how the general scenario using a router, a view, their models, and collection should proceed (no code is necessary for an answer, just one line for each steps) ?
I know there can be multiples ways, but what is the most common backbone step strategy to create elements.
Thanks in advance.
I'm not 100% sure I understood you correctly. If I didn't please let me know in the comments.
There seems to be some confusion in your question regarding the usage of Backbone.Router in general. When the router maps a route to URL fragment #contacts, that has nothing to do with a DOM element with the id #contacts. The hash sign simply happens to be the identifier for an URL fragment and id CSS selector, but that's where the similarity ends.
Typically my router looks something like this:
var AppRouter = Backbone.Router.extend({
routes: {
contacts: "contactList"
},
contactList: function() {
var contacts = new ContactCollection();
var view = new ContactListView({collection:contacts});
view.render().$el.appendTo("#contacts");
}
});
Notice that the #contacts element doesn't need to be called that. You can call it #pony, or you can render the view directly to the document body if you want.
So in these terms the workflow is:
Router gets hit
Collection is initialized
View is rendered
Usual way i do is
Have the div#contacts loaded within body
Router maps the #contacts to the method showContacts in the router
showContacts creates the view, attaches it to the desired div
var view = new contactsView();
$('#contacts').empty().append(view.el);
view.render();
You need not define the id in the definition of contactsView