How to Create a View Multiple times with Distinct Controller Instances? - javascript

I'm working on a dashboard with multiple tiles. Every tile is an XML view. At first, every tile view was supposed to be created once, but due to some new requirements, the user should be able to add the same tile multiple times with different configurations.
To achieve this, I simply tried to create a view like this:
sap.ui.xmlview({
viewName: "com.sap.tiles." + selectedTile + "." + selectedTile
});
The tile shows up correctly, but it seems like it's using the exact same controller as the already existing tile of the same type. Every variable is already set in the controller. Is it possible to instantiate a new controller?
I've read that using the same view multiple times in one window (with different controller instances) is not possible and components or fragments should be used instead. Is that true, or am I doing something wrong?
Final edit:
I've solved my problem. It was a problem very specific to my code, which lead to a wrong function call. Boghyon Hoffmanns answer helped me find the solution.

The tile shows up correctly, but it seems like it's using the exact same controller as the already existing tile of the same type. Every variable is already set in the controller.
You probably declared the variables in the closure outside of the module definition.
sap.ui.define([
"sap/ui/core/mvc/Controller",
// ...
], function(Controller /*, ...*/){
"use strict";
var something; // Visible to all Controller instances of the same name
return Controller.extend("com.sap.tiles...", {
onInit: function() {
something = 1; // Applies to all Controller instances of the same name
this.something = 0; // Applies to this instance only
},
});
});
Creating the same view multiple times does not fetch the corresponding Controller multiple times (since the module has been already defined), but it does instantiate the module (Controller) every time when the view is created.

Related

How to create an instance of the Model Class in MVC Pattern - SapUI5

I am currently trying to implement the Model-View-Controller in my SAPUI5 / Fiori Project.
I managed to create an Instance of the Controller with: new sap.ui.core.mvc.Controller('controller.js')
This does not work for the Model (sap.ui.core.mvc does not contain a Model attribute).
Now I am searching a way to call functions of the Model from the Controller, to get my data.
I already tried this code: oObjModel = new sap.ui.mode.Model(), using this I cannot call functions from my Model.
I recommend you look at the walkthrough on the SAPUI5 documentation site. It shows how to initialize all the aspects of MVC in the correct way.
Models in SAPUI5 come in different classes to support different forms of data. For example, there is JSONModel, XMLModel, ODataModel, etc.
So to create a model, you need to first determine the specific type of model you need and use its specific constructor. For example, if you have JSON data (or simply a JavaScript object), you use the JSONModel:
var yourData = { "hello": "world" };
var oModel = new JSONModel(yourData);
Note that the above code assumes you are following the recommended way to use modules and that this code is wrapped with a sap.ui.define or sap.ui.require, where the module sap/ui/model/json/JSONModel is assigned to the variable JSONModel. The walkthrough shows this correct usage pattern. Accessing the constructor directly like the below is not recommended:
// Also probably works, but not the recommended way
var oModel = new sap.ui.model.json.JSONModel(yourData);
Your way of creating a controller is also not correct. You should preferably let the view instantiate the controller for you by providing it a controllerName, as shown in the walkthrough for Controllers.
<mvc:View xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc" controllerName="name.of.your.controller">
<!-- ... -->
</mvc:View>
If you need to manually instantiate a controller from code, use this:
Controller.create({ name: "name.of.your.controller" }).then(function(oController) {
// Do something with oController
});
This again assumes you have the module sap/ui/core/mvc/Controller linked to the variable Controller.
Before version 1.56, you can use the now-deprecated sap.ui.controller function to create controllers instead:
sap.ui.controller("name.of.your.controller", null, /*async=*/true).then(function(oController) {
// Do something with oController
});
Be aware that both of these examples load the controller asynchonously, as synchronous XHR is being globally deprecated outside of Workers, and thus the framework recommends you to use async only. In fact, the new way of loading does not even provide an option for sync loading.

Angular nested-scopes and nested-views retaining model data

I currently have an application with a rather complex wizard to create a data record. The wizard consists of 3 steps, each associated with a nested view and a controller. Only the data record itself is shared among all three scopes and each controller contributes additional data to the main record.
But they also have scope specific data, that will be used to render additional fields which are only relevant to that nested scope.
I want to be able to go back and forth between the wizard steps but currently it looks like the nested scopes get discarded as soon as I move on to another nested view. I looked up the scope lifecycle in the developer guide: https://docs.angularjs.org/guide/scope#scope-life-cycle
But I does not really tell me how the scope lifecycle applies to nested scopes and how I can prevent these scopes from being discarded. Of course I could move all the data of the nested scopes into the parent scope, but to me that would just feel like a workaround, because actually that data is only relevant to the individual scopes.
I'll try to give a short example:
angular.module('app').controller('ParentCtrl', function ($scope) {
...
$scope.dataRecord = {};
}
angular.module('app').controller('Child1Ctrl', function ($scope) {
...
$scope.dataRecord.test = 'a';
$scope.childScope1SpecificData = '123';
}
angular.module('app').controller('Child2Ctrl', function ($scope) {
...
$scope.dataRecord.test2 = 'b';
$scope.childScope2SpecificData = '456';
}
When I now switch back and forth between the two childscopes, the dataRecord will be adjusted properly, but changes to childScope1SpecificData (via an input field from the template) will be discarded as soon as I switch to Child2Ctrl and back.
Is there a way to persist this data which switching the scope or is it meant to be discarded and I am simply using it wrong?
Thanks
EDIT:
Ok I looked into the factory approach. Maybe to make it more plastic: The additional data, that belongs to each child scope, is a fileuploader with its associated upload queue. Only in a later validation step these pictures actually become part of the datarecord, but until then I don't want the uploaded images to get lost upon switching views.
So what I could do is to externalize the whole fileupload logic into a factory that returns fileuploaders associated to IDs. Whenever a child scope requests the same id the factory will return the same fileuploader. Different Ids will return different uploaders or new ones. That would pretty much solve the problem but would also mean that the data never gets discarded at all unless I really close the browser, because the factory now is absolutely independent of any scope. Since I only want to retain the data in the context of that wizard, I want the data to be discarded, as soon as I leave the wizard.
So after having looked into these other approaches, it seems like I have to go with the original idea: I have to attach the uploaders to the parent scope. So they will continue to exist when switching to other child views, but they will also be discarded as soon as I leave the wizard.
I hope that was correctly summarized
If you are using 'controller as' syntax, you can use this variant.
angular.module('app').controller('ParentCtrl', function ($scope) {
...
$scope.dataRecord = {};
}
angular.module('app').controller('Child1Ctrl', function ($scope) {
...
$scope.ParentCtrl.dataRecord.test = 'a';
$scope.ParentCtrl.childScope1SpecificData = '123';
}
angular.module('app').controller('Child2Ctrl', function ($scope) {
...
$scope.ParentCtrl.dataRecord.test2 = 'b';
$scope.ParentCtrl.childScope2SpecificData = '456';
}
So, you are changing ParentCtrl object in you parent scope, not for every instance.
Sorry, if it was no understandable

Using arbitrary/different Backbone models in a view

Can one use differently defined models defined via require.js/AMD in a single central view? I mean separately defined models, not ones of a collection.
If yes, how they are referenced in the json part of the callback function of define() that defines that central view, in its vars, in functions of its attributes etc. There can only one this.model, right?
Is it possible to Render different templates, possibly populated by vars from those different models, conditionally, from within this single central view?
To extend my question:
Can one use differently defined collections defined via require.js/AMD in a single central view?
Can one use differently defined models defined via require.js/AMD in a single collection? (this alone could achieve first goal with referencing only this capable collection.)
Can one use differently defined models defined via require.js/AMD in a
single central view? I mean separately defined models, not ones of a
collection.
If yes, how they are referenced in the json part of the callback
function of define() that defines that central view, in its vars, in
functions of its attributes etc. There can only one this.model, right?
A View's this.model is just one way to provide a view with a model. You can also pass whatever other options you want to a view, like so:
var YourView = Backbone.View.exend({
initialize: function(options) {
this.foo = options.foo;
}
});
var modelA = new Backbone.Model();
var modelB = new Backbone.Model();
var yourView = new YourView({model: modelA, foo: modelB});
// yourView.model == modelA
// yourView.foo == modelB
Is it possible to Render different templates, possibly populated by
vars from those different models, conditionally, from within this
single central view?
Yup. For instance, here's an example if we add a render method to YourView:
render: function() {
if (this.model.get('bar')) {
this.$el.html(this.templateA(this.model.toJSON());
} else {
this.$el.html(this.templateB(this.foo.toJSON());
}
}
Can one use differently defined collections defined via require.js/AMD
in a single central view?
Yup, the same way as you pass in multiple models. It doesn't matter if you use Require; you can pass around Backbone objects/classes through it just fine.
Can one use differently defined models defined via require.js/AMD in a
single collection? (this alone could achieve first goal with
referencing only this capable collection.)
Yup, just throw all the models in to a single collection, like so:
var modelA = new ModelClass();
var modelB = new SomeOtherModelClass();
var collection = new Backbone.Collection([modelA, modelB]);
The only limit is that a Collection can only have a single model property, which means that whenever you create a new Model through the Collection (eg. via fetch or create) they will all be of that model.
(It's possible to get around even that with if you replace the model with a custom function, but you shouldn't need to do that.)

GeoJSON layer does not get recreated when rendered using EmberJS's 'link-to' helper

I am trying to render a LeafletJS map where the colours of the states in the map are dependent on a global parameter that is set in the appropriate Ember route. The setting of the parameter is not the issue but rather the (re)creation of the geoJson layer. When hitting the URL for the first time or when reloading the page the correct map is created, however when the page is rendered using Ember's 'link-to' helper, the map still holds the state colours of the previous page.
drawAll: function() {
var that = this;
Ember.$.ajax('/data/sa_provinces.json').then( function(data){
Frontend.globalPaths = data;
that.get('store').findAll('province').then(function(provinces) {
provinces.forEach(function(province) {
var provinceGeoJSON = window.L.geoJson( province.get('dataFromJSON'),
{ style: province.get('geoJSONStyle'),
province: province,
onEachFeature: province.get('onEachFeature') });
province.set('geo_json', provinceGeoJSON);
provinceGeoJSON.addTo(Frontend.map);
window.province = province;
});
});
});
}.property('drawAll')
This drawAll function is located within a Ember controller and is called from an Ember template. The functions dataFromJSON, geoJSONStyle and onEachFeature are all called the first time a page is called or when the page is refreshed but not when the page is rendered using the Ember's 'link-to' helper. Neither are they called when the URL is entered manually.
If anyone has any ideas or experience with LeafletJS and/or Ember I would really appreciate your help. Thanks in advance, Greg.
The first issue I notice is that drawAll is a computed property, not a function - you seem to be confusing computed properties and functions.
http://emberjs.com/guides/object-model/computed-properties/
Ember computed properties are more like normal attributes that observe other variables, and recompute when those variables change. The property() method after the function declaration changes it into a computed property and specifies which variables the property depends on. On the last line you're specifying that drawAll observes itself, which doesn't make much sense.
You can't call functions from handlebars templates - you can only access properties. So you can access a property, with the side effect of causing that property's function to be called.
If you want just a function that is called as soon as the template loads, you can implement the didInsertElement function on that templates corresponding view, and the contents of the didInsertElement function will run when the template loads.
If you want a property that recomputes based on some conditions changing, you should change the last line to specify which conditions it is observing.
I can't be sure without more info about the template and controller you're using, but for your current use case it looks like you just want a function that runs whenever the template is inserted, so changing the drawAll to an actual function (by removing the .property('drawAll)) and calling it from didInsertElement of the corresponding view will rerun it every time the controller is inserted. Like:
didInsertElement: function() {
this.drawAll()
}
(You need to have created a view that corresponds to the controller in this context)

ExtJs5: Call Controller 2's method from Controller 1

I am trying my hands on the new ExtJs 5.
I have created a small app as per the defined MVC pattern of ExtJs5.
Am using ViewControllers for each View.
Problem Statement: Now suppose I have two VCs (Controller1 & Controller2). Each has its own methods. I wish to call a method of Controller2 from Controller1. I want to update the View associated with the Controller2 from Controller1.
E.g. Suppose there is a separate view for Status Bar and a ViewController(StatusBarController).
This VC has a method to update the view based on whatever message it receives as input parameter.
All the other controllers in the application will call this VCs method to update the status of the application on the status bar.
In the previous versions, this.getController('StatusBarController') was used to get the handle to any controller and then call its method.
But this is not working in my case when I use a ViewController.
Can anyone guide me how to achieve this thing? And also whether it is the correct/ideal way to do such a thing or is there any better option?
Here is my code:
StatusBarView:
Ext.define('MyApp.view.statusbar.StatusBarView', {
extend : 'Ext.panel.Panel',
controller: 'StatusBarController',
region : 'south',
xtype : 'status-bar-panel',
html : 'This is a status bar'
});
StatusBarController:
Ext.define('MyApp.controller.StatusBarController', {
extend : 'Ext.app.ViewController',
alias: 'controller.StatusBarController',
updateStatusBar : function(message) {
this.getStatusBarView().update(message);
}
});
Some Other Controller in app:
Ext.define('MyApp.controller.ResourcesPanelController', {
extend : 'Ext.app.ViewController',
alias : 'controller.ResourcesController',
onItemClick : function(tree, record, item, index, e, eOpts) {
// here I am calling the other controller's method.
this.getController('StatusBarController').updateStatusBar(
record.data.text + ' has been clicked');
}
});
ViewControllers are tightly related to their views, they are even created and destroyed together with views, and they should be controlling only their own views. The idea is to separate logic from UI on the view level.
Calling methods of one ViewController from another is not a good practice and, for big applications, it is route to hell as it inevitably leads to unmaintainable spaghetti code.
The correct approach is minimize the number of ViewModels, ViewControllers and Controllers and let them work in their own areas of responsibilities.
For example: Suppose you want a grid and form in a container. Form would allow editing of the record selected in the grid. Plus some buttons. These three views (container, grid and form) together form a unit. Thus:
only one ViewController at container is needed, all views can use it
only one ViewModel at container is needed, all view can use it
if you want to let this trio to communicate with the outer world of the rest of the application, the container's view controller can fire events and can have API methods to call
Thus, if needed, you can have an MVC (global) Controller(s) that would coordinate functions of units, like our trio.
Also, data binding simplifies the logic to a great degree so controllers and listeners are not needed that much.
See Binding Grid and Form in ExtJS 5 example.
my answer is simple and short:
Ext.app.ViewController.fireEvent()
while one can add any type of custom event with the listeners config of the ViewController - the docs of the listen config state "event domains", so I'd assume, that both controller need to reside within the same domain in order to be able to interact, event-wise.
the 2nd argument of .fireEvent() might need to imitate the element which ordinary triggers the event.
well, it should also be possible to access it like that (in the secondary controller):
this.getApplication().getStatusBarController().updateStatusBar('...');

Categories

Resources