I am very new to Salesforce. I am using some variables which is common in multuple methods inside this controller. Is there a way to define the variables once and reuse them inside this component methods?
controller
doInit : function(cmp, event, helper) {
var categoryUsed = 'Abc';
var locations = ['ten', 'twenty', 'thirty'];
var address = window.location.search;
//other logic1
},
handleFlag : function(cmp, event, helper) {
var categoryUsed = 'Abc';
var locations = ['ten', 'twenty', 'thirty'];
var address = window.location.search;
//other logic2
},
handleArea : function(cmp, event, helper) {
var categoryUsed = 'Abc';
var locations = ['ten', 'twenty', 'thirty'];
var address = window.location.search;
//other logic3
}
Is there a way to declare these common variables (categoryUsed, locations,address) once and use in all the above methods inside this component?
In an Aura component like this one, your persistent or shared storage should be in component attributes.
An alternate approach is to factor common code that uses constant values and shared logic, rather than or in addition to shared state as such, into the helper object to reduce code duplication.
Aura is an obsolete framework. While there are still a handful of places where Aura is required, if your use case permits, consider using Lightning Web Components instead. LWC allows you to structure your component using more mainstream, easy-to-understand structures, including modules and classes with instance variables.
Related
I'm not sure if I'm thinking about my problem incorrectly, but basically here it is: I have a pen and paper gaming site I've built in PHP that I'm converting to Angular. I have one particular set of pages (for character sheets, displaying and editing) that are hit, then based on URL parameters, instantiate one of a few dozen subclasses, and call a common function among them. This function displays the page according to that subclasses template and logic. Example:
URL: /characters/dnd4/5/
When someone hits /characters/(anything)/ the site looks up (anything) to see if its a valid system, and instantiates an object of class (anything)Character, which is a child of Character. Character has a method called displayEdit which in (anything)Character calls the correct template for that system. Then everything gets carried down: functionality common to all characters (or a set of characters) is shared by parent classes, which can go up 2-3 tiers before hitting Character.
In PHP, this was just a case of having everything hit a set of common parents, but basic logic for the page is in the Character class, and then specific actions in the child classes. I'm having trouble figuring out how to map this in Angular. So far, I've just been creating controllers that do the logic for their page; I'm not sure how to set this up for logic being split to other functionality. I thought about making each option a directive, but can a directive be called dynamically? Should I be creating JS classes that operate the same? Can they carry the $scope from controllers?
All in all, I'm just not sure how to tackle this, or I've just overthought this so much I can't see an obvious solution.
Class inheritance in JavaScript can be done in a few ways. Creating closures with private variables and returning accessor methods is one such way, as opposed to prototyical inheritance. In Angular, you can abstract all your class logic into a service, then pass it into your controllers which will create the relevant view by loading a particular template & populating the correct information. Here is an example of creating a base class, a couple of sub-classes, and a sub-sub-class. I did not include templates, as this is simply a matter of calling a particular template file (via ng-include or by directive), but the general idea is here.
Update: The more succinct, ES6 version of the code
// Module
angular.module('app', [])
// Service
function characterService () {
return {
character: function (spec) { // base class
var type = spec.type || 'untyped'
var name = spec.name || 'unnamed'
var stats = {
health: 10,
mana: 10
}
return {
getType: function () {
return type
},
getName: function () {
return name
},
getStats: function () {
return stats
},
setStats: function (newStats) {
stats = newStats
}
}
},
// sub class of character
elf: function (spec) {
spec.type = spec.type || 'Elf'
var elf = this.character(spec)
var stats = elf.getStats()
stats.health += 10
elf.setStats(stats)
elf.attack = function () {
return 'You have shot an arrow!'
}
return elf
},
// sub class of character
mage (spec) {
spec.type = spec.type || 'Mage'
var mage = this.character(spec)
var stats = mage.getStats()
stats.mana += 10
mage.setStats(stats)
mage.attack = function () {
return 'Woah! Magic missile!!'
}
return mage
},
// sub class of mage
warlock (spec) {
spec.type = 'Warlock'
var warlock = this.mage(spec)
var stats = warlock.getStats()
stats.mana += 10
warlock.setStats(stats)
warlock.attack = function () {
return 'Cthulhu is summoned!'
}
return warlock
}
}
}
//Register Service
angular.module('app').factory('characterService', characterService)
// Main Controller
function main ($scope, characterService) {
$scope.player1 = characterService.elf({name:'Erik'})
$scope.player2 = characterService.mage({name:'Aayla'})
$scope.player3 = characterService.warlock({name:'Zenthyr'})
}
// Register controller
angular.module('app').controller('main', main)
I have multiple Flux stores. Now clearly, all of them are extending the same Event emitter singleton. This has led to events across stores clashing with each other (even the most common, emitChange). There seems to be no difference between doing Store1.getID() and Store2.getID(), because stores seem to be one large object extended from every other store. What am I doing wrong?
I have been having this issue for a while now, and its driving me nuts. I am sure this has a simple answer that I am missing. It's one of the reasons I am waiting for relay and GraphQL.
EDIT: What all my stores look like in code.
var Events = require('events'), extend = require('deep_extend'),
EventEmitter = Events.EventEmitter,
CHANGE_EVENT = 'change';
var SomeStore = extend(EventEmitter.prototype, {
someGetter: function(){
return _someVar;
},
dispatchToken: AppDispatcher.register(function(action) {
switch(action.type) {
case 'SOME_ACTION':
_someVar = 'someValue'
break;
default:
return true;
}
SomeStore.emitChange();
return true;
})
});
return SomeStore;
stores seem to be one large object extended from every other store.
There must be some problem with how you extend from EventEmitter otherwise your code should be working fine.
Now that there are a few ways to do the same thing, here is how facebook implemented it in their official examples:
var assign = require('object-assign');
var EventEmitter = require('events').EventEmitter;
var TodoStore = assign({}, EventEmitter.prototype, {
...
UPDATE
Now looking at your code
extend(EventEmitter.prototype, {
is actually writing on the prototype itself, hence the errors you got. Instead you should be extending an empty object:
extend({}, EventEmitter.prototype, {
So this is my first Backbone project and I'm wondering if I'm doing things in the best way. My app basically has two states, one of them displays a search box and the other displays a search box with a table under it. My router has routes for searching and for the initial landing page with just the search view. When the user types in the query the router navigates to the search route and the table view is added to the page. This is my router:
app.Router = Backbone.Router.extend({
routes: {
'': 'index',
'search/coords=:address&age=:age&rad=:rad': 'search'
},
search: function(address, age, rad){
app.statusView || (app.statusView = new app.StatusView());
app.searchView || (app.searchView = new app.SearchView());
app.trigger('status:loading');
app.Practices.fetch({
reset: false,
success: function() {
app.searchView.setElement($('#search-box')).render();
var searchQuery = new app.SearchQueryModel({age: age, coords: address.split(","), radius: rad});
if (!app.tableView){
app.tableView = new app.TableView({model: searchQuery});
} else {
app.tableView.model = searchQuery;
app.tableView.refresh();
};
}
});
app.trigger('status:clear');
},
index: function() {
app.statusView = new app.StatusView();
app.searchView = new app.SearchView();
app.footerView = new app.FooterView();
app.searchView.setElement($('#search-box')).render();
}
});
As you can see my views are instantiated in the index route and then the same views are used when you search, unless the user is going directly to the search page in which case the views are instantiated there. I'd be surprised if this wasn't very sub-optimal because it seems clumsy to be checking if the view already exists in the search route. Is there a better way of doing things?
Lets say its not bad, but there is one better approach.
As for now you router is in charge of hook-up URL with app astatus and also for view and model control. The second may be detached from Router, so you will need Controller abstraction, but Backbone does not provide Controller "from the box".
But this is not the problem, you can use plugin or take a look at Controller realization in Marionette.js
The main idea here is to split responsibilities between app part correctly:
1) Router - keeps routes and hook up URL with controller action
2) Controller - manage views and models (create, delete, fetch and so on)
3) View - listen to model and DOM events and render data
4) Model - provide actual data and work with data.
First of all welcome to Backbone. It is a lovely framework which can allow you to make things as beautiful or ugly as you'ld like. Your question is about where view instantiation should be, in terms of good practices. Of course it seems sort of wrong to do it there as it violates the Law of Demeter by handling both url routing and view instantiation.
But the views have to be run from somewhere right? If not the router then where?
So I have two responses:
If your app is simple and you just want to play with backbone then you're probably going to be fine. A lot of people let single page app frameworks complicate otherwise simple apps. I'm not trying to be lazy, but where you have it now is the natural beginner's choice in Backbone. If this is your case then stop here.
If you want to use the full power of backbone to custom make a framework then read on.
So my setup is designed to be able to start a new project using some boilerplate functions and create only a few classes which are specific to the new app. Route handling and all of that kind of thing seems low-level enough to me that it should be just part of some configuration that I don't want to look at often. The upshot is that my router looks like this:
define([
'autorouter'
], function(AutoRouter){
var AppRouter = AutoRouter.extend({
autoRoutes: {
":page" : "routeDirect",
":page/:object" : "routeDirect",
":page/:object/:action" : "routeDirect",
"": "routeDirect"
}
});
return AppRouter;
});
Then for each new project I have a file where I keep the non-default routes, for instance:
define(function(require){
return {
"schedule" : require('screens/schedule')
, "logout" : require('screens/logout')
, "login" : require('screens/login')
, "create" : require('screens/create')
, "upload" : require('screens/upload')
, "select" : require('screens/selection')
, "inventory" : require('screens/inventory')
, "describe" : require('screens/description')
}
});
I put each screen into it's own file (using requirejs for the multi-file dependency management). The extra variables get passed through to the screen.
Each screen is the brain for a particular user experience and is responsible for loading views and maybe handling some events while that screen is active.
If that seems like an interesting setup then here is how I did it:
For the router itself I use a boilerplate class which I borrowed from Derick Bailey with some slight modifications:
define([
'jquery', 'underscore', 'backbone'],
function($, _, Backbone) {
var AutoRouter = Backbone.Router.extend({
constructor: function(options){
Backbone.Router.prototype.constructor.call(this, options);
var that = this;
that.app = options.app;
if (this.autoRoutes){
that.processAutoRoutes(options.app, that.autoRoutes);
}
},
processAutoRoutes: function(app, autoRoutes){
var method, methodName;
var route, routesLength;
var routes = [];
var router = this;
for(route in autoRoutes){
routes.unshift([route, autoRoutes[route]]);
}
routesLength = routes.length;
for (var i = 0; i < routesLength; i++){
route = routes[i][0];
methodName = routes[i][1];
method = app[methodName];
router.route(route, methodName, method);
}
}
});
return AutoRouter;
});
I never have to look at it, but I do need to pass it an app instance. For example:
this.appRouter = new AppRouter({app : this});
Finally my route direction function:
define(function(require){
var pathParser = function(path){
return Array.prototype.slice.call(path);
}
var pathApply = function(path, routes, context){
var pathArray = pathParser(path);
var primary = pathArray[0];
if (routes.hasOwnProperty(primary)){
routes[primary].apply(context, pathArray.slice(1));
} else {
routes["default"].apply(context, pathArray.slice(1));
}
}
return function(path){
//NOTE PLEASE that this references AutoRouter
//Which has an app property
var oApp = this.app;
var pathRoutes = _.extend(require('urls'), {
"default" : require('screens/default')
});
pathApply(arguments, pathRoutes, oApp);
};
});
So, did I make things better? Well if you're doing something very simple with just a screen or two, then you certainly don't want to build this sort of setup from scratch. But if you're like me, and you want to be able to quickly produce new projects then having some boilerplate like the two classes above allows for one JSON object to tell the app which routes I should send to which screens. Then I can have all of the logic in the appropriate places, allowing separation of concerns. Which is why I think Backbone is so pleasant.
My understanding of your problem is that you are triggering a route each time you are hitting search.
If this is how you are doing it, then use view events hash (used to capture and handle events that happen in a view) for search.Don't use routes. Define an events hash in the view and have a callback to handle the search.
var myAppEventBus = _.extend({},Backbone.Events);
var myAppController = {
function : search(options) {
// create an instance of the collection and do a fetch call passing the
// search parameters to it.
var searchResultsCollection = new SearchResultsCollection();
// pass search criteria, the success and error callbacks to the fetch
// method.
var that = this;
searchResultsCollection.fetch(
{
data:that.options,
success : function() {
// Pass the fetched collection object in the trigger call so that
// it can be
// received at the event handler call back
var options = {
"searchResultsCollection" : that.searchResultsCollection;
};
myAppEventBus.trigger("search_event_triggered",options);
},
error : function() {
// do the error handling here.
}
}
);
}
};
// Application Router.
var MyAppRouter = Backbone.Router.extend({
routes : {
'search/coords=:address&age=:age&rad=:rad': 'search'
},
search : function(searchParams) {
// Fetch the query parameters and pass it to the view.
var routeSearchExists = false;
var searchOptions = {};
var options = {};
if(searchParams) {
routeSearchExists = true;
// If search params exist split and set them accordingly in
// the searchOptions object.
options.searchOptions = searchOptions;
}
// Create and render the search view. Pass the searchOptions
var searchView = new SearchView(options);
searchView.render();
// Create and render an instance of the search results view.
var searchResultsView = new SearchResultsView();
searchResultsView.render();
// If there are search parameters from the route, then do a search.
if(routeSearchExists) {
searchView.search();
}
}
});
// The main view that contains the search component and a container(eg: div)
// for the search results.
var SearchView = Backbone.View.extend({
el : "#root_container",
searchOptions : null,
initialize : function(options) {
// Intialize data required for rendering the view here.
// When the user searches for data thru routes, it comes down in the
// options hash which can then be passed on to the controller.
if(options.searchOptions) {
this.searchOptions = options.searchOptions;
}
},
events : {
"search #search_lnk":"initSearch"
},
initSearch : function(event) {
event.preventDefault();
var searchOptions = {};
// Fetch the search fields from the form and build the search options.
myAppController.search(searchOptions);
},
search : function() {
if(this.searchOptions) {
myAppController.search(searchOptions);
}
}
});
// The view to display the search results.
var SearchResultsView = Backbone.View.extend({
searchResultsCollection : null;
initialize : function(options) {
// Handling the triggered search event.
myAppEventBus.on("search_event_triggered",this.render,this);
},
render : function(options) {
//search results collection is passed as a property in options object.
if(options.searchResultsCollection)
//Render your view.
else
// Do it the default way of rendering.
}
});
SearchView is the root view that contains the search component and a container like div to hold the search results.
SearchResultsView displays the result of a search.
When search option is clicked, the event callback (initSearch) gets the entered search data.
The search method on myAppController object is invoked and the search query is passed.
An instance of the search collection is created and fetch is invoked passing it the search query and also the success and error callback.
On success, a custom backbone event is triggered along with the fetched collection.
The callback(render method in SearchResultsView) for this event is invoked.
The callback renders the results of the search.
When loading in the router an instance for both the views can be created(the results view will be empty) and attached to the dom.
If you wish to search by multiple query strings at the url then I would suggest you to use the following route.
search?*queryString.
In the route callback make a call to a utility function the splits the querystring and returns you a search object and pass on the search string to the view.
Can someone help explain / provide an example on how to use the LayoutManager within the Backbone Bolierplate?
Within app.js I can see a useLayout function that extends the main app object. Within here it appears to be setting a base layout element:
// Helper for using layouts.
useLayout: function(name, options) {
// Enable variable arity by allowing the first argument to be the options
// object and omitting the name argument.
if (_.isObject(name)) {
options = name;
}
// Ensure options is an object.
options = options || {};
// If a name property was specified use that as the template.
if (_.isString(name)) {
options.template = name;
}
// Create a new Layout with options.
var layout = new Backbone.Layout(_.extend({
el: "#main"
}, options));
// Cache the refererence.
return this.layout = layout;
}
Is that correct? If so, do I somehow the use the 'UseLayout' function with the applications Router? ...to add different UI elements/nested views to the main view?
Thanks.
I will usually have an "app" object that stores all my settings needed throughout the application. This object then extends some useful functions like the one you listed above. For example:
var app = {
// The root path to run the application.
root: "/",
anotherGlobalValue: "something",
apiUrl: "http://some.url"
};
// Mix Backbone.Events, modules, and layout management into the app object.
return _.extend(app, {
// Create a custom object with a nested Views object.
module: function(additionalProps) {
return _.extend({ Views: {} }, additionalProps);
},
// Helper for using layouts.
useLayout: function(options) {
// Create a new Layout with options.
var layout = new Backbone.Layout(_.extend({
el: "#main"
}, options));
return this.layout = layout;
},
// Helper for using form layouts.
anotherUsefulFunction: function(options) {
// Something useful
}
}, Backbone.Events);
});
Now in my router I would do something like:
app.useLayout({ template: "layout/home" })
.setViews({
".promotional-items": new Promotions.Views.PromotionNavigation(),
".featured-container": new Media.Views.FeaturedSlider({
vehicles: app.vehicles,
collection: featuredCollection
})
}).render().then(function() {
//Do something once the layout has rendered.
});
I have just taken a sample from one of my applications, but I am sure you can get the idea. My main layout is basically just a layout template file which holds the elements so the views can be injected into their respective holders.
You would use it as if you're using a regular Backbone View. Instead of building the View directly, you can use this to create a new instance. The code you posted is a wrapper object on top of the Backbone Layout Manager extension with el: #main set as the default View element which is overridable.
var layout = new useLayout({ template: "#viewElement", ... });
In using Backbone.js, I've noticed that both views and models are given cids. I understand that if these classes are part of a collection, I can pull any of them by collection.getByCid. Is it at all possible to pull any class, outside of a collection, given its cid, using Backbone?
For example, if I have MyObject.Views.Tree = Backbone.View.extend({ });, I can create a new Tree view from var tree = new MyObject.Views.Tree();. Calling tree.cid returns a specific cid--something like view231. Is there any way to reference my tree view given only its cid? A global Backbone.getByCid method, perhaps?
ExtJS spoiled me and I felt the need to recreate something similar for Backbone. Maybe this will help you out too? I haven't tested it too much, but it's a very simple change. Just be careful of creating lots of things and not removing them, or you'll have a bunch of registered objects eating up memory.
Backbone.View.Registry = {
items: {},
register: function (id, object) {
this.items[id] = object;
},
lookup: function (id) {
return this.items[id] || null;
},
remove: function (id) {
delete this.items[id];
}
}
Backbone.RegisteredView = Backbone.View.extend({
initialize: function () {
Backbone.View.prototype.initialize.apply(this);
this.cid = this.options.id || this.cid; //just in case you want to assign a unique ID and lookup from that.
Backbone.View.Registry.register(this.cid, this);
},
remove: function () {
Backbone.View.prototype.remove.apply(this);
Backbone.View.Registry.remove(this.cid);
return this;
}
});
test = Backbone.RegisteredView.extend({
intialize: function () {
return $("<div></div>"); //Just return something for this example
}
});
div1 = new test({id: 'header_pane'});
div2 = new test();
console.log(Backbone.View.Registry.items); //Will have the header_pane and a cid based obj in it
ref = Backbone.View.Registry.lookup('header_pane');
console.log(ref); //Will be the header_pane object
div1.remove();
console.log(Backbone.View.Registry.items); //Will only have the cid based object in it
No.
I think you have a slight misunderstanding of the backbone programming model, as well as JavaScript in general. Backbone doesn't keep track of what you create; it only helps you create objects with specific prototypes (Models, Collections, etc.). It doesn't care at all what you do with them. The CID is just a convenience method you can use for indexing and cross-referencing, but you have to write the indices and cross-references yourself.
So if you create an object and don't keep a reference to it somewhere (in a collection, in your router, in another object), it becomes inaccessible and the JavaScript VM will eventually garbage collect it.