ExtJs5: Call Controller 2's method from Controller 1 - javascript

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('...');

Related

How do I change one Directive when I click another Directive?

So here's my problem:
I have a page which displays two different graphs. Each of these graphs are there own Directives which has their own isolate scope.
When a user clicks on one of the bar's in the chart in Directive #1, I need the graph in Directive #2 to change.
Currently both Chart Directives are being fed their respective data sets from the Controller of this page.
Now from what I've seen I really have about three options:
Pass a callback function into Directive #1 which be called when the chart is selected. This callback function will exist on the Controller of the page and then can change the necessary data in order to get Directive #2 to update via data-binding.
Events. Fire an event on $rootScope inside of Directive #1 when the chart is selected. I can then listen to this event on the Controller and change the data in Directive #2 to update it via data-binding.
Use a Library like Rx.JS in order to make an observable inside of Directive #1. I haven't used Rx.JS with Angular that much so to be honest I have no idea if this would even work or what it would look like. But if I could expose this Observable to page's Controller from within Directive #1 then I should be able to subscribe to it and update Directive #2 when necessary.
Now I have a good understanding of Solution #1 and #2 but they have their own issues:
This very quickly could turn into "callback hell" and doesn't seem to be a very "Angular" solution. This also creates a bit of a tight dependency between the page's Controller and this very generic Chart Directive. Out of my options I think this is the best solution but I would love a better one.
I have to build a way to specify id's on the event names that are unique to that explicit instantiation of the directive, since theoretically there could be more than one of these Chart Directives on the page.
I would love to know if anyone has any other ideas that I haven't thought of or a better approach? Maybe even something that I'm not aware of that Rx.JS offers with Observable's?
TLDR: I need to click on Directive #1 and have it effect what is currently being displayed in Directive #2.
I think this can be done by using two binding scopes in your directive like,
.directive('graphOne', function () {
return {
template: blah/blah.html,
scope: {
scopeToPass: '='
}
}
})
and
.directive('graphTwo', function () {
return {
template: blah1/blah1.html,
scope: {
scopeToGet: '='
}
}
})
and in html
<graph-one scope-to-pass="uniqueScope"></graph-one>
<graph-two scope-to-get="uniqueScope"></graph-two>
Since we are assign $scope.uniqueScope to both directives, and the scopeToPass is two way binding, when the value of scopeToPass get changed it will be passed to uniqueScope and from uniqueScope it will be passed to scopeToGet.

Angular JS Concept 'Separation Of Concerns' --> CRUD Screen

I have a two CRUD screens in AngularJS.
1) Separate html file for View, Add and Edit. View Controller, Add Controller and Edit Controllers also Separate. This structure creating more duplicate code in html and controller side.
2) Separate html file for View, Add. View Controller, Add Controller only Separate. During edit mode I'm using a Boolean in controller to find its in edit mode or not.
I'm new to AngularJS. Anybody clearly tell me pros and cons, which way is correct in AngularJS 'Separation Of Concerns' concept.
I'm not sure you're going to the good direction.
By separation of concern, what is intended is to split the code that is responsible for managing the view, and the code responsible to make calls to your server. Imagine you want to make CRUD around a person, I would do the following :
service :
angular.module('app').factory("personService", ["$http", function($http)]){
return {
create : create,
update: update,
remove : remove,
get: get
};
function create(person){
return http.post("person/create", person);
}
// other functions
}
Then, I would only have 1 controller for everything :
angular.module("app").controller("PersonController", ["personService", function(personService)]){
var self = this;
self.isUpdate = true; // Insert logic here
self.isCreate = false; // Insert logic here
init();
self.save = function(){
var promise = self.isCreate ?
personService.create(self.person)
:personService.update(self.person);
promise.then(function(result){
// Handle return of save;
});
};
function init(){
if (!self.isCreate){
personService.get(personId).then(function(result){
self.person = result.data.person;
});
}
}
}
And then I would have the following view :
<div ng-controller="personController as person">
<label>Name: </label>
<input type="text" ng-disabled="!person.isUpdate" ng-model="person.person.name" />
<button ng-click="person.isUpdate = !person.isUpdate;">Edit</button>
<!-- Edit : the code 'person.isUpdate = !person.isUpdate;' could also be into a controller's function (like the save function) -->
<button ng-click="person.save()" ng-if="person.isUpdate">Save</save>
</div>
I also recommend you to read this : https://github.com/johnpapa/angular-styleguide
Separation of concerns means that you have a well defined structure of your application: the data model in the application is decoupled from the business and presentation logic. It is the base of the MVC pattern, which defines the view, the controller and the model.
This separation makes the code maintainable and easy to test.
Follow this guidance how to architect your Angular application.
The model should:
Include the domain data;
Implement the management of the domain data (query, edit, delete, storing mechanism, REST implementation, http fetching);
Expose an API that makes possibly the model usage in controller or other service;
The model should not:
Provide the details on how the domain data is managed (all REST implementation, http calls should be encapsulated in the model);
Contain logic that transforms the model based on user interaction (it is controller's role);
Contain logic for displaying data to the user (this is the view’s job);
A controller should:
Contain the logic required to initialize the scope;
Contain the logic/behaviors required by the view to present data from the scope;
Contain the logic/behaviors required to update the scope based on user interaction;
A controller should not:
Contain logic that manipulates the DOM (that is the job of the view);
Contain logic that manages the persistence of data (that is the job of the model);
Manipulate data outside of the scope;
A view should:
Contain the logic and markup required to present data to the user
A view should not:
Contain complex logic (this is better placed in a controller);
Contain logic that creates, stores, or manipulates the domain model.
This guidance is taken from this awesome book, which I recommend for any starting AngularJS developer.
Related to your example, in my opinion you should:
Create a controller and a template for editing and adding; Depending on model's isNew property, you can apply editing or adding action;
Create a controller and a template for viewing the model;
Optionally, if you have a collection of models, you can create a new controller and view also.
However it depends on the amount of logic behind the model. If the model is trivial, probably you can implement everything in a single controller and view. But it's rare.

Heavy controller communication in AngularJS

I have implemented a single page application with AngularJS. The page consists of a content area in the middle and sections assembled around the center that show additional info and provide means to manipulate the center.
Each section (called Side Info) and the content area have a separate AngularJS controller assigned to them. Currently, I communicate via $rootScope.$broadcast and $scope.$on(), e.g.
app.controller('PropertiesController', function ($scope, $rootScope) {
$scope.$on('somethingHappened', function(event, data){
// react
});
});
I then call to communicate with other controllers:
$rootScope.$broadcast('somethingHappened', data);
I have quite a lot of communication happening between the Controllers. Especially if something is going on in the content area, several side info elements have to adopt. The other way around is also frequent: a user submits a form (located in a side info) and the content area and other side info elements have to adopt.
My question:
Is there a better way to handle SPA with heavy controller communication?
The code works fine but it is already getting a bit messy (e.g. it is hard to find which events are handled where etc.). Since the application is likely to grow a lot in the next weeks, I'd like to make those changes (if there are any better solutions) asap.
This is really interesting. Pub/Sub should be a right solution here.
You could add extra order to your project by using Angular services as your MVC's model, and update this model for each change. The issue here is that you should implement an observable pattern inside your service and register to them, in order for this to be live synced. So - we're back to Pub/Sub (or other Observable solution that you could think about...).
But, the project will be better organised that way.
For example - SideInfo1Service will be a service/model. Each property change will trigger an observable change which will change all listeners:
myApp.factory('SideInfo1Service', function($scope){
var _prop1;
return {
setProp1: function(value){
$scope.$broadcast('prop1Changed', value);
_prop1 = value;
},
getProp1: function(){
return _prop1;
}
}
});
You could find those really interesting blog posts about using Angular Services as your MVC's model:
http://toddmotto.com/rethinking-angular-js-controllers/
http://jonathancreamer.com/the-state-of-angularjs-controllers/
And, this post is about observable pattern in Angularjs:
https://stackoverflow.com/a/25613550/916450
Hope this could be helpful (:
You have multiple options in order to avoid broadcasts calls:
Share data between controllers using services like it was mentioned in the comments. You can see how to this at: https://thinkster.io/egghead/sharing-data-between-controllers
Create a main controller for the whole page and child controllers for each section (Content Area and Side Info). Use scope prototype inheritance. For example:
if in main controller you have:
$scope.myObject = someValue;
in child Controllers you can set:
$scope.myObject.myProperty = someOtherValue;
you can access myObject.myProperty from your Main Controller
You can use
$rootScope.$emit('some:event') ;
because it goes upwards and rootscope ist the top level
use
var myListener = $rootScope.$on('some:event', function (event, data) { });
$scope.$on('$destroy', myListener);
to catch the event
Then you have a communication on the same level the rootscope without bubbling
Here is my implemented eventbus service
http://jsfiddle.net/navqtaoj/2/
Edit: you can use a namespace like some:event to group and organize your event names better and add log outputs when the event is fired and when the event is catch so that you easy can figure out if fireing or catching the wrong eventname.
Very important question and very good answers.
I got inspired and created three plunks showing each technique:
Broadcasting: http://embed.plnkr.co/lwSNDCsw4gjLHXDhUs2R/preview
Sharing Service: http://embed.plnkr.co/GptJf2cchAYmoOb2wjRx/preview
Nested Scopes: http://embed.plnkr.co/Bct0Qwz9EziQkHemYACk/preview
Check out the plunks, hope this helps.

rivets.js: prepopulate model with data from view on init

Perhaps this seems a bit backwards, but I have a view bound with Rivets.js for which I'd like the view to populate the model on initialization.
The usecase is that I'm using server-side rendering to return a snippet (the view) including rivets' data-attributes. So NO JSON is returned from server to client.
Now, by pressing 'edit' a user may put the content in 'edit'-mode, and start editing at will. (Using contenteditable, but this is out of scope here I guess).
So how to make sure the model is populated with values from the view on init?
I know that this question is a little outdated but I recentry tried rivets and I came across the same problem.
The solution:
// In your rivets configuration you disable preload:
rivets.configure({
templateDelimiters: ['[[', ']]'],
preloadData: false
});
// you bind your data
var binding = rivets.bind($('#auction'), {auction: auction});
// you manually publish it once to populate your model with form's data
binding.publish();
And that's it. I still don't know how to disable prelaod per bind
From the example on Rivets website (assign to 'rivetBinding')
var view = rivets.bind($('#auction'), {auction: auction});
doing rivetBinding.publish(); will bootstrap the model with values from the view for all bindings that have 'publishes = true'.
This question is old but it still has no accepted answer, so here goes:
You need to disable the preload configuration so rivets doesn't override whatever is in the input with what you have in your model at the time you do the binding. This can be done via the preloadData=false configuration, either globally (rivets.configure(...)) or view-scoped (third param to rivets.bind(...)).
After the binding, you need to publish the view (pull the values to your model). You also need to set up the observers via sync() call, otherwise your binded methods won't be triggered.
Using the same example as the previous answers:
var view = rivets.bind($('#auction'), { auction: auction }, {
preloadData: false
});
view.publish();
view.sync();

Backbone.js with non RESTful app? (is Backbone.js right for my current project?)

I'm trying to figure out if Backbone.js is the right framework for my current project: a visualization app.
I have a number of questions:
1) State / Routing?
As this is not your typical RESTful app, but rather a visualization application with various chart types and settings for these charts, how do i maintain state in the URL?
Let's say my areaChart model has a number of defaults like this:
AreaChartModel = Backbone.Model.extend({
defaults: {
selectedCountries: [],
year: 1970,
stacked: false
},
initialize: function(){
[...]
}
});
On an update to the model I'd like to serialize some of these attributes so that I can bookmark the specific state: chartApp.html#!year=1970&stacked=false etc.
And vice-versa, when initing the app with this state, how do I "deparam" the url state and set the model? Can I use Backbone's intrinsic routing?
2) Controller and coupling?
It seems as Backbone has a pretty tight view-model coupling?
Is this really how I should bind for example my areaChartView to the model?
AreaChartView = Backbone.View.extend({
initialize: function(){
areaChartModel.bind("change:year", this.render);
}
});
Isn't this normally the role of the controller?
3) Continuation: Model vs. Controller?
Given this scenario:
A change in the "Sidebar" should trigger a sequence of functions:
1) "New data for the current selection should be loaded"
2) "Based on this data, the scales in the Visualization view should be updated"
3) "The visualization view should be rendered"
Where should I place these functions and how can I create an event in the model that I trigger when the state is stable? (i.e. when all the functions have been invoked and it's time to set the view states?)
1) I would use Backbone.js native routing as much as possible using “:params” and “*splats” , read more. You could fit all your queries into the Backbone.js routing but I would personally sacrifice certain things in favor of intuitive UI buttons
e.g. I would have the default as a line bar and you can't preset this with the URL but to change to a stacked graph would be a simple click of a button.
I would probably stray from ever using ? and & in my URL's. I might come back to this point later as it is interesting.
2) Your example is fine and you just need to remember Backbone.js MVC terminology doesn't correlate to traditional MVC.
Backbone Views are essentially the Controller in traditional MVC.
Backbone Controllers are simply a way of routing inside a framework.
The templating engine you use with Backbone.js is the traditional MVC view.
3) Still writing
Regarding question #3, I would create a Model and a View for the slider.
Then I would associate the triggering of the change event on the model to some function in the view that updates the graph's view (like changing the scales). Something like:
var Slider = Backbone.Model.extend({})
var SliderView = Backbone.View.extend({
initialize: function() {
this.model.bind('change', this.render);
}
render: function() {
// load data, change scales, etc.
}
});
var slider = new Slider();
var slider_view = new SliderView({ model: slider });
Maybe a good idea would be to put the bindings in a parent view, that would then dispatch to sub-views, coordinating their work.
Do sit down for a while and consider if maintaining the entire state is at all a good idea ? The key motivations for having url-based state management is being able to support browser based navigation buttons and being able to bookmark a page. In a visualization app, your data would probably change every moment. This is not something you want to persist in your app-url. Do you really want that when a user bookmarks your app and comes back to it three days later - he sees the visualization for three days old data ? For your scenario, assuming I have not misunderstood your requirements, I would recommend to keep the data state in your model itself.
Also regarding synchronization of views with model data, Yes you can code all the binding logic on your own. In that case your View class will take care of setting up the bindings on the first render. And upon subsequent calls to render, which can be invoked in response to any change event in the model, will refresh the DOM/canvas where the visualization is present.
Probably you should be look forward to a plugin for data-synchronization that takes care of much of boilerplate for you. This page lists some of the data-binding extensions available. Orchestrator is another solution that I have been working on, which might be helpful in this regard.

Categories

Resources