So I have my app controller as always in sapui5:
sap.ui.define(["sap/ui/core/mvc/Controller"], function(Controller) {
"use strict";
return Controller.extend("com.test.controller.App", {
onInit: function() {
if (checkSomething)) {
// here call my first controller
} else {
// here call my second controller
};
}
},
});
});
and I have my second controller which I want to call only if that one if-statement fails
sap.ui.define(["sap/ui/core/mvc/Controller", "sap/m/MessageBox"], function(Controller, MessageBox) {
"use strict";
return Controller.extend("com.test.Controller1", {
onInit: function() {
this.oRessourceBundle = this.getOwnerComponent().getModel("i18n").getResourceBundle();
}
});
});
here my second controller:
sap.ui.define(["sap/ui/core/mvc/Controller", "sap/m/MessageBox" ],function(Controller, MessageBox) {
"use strict";
return Controller.extend("com.test.Controller2", {
onInit: function() {
this.oRessourceBundle = this.getOwnerComponent().getModel("i18n").getResourceBundle();
}
});
});
I have a view for both controller so I don't copy it because it is empty so far anyway so there is also a
App.view.xml
Controller1.view.xml
Controller2.view.xml
how do I tell my appcontroller to call different controllers ?
I have also implemented the routes in the manifest.json file
You can call other controller methods like below
sap.ui.controller("com.test.Controller2").yourMethodName();
since my old answer was deleted with some saying "this is not the answer to the question of the author" (while with all respect I was the author and therefore I guess I know if this solved my issue or not) I will explain what is explained in the link I posted:
this is what is in my index.html file:
<script>
sap.ui.getCore().attachInit(function () {
sap.ui.require([
"sap/m/Shell",
"sap/ui/core/ComponentContainer"
], function (Shell, ComponentContainer) {
new Shell({ // placing everything in shell
app: new ComponentContainer({ // component container which holds component file
name: "sap.ui.iyc", // root location of app
height: "100%"
})
}).placeAt("content");
});
});
Component.js This is a file which contains the configuration for router. After configuration and declaring routes and targets we have to initialize() the router in init() function as shown in below code snippet.
sap.ui.define([
"sap/ui/core/UIComponent"
], function (UIComponent) {
"use strict";
return UIComponent.extend("sap.ui.iyc.Component", {
metadata: {
"rootView": "sap.ui.iyc.routepages.App", // initial view
"routing": {
"config": {
"routerClass": "sap.m.routing.Router", // router class
"viewType": "XML", // types of views using in app
"viewPath": "sap.ui.iyc.routepages", // folder of views
"controlId": "app", // container where pages are placed while navigating
"controlAggregation": "pages", // contents which needs to be replced while navigating
"transition": "slide" // navigation transition effect
},
"routes": [{ // defining routes
"pattern": "", // pattern of the URL
"name": "first", // name of route
"target": "first" // name of target
},
{
"pattern": "second",
"name": "second",
"target": "second"
}],
"targets": { // defining targets
"first": { // route name
"viewName": "First" // target view name, will be navigated to this view
},
"second": {
"viewName": "Second"
}
}
}
},
init: function () {
// call the init function of the parent
UIComponent.prototype.init.apply(this, arguments); // calling parent UIComponents
// create the views based on the url/hash
this.getRouter().initialize(); // initializing router
}
});
});
first controller:
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function (Controller) {
"use strict";
return Controller.extend("sap.ui.iyc.routepages.First", {
// getting router which is declared in component file
getRouter : function () {
return sap.ui.core.UIComponent.getRouterFor(this);
},
// this function will trigger when button is clicked
navToSecond : function (oEvent){
this.getRouter().navTo("second"); // calls route name "second"
}
});
});
second controller:
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function (Controller) {
"use strict";
return Controller.extend("sap.ui.iyc.routepages.Second", {
getRouter : function () {
return sap.ui.core.UIComponent.getRouterFor(this);
},
// this function will trigger when button is clicked
navToFirst: function() {
this.getRouter().navTo("first"); // calls route name "first"
}
});
});
for an even better understanding visit the following link:
http://www.inkyourcode.com/how-to-navigate-between-two-views-using-routing/
and of course in your views you will have to implement buttons or whatever you want to trigger those functions which will route you to your views
Related
I have recently taken on the development of web app that has been written in AngularJS. In one of the files, myApp.js, there is a provider that is defined as follows:
.provider('myAppConf', function($routeProvider){
var constants = {
'HOMEPAGE': '/alarms',
...
};
// Setter function for constants
this.setConstant = function(constant, value){
constants[constant.toUpperCase()] = value;
};
...
// Other setter functions
...
// Non- setter/ getter functions:
this.addElementOverview = function(){
...
var location = 'pages/elementbrowser';
...
return '/' + location;
}
function addCreatePageRoute(){
$routeProvider.when('/pages/create', {
page: {
editable: true,
title: {
...
},
layout: {
...
},
widgets: [
...
]
}
});
}
// More non- setter/ getter functions
this.$get = ['$q', '$timeout', 'myAppUI' function($q, $timeout, myAppUI){
...
}];
}).run(function(...){
...
});
On most of the pages on the site, there is a 'settings' button, which opens a dialog box that the user can use to change the settings for a given page. I have added a checkbox to that dialog box, which, when checked, I want to use to set the page on which it is checked as the 'home' page, overwriting whichever page was previously the 'home' page. If/ when the checkbox is deselected again, the home page should be set back to its original value (i.e. the /alarms page determined in the constants).
As things currently stand, I have managed to change the home page, so that it is updated to the page selected by the user- when the click 'Home' they are taken to the page on which the checkbox was selected. But as soon as they log out, their chosen home page is forgotten, and when they log in again, the default home page is their home page until they select another one.
I now want to set the user's default home page to whatever they choose as their custom home page, and am trying to use the 'setter function for constants' that is defined in the provider function above.
I have done this by calling:
myAppConf.setConstant(myAppConf.HOMEPAGE, $location.path());
when the 'Confirm' button is pressed on the dialog box (with the 'set as homepage' checkbox checked).
However, when I press the 'Confirm' button, I get a TypeError in my console which says:
TypeError: myAppConf.setConstant is not a function
I don't understand why I'm getting this error... setConstant() is defined as a function with:
this.setConstant = function(constant, value){...};
so why is the console stating that it's not a function? How can I fix this?
Edit
The function where I'm calling myAppConf.setConstant() is defined in Pages/ctrls.js as follows:
angular.module('myApp.pagse')
...
.controller('LayoutCtrl', function($scope, $route, $location, $timeout, Page, myAppConf, NotifyMgr, DialogMgr,myAppUI){
...
$scope.confirm = function(e){
...
if($scope.checkboxModel){
...
myAppConf.setConstant(myAppConf.HOMEPAGE, $location.path());
}else{
console.log("homepage not changed");
}
};
setConstant is myAppConfProvider method, not myAppConf. If it should be available both in config and run phases, it should be defined on both a provider and an instance:
.provider('myAppConf', function(){
...
var commonMethods = {
setConstant: function (constant, value) {
constants[constant.toUpperCase()] = value;
},
...
}
Object.assign(this, commonMethods, {
$get: function () {
return commonMethods;
}
})
})
A cleaner way to do this is to use constant:
.constant('myAppConf', {
_constants: { ... },
setConstant: function (constant, value) {
this[constant.toUpperCase()] = value;
},
...
})
Since getters and setters can be considered antipattern unless they are justified, a KISS alternative is just:
.constant('myAppConf', {
'HOMEPAGE': '/alarms',
...
})
I'm building a little CRUD app in Backbone, and I'm stuck a little with a need to redirect from one view to another. My app consists of a layout view, in which other views are rendered, and a router. Here it is:
var router = Backbone.Router.extend({
routes: {
'': 'home',
'resumes/:id': 'showResume'
},
home: function () {
// renders a index view with my collection
this.layout.render(new ResumeList({collection: resumes});
},
showResume: function () {
if (!this.fullResume) {
this.fullResume = new FullResume({model: new Resume()});
}
// allowing to navigate via url with model id
this.fullResume.model.set('id', id).fetch({
context: this,
success: function () {
this.layout.render(this.fullResume);
}
});
}
});
Then, in my FullResume view I've got a delete event, which destroys the model. Here it goes:
var FullResume = Backbone.View.extend({
// tagName and other stuff
events: {
// other events
'click #delete': 'deleteResume'
},
// initialize, render and other functions
deleteResume: function () {
this.model.destroy({
success: function (res) {
console.log('DELETE model' + res.toJSON().id);
},
error: function () {
console.log('Failed to DELETE');
}
});
}
});
The function above works perfectly and deletes the model, but after deleting the model it still remains on it's view until I navigate somewhere manually. I read a bit and tried to manage how to render the main view after this event or redirecting to it, but didn't succeed a much.
You are looking for the http://backbonejs.org/#Router-navigate function with the trigger option set to true.
Here's an example: http://jsfiddle.net/x3t7u5p0/
Clicking on "Home" or "About" links will change the view, however I've added a delayed programmatic view change, when the About view renders, it will switch back to Home after the delay
render: function () {
this.$el.html(this.template);
_.delay(function() {
appRouter.navigate('home', {trigger: true});
}, 500);
}
We have a large Marionette app, with sub apps/modules.
Each of these registers its own router within the App.addInitializer.
What is the best way to flag certain routes as public and others as requiring authentication?
I have a way in the app to check if the user is authenticated or not, but I'm trying to avoid having to implement that check in every route handler.
PrivateModuleRouter.Router = Marionette.AppRouter.extend({
appRoutes: {
"privateRoute(/)" : "handlePrivateRoute",
}
});
var API = {
handlePrivateRoute: function() {
//I don't want to repeat this everywhere..
if(!Auth.isAuthenticated()) {
App.navigate('/login', {trigger:true});
} else {
PrivateRouteController.showForm();
}
};
App.addInitializer(function(){
new PrivateModuleRouter.Router({
controller: API
});
});
Is there way in the route definition to flag it as private, and then a top level route handler performs this check?
If it's on a Router event though, this may not trigger if the route handler was triggered directly (not passing trigger:true, and calling API.handlePrivateRoute() directly.
Disclaimer: as I don't personally use Marionette, this answer is based on Backbone only.
The execute function
Backbone provides the execute function in the router as a way to handle that kind of logic. Even the example has authentication logic in it:
var Router = Backbone.Router.extend({
execute: function(callback, args, name) {
if (!loggedIn) {
goToLogin();
return false;
}
args.push(parseQueryString(args.pop()));
if (callback) callback.apply(this, args);
}
});
The authentication router
One way to avoid repeating the execute in each router would be to make a base router for your app.
var BaseRouter = Backbone.Router.extend({
constructor: function(prefix, opt) {
// get the hash
this.auth = _.result(this, "auth", {});
BaseRouter.__super__.constructor.apply(this, arguments);
},
// requires auth by default?
authDefault: false,
/**
* Check the `auth` hash for a callback. Returns `authDefault` if
* the callback is not specified.
* #param {String} callbackName name of the function.
* #return {Boolean} true if the callback is private.
*/
hasAuth: function(callbackName) {
return _.result(this.auth, callbackName, this.authDefault);
},
// To easily override the auth logic in a specific router
checkAuth: function(){
return Auth.isAuthenticated();
},
execute: function(callback, args, name) {
if (this.hasAuth(name) && !this.checkAuth()) {
this.navigate('/login', { trigger: true });
return false;
}
}
});
Defining the specific routers
Then for each of your router, extend BaseRouter.
var SpecificRouter = BaseRouter.extend({
routes: {
'*otherwise': 'home', // notice the catch all
'public': 'publicRoute',
'private': 'privateRoute',
'unspecified': 'defaultAccessRoute'
},
/**
* The auth hash works like this:
* "functionName": [boolean, true if needs auth]
*
* home and publicRoute could be left out as it's the default here.
*/
auth: {
home: false, // public
publicRoute: false, // public
privateRoute: true, // needs authentication
// defaultAccessRoute will be public because BaseRouter
// defines `authDefault: false`.
},
home: function() {},
publicRoute: function() {},
privateRoute: function() {},
defaultAccessRoute: function() {},
});
And for a router which all routes are private by default:
var PrivateRouter = BaseRouter.extend({
authDefault: true,
routes: {
'*otherwise': 'home', // private
'only-private': 'onlyPrivate', // private
},
// ...snip...
/**
* Optional example on how to override the default auth behavior.
*/
checkAuth: function() {
var defaultAuthResult = PrivateRouter.__super__.checkAuth.call(this);
return this.specificProperty && defaultAuthResult;
}
});
In github you can find many solution for calling some methods before router's execution. For marionette you can use ideas from marionette-lite extension based in filters system.
You should define filter, for example RequresAuthFilter as:
import { Filter } from 'marionette-lite';
const RequresAuthFilter = Filter.extend({
name: 'requresAuth', // name is used in controller for detect filter
async: true, // async mode
execution: Filter.Before,
handler(fragment, args, next) {
// Requesting server to check if user is authorised
$.ajax({
url: '/auth',
success: () => {
this.isSignedIn = true;
next();
},
error: () => {
Backbone.navigate('login', true);
}
});
},
});
or short sync way:
import { Filter } from 'marionette-lite';
const RequresAuthFilter = Filter.extend({
name: 'requresAuth',
handler(fragment, args) {
if (!window.isSignedIn) {
Backbone.navigate('login', true);
}
},
});
And add this filter to Controller as:
const AppController = Marionette.Object.extend({
// Add available filters map
filtersMap: [
new RequresAuthFilter()
],
filters: {
// e.g. Action that need authentication and if user isn't
// authenticated gets redirect to login page
requresAuth: ['logout', 'private'],
},
logout() { /* ... */ },
private() { /* ... */ }
});
I have 2 models which are cross referencing each other. This could look like this:
MainModel:
define(
[ 'durandal/app', 'durandal/plugins/router', 'models/Shell', 'models/EditModel' ],
function (app, router, shell, editModel) {
//...
return {
//...
// This function should be accessible by the EditModel
update: function() {
//...
},
showEditView: function() {
// Initialise the EditModel with some data and show the according view afterwards
editModel.init('set some important stuff here...');
router.navigateTo('#/EditView');
}
//...
};
}
);
EditModel:
define(
[ 'durandal/app', 'durandal/plugins/router', 'models/Shell', 'models/MainModel' ],
function (app, router, shell, mainModel) {
//...
return {
//...
// This function should be accessible by the MainModel
init: function() {
//...
},
showMainView: function() {
// Update the the MainModel with some data and show the according view afterwards
mainModel.update('set new data here...');
router.navigateTo('#/MainView');
}
//...
};
}
);
Unfortunately this is not working. If I load my page on the MainView and call showEditView, the variable editView is known and everything works fine but then the variable mainModel in the EditModel is undefined and therefore the call mainModel.update(...) fails.
Same thing happens if I load my page on EditView but in the "opposite direction" (var mainModel in the EditModel is known, but editModel in the MainModel is undefined).
Is this a known issue and if so: How can i circumvent it?
I also posted this question in Durandals Google Group
Thanks
Check requierejs documentation for circular dependencies http://requirejs.org/docs/api.html#circular.
Circular dependencies are rare, and usually a sign that you might want
to rethink the design. However, sometimes they are needed, and in that
case, use require() as specified above.
For main.js add require as dependency and then explicitly require models/EditModel should do the trick. Either replicate that for the other modules or rethink the design ;-).
define(
[ 'require', 'durandal/app', 'durandal/plugins/router', 'models/Shell', 'models/EditModel' ],
function (require, app, router, shell, editModel) {
//...
return {
//...
// This function should be accessible by the EditModel
update: function() {
//...
},
showEditView: function() {
// Initialise the EditModel with some data and show the according view afterwards
require('models/EditModel').init('set some important stuff here...');
router.navigateTo('#/EditView');
}
//...
};
}
);
I have been working on a new project for sometime and recently started using require.js in the project.
It all seemed to work fine until I tried making a call from application.js to dashboard.js when the tab #dailyTab or #weeklyTab was clicked or access varialbe var a in dashboard,js. My code is below
main.js
requirejs.config({
baseUrl : '../static/js/',
paths : {
'jquery' : 'http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min',
'jquery-ui' : 'http://ajax.googleapis.com/ajax/libs/jqueryui/1.9.1/jquery-ui.min',
'bootstrap' : ['//netdna.bootstrapcdn.com/twitter-bootstrap/2.2.1/js/bootstrap.min', 'bootstrap.min'],
},
shim : {
'bootstrap' : ['jquery'],
'bootswatch' : ['bootstrap'],
}
});
require(["jquery", "jquery-ui", "bootstrap", "dashboard", "application"], function($, jui, bootstrap, dashboard, application) {
$("#myTab a:first").tab("show");
}, function(err) {
console.log(err)
});
dashboard.js
define(function() {
var a = 10;
var Reload = {
dailyCharts : function() {
console.log("Daily Charts");
},
weeklyCharts : function() {
console.log("Weekly Charts");
}
};
var Display = {
none : function(node) {
console.log("none");
},
block : function(node) {
console.log("block");
}
}
});
application.js
define(["jquery", "dashboard"], function(jquery, dashboard) {
$("#dailyTab").click(function() {
dashboard.Reload.dailyCharts();
});
$("#weeklyTab").click(function() {
dashboard.Reload.weeklyCharts();
});
});
Can someone tell me what I have done wrong or what I can do to fix this issue. I accept my coding skills is not very good.
Any help on this is much appreciated.
Thanks in advance
Well, your dashboard.js didn't return anything. How would application.js use it?
define(function() {
var a = 10;
var Reload : {...};
var Display : {...};
//return the interface
return {
Reload : Reload,
Display : Display
}
});