Loading Ext.app.ViewController via Ext.application() / modern toolkit - extjs

According to the Sencha Documentation here: https://docs.sencha.com/extjs/7.0.0/modern/Ext.app.Application.html
we can automatically load application controllers as needed via the controllers configuration of the Ext.application() method (thus avoiding the need to include many script tags within the html) like so:
Ext.application({
name: 'App',
controllers:['Main']
});
this requires a controller like this:
Ext.define('App.controller.Main', {
//extend: 'Ext.app.ViewController',
extend: 'Ext.app.Controller'
});
And this works. However, the controllers must derive from Ext.app.Controller and can not be Ext.app.ViewController (in which case we receive error because of a missing doInit() controller method).
Can anybody explain why is that?
And how to instantiate an Ext.app.ViewController using the automatic loading logic?

You are confusing ViewControllers with AppControllers. ViewControllers are the C from MVC while AppControllers kinda global. Those are not specific to any view but application. The lifecycle also different as ViewControllers created for views while AppControllers created for the entire application. In your example you need Ext.app.Controller instead of ViewController, if you wish to use ViewController it must be attached to e.g. a Ext.Component.
Ext.define('ViewController', {
extend : 'Ext.app.ViewController',
alias: 'controller.SomethingController',
onClick: function() {
}
});
Ext.define('View', {
extend: 'Ext.Panel',
controller: 'SomethingController',
items: [{
xtype: 'button',
handler: 'onClick',
}]
});

Well, i can understand this.
However, listing the controller inside the controllers array within Ext.application() automatically loads the appropriate controller's javascript file:
Ext.application({
name: 'App',
controllers:['Main'] //loads app/controller/Main.js
});
After this, listing the view class name within the controllers views array automatically loads the view's JS file:
Ext.define('App.controller.Main', {
//extend: 'Ext.app.ViewController',
extend: 'Ext.app.Controller',
views:['Main'] //loads app/view/Main.js
});
This way i do not need to include the 2 JS source files within the html. I could use the views config within the Ext.application() also, but the Sencha docs here: https://docs.sencha.com/extjs/7.0.0/modern/Ext.app.Application.html
is telling us this:
*Note that we didn't actually list the Views directly in the Application itself. This is because Views are managed by Controllers, so it makes sense to keep those dependencies there. ... In turn, each Controller simply needs to list the Views it uses and they will be automatically loaded*
And this is true, but only for the application controllers, which means i can not benefit from the automatic loading when want to use a view controller.
The other confusing part is that the application controller actually can control a view also...

Related

Angular 1 common service/module/library for multiple "apps"

I can't describe it very well but:
I have one angular app for "one" functionality.
E.q. I have "campaigns" functionality. Add, Edit, Display all, display in another way.
For add and edit I have that same app and controller and context etc.
For rest I have different folders with app, controller, context, config, eventHanlder etc.
But all of them have simillar code in ~70%.
It is context with download and upload data via $http to ASP.NET WebAPI project.
And few methods in controller.
I want "merge" it into one but I cant don't want merge these functions into one app because they have different "view".
So I was thinking about:
Make "routing" app for these simillar apps.
one context, config, and controller same in more than half
some different options so it won't be "SoC"?
Move common from "context, config, controller" to another files/apps and inject them to concrete controller.
But I don't know how to do it.
I know I can "factory" method to app inside it config but I want these "services" be indepentent to controllers and apps. As different app in different catalog. So I can inject them via controller.
How it is possible?
Yeah I really new in Angular ;)
Question 1:
I would advise to look at the ui-router module. This module allows you to define url routes to states with views and nested states/views. Very useful for anything more than a single page app.
Question 2:
Sounds like you're talking about setting up a few angular.modules that depend on one another.
In general you would group related services, controllers, configs into a module. If another module needs to use one of those, you make that module depend on the other.
Example:
// myApp.foo module
angular.module('myApp.foo', [])
.factory('Foo', function(){
return {
data: {
foo: 'angularjs is cool!';
},
doStuff: function(){
}
};
})
.controller('FooController', function($scope, Foo){
$scope.foo = Foo.data.foo;
Foo.doStuff();
});
// myApp.bar module
angular.module('myApp.bar', ['myApp.foo']) // notice dependency on myApp.foo
.factory('Bar', function(Foo){ // you can inject Foo because of dependency
return {
data: {
bar: 'using angular modules helps us organize'
},
doStuff: {
},
doOtherStuff: {
Foo.doStuff();
}
};
})
.controller('BarController', function($scope, Bar, Foo){ // you can inject Foo into controllers too
$scope.foo = Foo.data.foo;
$scope.bar = Bar.data.bar;
$scope.doFoo = Foo.doStuff;
$scope.doBar = Bar.doStuff;
$scope.doOtherBar = Bar.doOtherStuff;
});
It is always good practice to think about and develop your modules in a way that they can be reused in different projects.
Update: (RE: question about /AppCommon, /App1, /App2, /App3)
Just make your service in: /AppCommon/my-service.srv.js*
It would look something like:
angular.module('MyCompany.Common', [])
.factory('CommonService', function($http){
return {
doStuff: function(){
$http.get('/some-url').then(function(res){
});
}
};
});
And in your /App1, /App2, /App3 projects:
Include that js file into your HTML:
<script src="/AppCommon/my-service.srv.js></script>
Inject the MyCompany.Common module into the areas of the apps where you need it.
angular.module('App1', ['MyCompany.Common']);
Inject the CommonService into the app's controllers/services where you need it.
angular.module('App1', ['MyCompany.Common'])
.controller('App1Controller', function($scope, CommonService){
});
*Note: You can name your javascript file however you want. I have found it very helpful to include .srv in files containing services, .ctrl in files containing controllers, .drv in files containing directives, .tpl in template files, etc... This goes along with the best practice of having one thing per file.

How to pass parameter from controller to view?

I am loading a view like :
items:[{
xtype :<xtype>
}],
The class for has been included in "requires" in this controller.
How can I pass a parameter to the view that will be loaded? I don't wish to use config or set something in global namespace. My idea is to use same view with multiple parameters.

Instantiating a controller dynamically from a string and attaching it to an element

I'm writing code that lets me create dialogs that have some shared elements (e.g. cancel button, ok button, title) but you can also embed your own template and controller to customise it more. When you create the dialog, you specify the template and controller you want in "template" and "controller" field of a dialog "object" which is passed on to the primary controller for handling dialogs. The dialog controller now needs to embed the template and instantiate the named controller to control the template elements.
The template code I'm trying to use for this part is this:
<ng-include ng-controller="dialog.controller" src="dialog.template">
If I remove the controller part, the template appears properly. The controller part generates this error:
"Argument 'dialog.controller' is not a function, got string"
How do I instantiate the controller?
Edit: As an example, with the Angular UI modal library you can do this to create a controller:
var modalInstance = $modal.open({
templateUrl: 'dialog_form.html',
controller: 'DialogFormController',
resolve: {
options: function() {
return dialog;
}
}
});
Where the controller field is the name of one of your controllers. How can I copy this functionality to specify my controller with a string instead of a function?
Angular Controllers are functions, and when you specify ng-controller, Angular will call that function and treat the return result of it as the controller object. That's why controller definitions are done as function-type constructors.
But when this happens there's an additional piece of magic - Angular has a controller Provider that maintains a registry of known controllers, for a variety of reasons. (For instance, it knows what injections they need.) You can't just define a global function and hope it gets called.
If you want to do this, see the ngController documentation which describes this option:
From https://docs.angularjs.org/api/ng/directive/ngController:
If the current $controllerProvider is configured to use globals (via
$controllerProvider.allowGlobals()), this may also be the name of a
globally accessible constructor function (not recommended).
You would use something like this if you wanted the functions supplied to you to be in a global variable, although as noted above it's not recommended.
ngController can also take an expression. In that case it will look for dialog to be a scope variable in the parent controller where this is used, so in there you would need something like:
$scope.dialog.controller = function() { /* ... */ };
This second technique is less useful if you want to make a generic library, but there are ways around it. For instance, you might have your developers create a dialog collection in $scope or $rootScope:
$rootScope.myDialogs['dialog1']['controller'] = function() { };
and then use this in your template like:
<ng-include ng-controller="myDialogs.dialog1.controller" src="myDialogs.dialog1.template">
Finally, you could implement your own ngInclude directive that just did both of those things together from a single argument ('dialog1'). AngularJS Directives give you incredible control over the templates and controllers used to run them.

Dynamically loading AngularJS modules from within Templates (Views)

Background: Let's suppose for the sake of argument that you have 100,000 views (partials). Let's also suppose you have accompanying view-scoped controllers, and potentially view-scoped services and filters as well. Try to envision an aggregating application that hosts 100,000 disparate small applications.
Issue: When you have "partials" that require accompanying controllers, the typical solution is to do something like this:
$routeProvider.when('/app1', {
templateUrl: 'partials/view1.html',
controller: 'controller1'
});
The controller is typically loaded from index.html via:
<script src="js/directives/Controller1.js"></script>
The problem with this approach is that it doesn't scale. There are solutions out there for dynamically loading controllers, but they still require adding touch points in various config.
Ideal Solution: Ideally - again for very small applications whose numbers are in the 000's, the controller could be loaded dynamically, and from within the partial itself. This would alleviate the need to manage several files and several configuration touch points (not to mention network requests), and keep each partial very well contained.
It would look something like this:
In router:
$routeProvider.when('/apps/:appId', {
templateUrl: 'partials/app-frame.html',
controller: 'AppCtrl'
});
In containing html (app-frame) include the relatively disparate "mini app":
<h1>Currently hosting {{appId}}</h1><hr>
<div class="ng-include: appUrl"></div>
In partial resolved with appUrl, define controller and markup in one:
<script>
myApp.controller('controller1', ['$scope', function ($scope) {
$scope.foo = "bar";
}]);
</script>
<div ng-controller="controller1">
{{foo}}
</div>
For cases like this, where there are a lot of partials and a 1-1 mapping for controller and view, it can make sense to couple the two for development efficiencies and maintenance. It's a lot cleaner than using several files and additional configuration touch points.
The problem is, this doesn't work. It could be as simple as forcing the script to load prior to applying the directive... but not sure how to do that?
Here are some similar explanations of the problem:
https://groups.google.com/forum/#!topic/angular/H4haaMePJU0
Loading Partial Page With Angular and Compile The Controller
Igor from the AngularJS team says:
I see.. we looked into supporting script tags in jqlite, but what needs to be done to get a cross-browser support involves a lot of black magic. For this reason we decided that for now we are just going to recommend that users use jquery along with angular in this particular case. It doesn't make sense for us to rewrite one third of jquery to get this working in jqlite.
But I don't know what he means by "use jquery" ... JQuery is already loaded into the application from index.html (and prior to angularjs), but it sounds like I need to do something specifically within the partial itself.
You cannot add new controllers through module('app').controller(name, function() { .. }) after AngularJS bootstrap. In order make it work you should use $controllerProvider.register(name, function() { .. }).
You can override the original controller registering function in following way to be able to add controllers pre and pos bootstrap:
var app = angular.module('app', [
'ui.router'
]);
app.config(function($controllerProvider) {
app.controller = function (name, controller) {
$controllerProvider.register(name, controller);
};
});

Define controllers in app.js Extjs

Here my app.js code :
Ext.Loader.setConfig({
enabled: true
});
Ext.application({
name: 'KP',
appFolder: 'scripts/app',
controllers: [
'login.Login'
],
views: [
'login.Login'
],
launch: function () {
var view = Ext.widget('login')
}
});
If I want to use some others views, controllers, models and stores in my application, should I define them in app.js? (like this : controllers[....all of my controllers.....])
Are there other way to get to work init function in my controllers? Thanks!
There are many ways...
Following some basics first:
All controllers that are listed within the controllers array of the application controller get instantiated at startup (application get initialized with the onReady event. Also the init() and onLaunch() methods of the listed controllers get called. See the linked API for details when this occurs). Now each instantiated controller initialize its stores,views and models (creates getter for all and over that creates instances of each store while overwriting the storeId and append them to the Ext.StoreMgr). Note that each controller will contains his own models, stores and views.
The simplest way to receive a controller that is not listed in the application controller is by using a reference of the application controller and calling getController(name)
It is Important to know that while using that way the receive a controller instance the getter method will not call the init() or onLaunch() method for the invoked controller, it will just try to get the controller or create it. You should also note the API for those methods in these controllers are no longer correct. You need to set an init bool to these controllers and check it on the reference that the getController(name) method hands back to you. If it is undefined/false you call the init() / onLaunch() by yourself and set it after that. I use these technique by myself to reduce intial load for bigger application. I guess you don't really need this for just a handful of controllers.
Update
Due to some changes in the 4.1.x release it is no longer required to init the controller
manually. This is now done for us by the getController method
You can define as many controllers as you want. To implement hierarchy you can define views and stores related to certain controller within it.
For example:
controller/
artists.js (inside: artistsView1.js, artistsView2.js, artistsStore.js)
paintings.js (inside: paintingsView1.js, paintingsStore.js)
All of your views and stores used in controllers would be loaded and initializadduring Applocation load.
The controllers you define in your application are loaded before the application launch() is called. As each controller is loaded its models, views and stores (MVS) are also loaded.
Although you can define the MVSs in your application, I'd recommend that each controller defines its related MVSs, as it is possible for the programmer to load controllers (and their related MVSs) upon request rather than by default. It is also a good practice from reusability point of view (so if you ever to reuse the controller, you know what MVSs come with it).
You may want to include some views in the application if, say, they are used without controllers, like some sort of a custom window you display if there was some error in the system.

Resources