I'm a little confused with backbone js.
I have multiple views that act differently based on session. ie. When I'm logged in all my views will allow me to like a comment and follow an issue but when i'm logged out, all the other views corresponding to the user activity should prompt a login form.
So, the confusion is about, how should I notify other views when I'm logged in so that they allow me to do the corresponding activities.
I'm currently able to achieve this but I need to refresh the page after I log in but then the purpose of backbone js is not fulfilled.
User model: user.js.coffee
class window.User extends Backbone.Model
urlRoot: '/users'
isSignedIn: ->
Boolean(#get('remember_token'))
login: (attributes, options) ->
options.url = Root + '/sessions'
#save(attributes, options)
signup: (attributes, options) ->
options.url = Root + '/users/create'
#save(attributes, options)
Sign in view: signin_view.js.coffee
class window.UserView extends Backbone.View
initialize: ->
_.bindAll(this, 'onSignedIn', 'onSignedUp', 'onCommented')
$(#el).show()
if #model.isSignedIn()
#showUserDetails()
else
Some code here
User details view: user_detail_view.js.coffee
class window.UserDetailsView extends Backbone.View
initialize: ->
_.bindAll(this, 'onValidUser')
#model.on('change', #onValidUser)
if (#model.get('email'))
#onValidUser()
else
#model.fetch()
onValidUser: ->
#render()
render: ->
$(#el).show()
this.$(".name").text(currentUser.get('user')['first_name'] + ' ' + currentUser.get('user')['last_name'])
Now I want to notify my follow view that im logged in and I should not prompt for sign in form anymore, in fact all the views related to user activity
Follow view looks like this
class window.FollowView extends Backbone.View
initialize: ->
$(#el).show()
How do I achieve this ?
Thanks in advance
I think you have a bit of a structural problem. The "signed-in-edness" isn't really a property of a user, it is a property of the application as a whole: if they're not signed in then the application has no user, if they are signed in then the application does have a user.
So you should have an application object that has a user property. When the person signs in, set the user on the application object; when the person signs out, clear the user.
Once the above is in place, the solution naturally falls out: we're using Backbone so everything is based on triggering and listening for events and the events here are signed-in and signed-out. What triggers these events? The application object does. Who listens for these events? The views that care if someone is signed or not.
Suppose you have an application class like this:
class App
constructor: (#user_name) ->
_(#).extend(Backbone.Events);
sign_out: ->
#user_name = null
#trigger('signed-out')
sign_in: (user)->
#user_name = user
#trigger('signed-in')
user: ->
#user_name
window.app = new App
Then views that cared about the user could do things like this:
initialize: ->
app.on('signed-in signed-out', #render)
and their render methods would be called when someone signed in or out; the render methods would, of course, check for the user and display the appropriate things.
Live Demo: http://jsfiddle.net/ambiguous/TfPf9/
Real code might want to have remove implementations to app.off('signed-out signed-in') to avoid dangling listeners.
And a couple other things while I'm here:
Using bindAll is rarely necessary with CoffeeScript. Instead of this:
_.bindAll(this, 'onSignedIn', 'onSignedUp', 'onCommented')
define the methods with =>:
onSignedIn: => #...
onSignedUp: => #...
Newer versions of Backbone give you a jQuery/Zepto wrapped version of your el automatically so you can replace $(#el).show() with just #$el.show().
Related
So I'll start by saying I'm a C# .Net/Javascript developer with a lot of experience, but I have zero experience with Salesforce. Never ever seen it before today. So, I've been asked by another team to add a custom button to a Contact object, which when clicked calls an external API and updates a custom field in the Contact with the response. It was pitched as "just write some Javascript that calls an API when a button is clicked, it's literally embedded into the page, 15 minute job...".
Following what appears to be quite an outdated document, I've ended up in the Object Manager, selected the Contact object and I'm into the Buttons, Links and Actions page. I'm assuming before this was done using the Execute Javascript behaviour, which in the Lightning version I'm advised against using. So after much Googling I've read about APEX classes, Visualforce Components, Lightning Components, the Salesforce REST API, etc, etc. Not a 15 min job.
Essentially the requirements are to embed a button (or action, or..?) into this Contact page, so that when the sales guy clicks it, it gathers some of the Contact's details and uses them to form an API call to an external service. The data will be used to form a response, which must then be read (as JSON, then parsed) and written into a custom field on the Contact.
What would be the best approach for developing a solution? In the Apex Debug environment I've put together the code to call the API and parse the JSON response, I'm assuming I need to wrap this in an Apex class, with a method that calls this code and returns the response. What I'm not sure of is how to call into this from the button, and update the field in the Contact.
Do I need to do all that from the Apex Class method? passing in a reference to the Contact, or is there another component that needs to sit in between and do this.
Am I right in assuming I'll need to use the Salesforce API to update the Contact?
Any pointers appreciated.
Oh man. It can be a 15 min job but it's definitely "easy when you know how" or have some examples ;)
What's your user interface, Classic or Lightning? Do they have plans to migrate to Lightning soon? I'm assuming it's Lightning if you figured out the "Execute JavaScript" hacks are passé.
Do you care where the button/action will be? Is the top right corner with all other buttons fine or do you want it to be droppable to pretty much any area in the page?
Does the API callout need username, password, maybe certificate? It'll determine whether you need just to whitelist the endpoint on firewall (Setup -> Remote Site Settings) or you'll need something more advanced (Setup -> Named Credentials).
Do you have SFDX command line (CLI), VSCode / are determined to install some tooling? The Lightning Web Components are cutting edge, most sleek etc but you can't create them straight in the browser (at least not yet), you need tooling. Visualforce is OK but nothing special for this use case, Aura components are bit clunky to write - but you can do both in Developer Console without extra tooling.
Parsing the JSON response - depends how complex it is, you can hand-craft parser with JSON.deserializeUntyped() but life's too short for this. Here's nice apex code generator similar to what you'd get from parsing WSDL: https://json2apex.herokuapp.com/
We'll try to do Aura component way. It's ugly, LWC is future but hey, it'll get you started.
Go to Setup -> Remote Site Settings and add new entry with https://en.wikipedia.org/
Create new Apex class:
public with sharing class Stack63364119 {
static final String endpoint = 'https://en.wikipedia.org/w/api.php?action=query&format=json&list=search&srsearch=';
#AuraEnabled
public static String doCallout(Id contactId){
if(contactId == null){
throw new MyException('Missing record id');
}
List<Contact> contacts = [SELECT MailingCountry FROM Contact WHERE Id = :contactId];
if(contacts.isEmpty() || String.isBlank(contacts[0].MailingCountry)){
throw new MyException('Could not find the contact');
}
Contact c = contacts[0];
HttpRequest req = new HttpRequest();
req.setEndpoint(endpoint + c.MailingCountry);
req.setMethod('GET');
HTTPResponse res = new Http().send(req);
System.debug(res.getStatus());
System.debug(res.getBody());
// no special parsing, just chuck it into Description field
// no error handling
if(res.getStatusCode() == 200){
c.Description = res.getBody().abbreviate(32000);
update c;
}
return res.getBody();
}
public class MyException extends Exception{}
}
Make new "Lighning Component" in developer console (it'll be Aura, not LWC). You can tick the last checkbox about "lightning quick action". Name can be same as class but doesn't have to be.
For component (~ html part) paste this
<!-- Loosely based on https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/controllers_server_actions_call.htm -->
<aura:component controller="Stack63364119" implements="force:hasRecordId,force:lightningQuickAction" >
<!-- in the name of all that is holy do not name the JS function same as the Apex class function, it'll give you very cryptic errors to debug -->
<aura:handler name="init" value="{!this}" action="{!c.runCallout}"/>
</aura:component>
For controller (~ JavaScript) paste this
({
runCallout : function(cmp) {
let action = cmp.get('c.doCallout');
action.setParams({contactId : cmp.get('v.recordId')});
action.setCallback(this, function(response){
let state = response.getState();
if (state === "SUCCESS") {
alert('Saved OK: ' + response.getReturnValue());
$A.get("e.force:closeQuickAction").fire(); // if you want to self-close
} else if (state === "ERROR") {
var errors = response.getError();
if (errors) {
if (errors[0] && errors[0].message) {
console.log("Error message: " + errors[0].message);
}
} else {
console.log("Unknown error");
}
}
});
$A.enqueueAction(action);
}
})
Finally go Object Manager -> Contact -> Buttons Links and Actions. Create new Quick Action.
And add it to page layout(s)!
It should get you started. Maybe you'll decide to split it a bit, Apex would only do the callout, return results to UI and if user is happy - updating the contact can be done with one of these: https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/data_service_save_record.htm. Separation of concerns blah blah (but will the user be happy with 2 clicks).
P.S. If you tweak it and it dies but it's hard to see any JavaScript errors - it'll be because default is to run in release mode, SF rewrites your source code a bit, optimises, polyfills for "browsers" like IE11... Go to Setup -> Debug Mode and enable for your user.
P.P.S. In sandbox / dev org it's good idea to go Setup -> Session Settings -> and untick "Enable secure and persistent browser caching to improve performance". Your component will be always fresh, saves some frantic hitting Ctrl+R. Don't do it in prod ;)
I will write to you my approach
Button on the record page/page layout -> lighting component or flow -> Apex class for collecting data -> apex class request & response API(don't forget to add the endpoint to remote site settings)-> parse response and update contact (you can use queries and DML operations inside Apex)
lighting component it will be very simple just have a the apex class as the controller example
<aura:component implements="force:appHostable,lightning:isUrlAddressable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" controller="contactController" access="global" >
<aura:handler name="init" value="{!this}" action="{!c.fetchContact}"/>
</aura:component>
controller
({
fetchContact : function(component, event, helper) {
helper.fetchContactHelper(component, event, helper);
}
})
helper
({
fetchAccHelper : function(component, event, helper) {
var action = component.get("c.fetchContacts");
action.setParams({
});
action.setCallback(this, function(response){
var state = response.getState();
if (state === "SUCCESS") {
}
});
$A.enqueueAction(action);
}
})
assuming is apex function is fetchContacts and class contactController
OK, I'm stuck on what should be a basic task in ExtJs. I'm writing a simple login script that sends a user name and password combination to a RESTful web service and receives a GUID if the credentials are correct.
My question is, do I use a Model Proxy or a Store Proxy?
To my understanding, Models represent a single record, whereas Stores are for handling sets of data containing more than one record. If this is correct then it would seem that a Model proxy is the way to go.
Following Sencha's documentation at http://docs.sencha.com/extjs/4.2.1/#!/api/Ext.data.Model the code would look something like this:
Ext.define('AuthenticationModel', {
extend: 'Ext.data.Model',
fields: ['username', 'password'],
proxy: {
type: 'rest',
url : '/authentication'
}
});
//get a reference to the authentication model class
var AuthenticationModel = Ext.ModelManager.getModel('AuthenticationModel');
So far everything is OK, until the next step:
//Use the configured RestProxy to make a GET request
AuthenticationModel.load('???', {
success: function(session) {
console.log('Login successful');
}
});
The load() method for the Model class is a static call expecting a single unique identifier. Logins typically depend upon two factors, username and password.
So it appears Store proxies are the only way to validate someone's username and password credential combination in ExtJS. Can someone verify and explain? Any help to understand this would be greatly appreciated.
You just need to know the following:
The store will use it's own proxy if you configured one for this
instance and if not he takes the proxy from the model.
So you can easily go with two proxy configurations to enable the multi-CRUD operations on the store and the single-CRUD operations on the Models. Note the the static load method of the Model expects the model id because it is supposed to load a model by just one Id (yes, composite keys are not supported). You will also have to fetch the model instance in the callback (As you did).
Back to your Username/password problem
You may apply your session Model with a custom 'loadSession' method
loadSession: function(username,password, config) {
config = Ext.apply({}, config);
config = Ext.applyIf(config, {
action: 'read',
username: username,
password: password
});
var operation = new Ext.data.Operation(config),
scope = config.scope || this,
callback;
callback = function(operation) {
var record = null,
success = operation.wasSuccessful();
if (success) {
record = operation.getRecords()[0];
// If the server didn't set the id, do it here
if (!record.hasId()) {
record.setId(username); // take care to apply the write ID here!!!
}
Ext.callback(config.success, scope, [record, operation]);
} else {
Ext.callback(config.failure, scope, [record, operation]);
}
Ext.callback(config.callback, scope, [record, operation, success]);
};
this.getProxy().read(operation, callback, this);
}
Now call this instead of load.
I found it in the documentation of sencha App Architecture Part 2
Use proxies for models:
It is generally good practice to do this as it allows you to load and
save instances of this model without needing a store. Also, when
multiple stores use this same model, you don’t have to redefine your
proxy on each one of them.
Use proxies for stores:
In Ext JS 4, multiple stores can use the same data model, even if the
stores will load their data from different sources. In our example,
the Station model will be used by the SearchResults and the Stations
store, both loading the data from a different location. One returns
search results, the other returns the user’s favorite stations. To
achieve this, one of our stores will need to override the proxy
defined on the model.
i am very confuse about using backbone.js model fetch method. See the following example
backbone router:
profile: function(id) {
var model = new Account({id:id});
console.log("<---------profile router-------->");
this.changeView(new ProfileView({model:model}));
model.fetch();
}
the first step, the model account will be instantiated, the account model looks like this.
define(['models/StatusCollection'], function(StatusCollection) {
var Account = Backbone.Model.extend({
urlRoot: '/accounts',
initialize: function() {
this.status = new StatusCollection();
this.status.url = '/accounts/' + this.id + '/status';
this.activity = new StatusCollection();
this.activity.url = '/accounts/' + this.id + '/activity';
}
});
return Account;
});
urlRoot property for what is it? After model object created, the profileview will be rendered with this this.changeView(new ProfileView({model:model}));, the changeview function looks like this.
changeView: function(view) {
if ( null != this.currentView ) {
this.currentView.undelegateEvents();
}
this.currentView = view;
this.currentView.render();
},
after render view, profile information will not display yet, but after model.fetch(); statement execute, data from model will be displayed, why? I really don't know how fetch works, i try to find out, but no chance.
I'm not entirely sure what your question is here, but I will do my best to explain what I can.
The concept behind the urlRoot is that would be the base URL and child elements would be fetched below it with the id added to that urlRoot.
For example, the following code:
var Account = Backbone.Model.extend({
urlRoot: '/accounts'
});
will set the base url. Then if you were to instantiate this and call fetch():
var anAccount = new Account({id: 'abcd1234'});
anAccount.fetch();
it would make the following request:
GET /accounts/abcd1234
In your case there, you are setting the urlRoot and then explicitly setting a url so the urlRoot you provided would be ignored.
I encourage you to look into the Backbone source (it's surprisingly succinct) to see how the url is derived: http://backbonejs.org/docs/backbone.html#section-65
To answer your other question, the reason your profile information will not display immediately is that fetch() goes out to the network, goes to your server, and has to wait for a reply before it can be displayed.
This is not instant.
It is done in a non-blocking fashion, meaning it will make the request, continue on doing what it's doing, and when the request comes back from the server, it fires an event which Backbone uses to make sure anything else that had to be done, now that you have the model's data, is done.
I've put some comments in your snippet to explain what's going on here:
profile: function(id) {
// You are instantiating a model, giving it the id passed to it as an argument
var model = new Account({id:id});
console.log("<---------profile router-------->");
// You are instantiating a new view with a fresh model, but its data has
// not yet been fetched so the view will not display properly
this.changeView(new ProfileView({model:model}));
// You are fetching the data here. It will be a little while while the request goes
// from your browser, over the network, hits the server, gets the response. After
// getting the response, this will fire a 'sync' event which your view can use to
// re-render now that your model has its data.
model.fetch();
}
So if you want to ensure your view is updated after the model has been fetched there are a few ways you can do that: (1) pass a success callback to model.fetch() (2) register a handler on your view watches for the 'sync' event, re-renders the view when it returns (3) put the code for instantiating your view in a success callback, that way the view won't be created until after the network request returns and your model has its data.
I have a problem when running with Meteor.
I have a "question" page which I want to increase the count view whenever it is rendered.
So in my template function I write
Template.questionview.helpers({
question : function() {
if(Session.equals('main_template_name', 'question')) {
console.log(Session.get('question_id'));
Questions.update({
_id: Session.get('question_id')
}, {
$inc: {
views: 1
}
});
}
});
Now here comes the problem, when I render the question view and update the question item, the view is refreshed again because it is a reflective page. And then it comes infinity loop.
Anyone has suggestions?
Typically, in situations like this, there is something broken about the model. In this case, I believe it's the "count view" idea. There are lots of ways to do this correctly. Incrementing it on render is not, since you're doing model work in UI code (broken conceptually and in implementation).
First, store the questions the user has visited somewhere. Why not a {questionsVisited:[]} property on the user?
Use a Meteor.call(...) method call to register a view instead:
Meteor.methods({
viewQuestion: function(questionId) {
// check if the user hasn't visited this question already
var user = Meteor.users.findOne({_id:this.userId,questionsVisited:{$ne:questionId}});
if (!user)
return false;
// otherwise, increment the question view count and add the question to the user's visited page
Meteor.users.update({_id:this.userId},{$addToSet:{questionsVisited:questionId}});
Questions.update({_id:questionId},{$inc:{views:1}});
return true;
});
So how about incrementing the view on UI changes? Well, let's not do that specifically. Let's increment the view count only when the question changes.
Meteor.autorun(function () {
var questionId = Session.get("question_id");
Meteor.call('viewQuestion',questionId,function(e,r) {
if (r)
console.log("Question " + questionId + " logged an increment.");
else
console.log("Question " + questionId + " has already been visited by user " + Meteor.userId();
});
});
And get rid of all this question helper stuff...
This is even better than what you originally wanted. Now views aren't counted twice for the same user. If that is the desired behavior, remove the questionsVisited logic.
Only change the 'question_id' session variable when you actually change the logical question the user is working with.
I solved this problem by using meteor-collection-hooks
First install it
>_ meteor add matb33:collection-hooks
Then in your model
Questions.after.findOne(function (userId, selector, options, doc){
Questions.update({_id: doc._id},{$inc:{views:1}});
});
And KABOOM Thats it
Rather than put in a helper, I would put this logic into the rendered event, ie.
Template.questionview.rendered ...
See the Meteor docs.
If you are using publish and subscribe ( you definitely should anyway) you can do it on your publish method like
Meteor.publish('posts', function(id) {
Posts.update({_id:id},{$inc:{view:1}});
return Posts.find({_id: id});
});
or on your subscribe callback
Meteor.subscribe("posts" , id {
onReady: function() { Meteor.call("incrementView", id);}});
this way you only increment the count once each time users open the browser.
This may be more of a question around design pattern - I hope it makes sense.
I am using backbone - am developing a relatively simple app where user can add requests (where the request model is simply heading, description and price). The only requirement to add the requests is that a user is 'logged in'.
However I wish that the user 'adds' the request before checking if the user is logged in (remove one barrier). By that I mean that the user fills the form in, if not registered they have to register and then the request is just sent, if they were logged in it would just be sent. So initially the 'add request' view is rendered, when save is triggered this will call the save on the model which calls the ajax request on the server. The response will either return true (the user was logged in and the request added) or false (the user was not logged in).
So assuming that the user was not logged in - then I would wish to render a new view 'register' which has the option for the user to switch to 'login' (another view). So User in theory could go from
Request (save) -> Register -> Login -> Request (save)
So presuming that the user then registered (filled in the form which then called the registers view save method which then called the registers model save and returned ok). I would then wish to once again call the 'request' save method once again (as now the user would be logged in). However I do not want the register/login tied to the Request view - as in theory a new view (lets say I had a sent message view) would want similar functionality e.g. try and make the request, if failed switch view to register - perform save and then call some callback.
So the question is what is the right way to do this?
1) Do I create some closure inside the request view referencing the 'save' function and store it in a global to be called by register/login onsuccess?
2) Create a closure as above and pass that to the register etc (and if so how would I pass this given register/login is a view).
3) Pass a reference to 'this' of the request view?
So far all I have is, so in the else I would render the 'register' view but would love to know best way to do this without it getting very ugly?
save : function(event){
if(this.model.isValid() == true) {
this.$("#general_error").hide();
this.model.set({'formattedPrice' : TB_H.formatPrice(this.model.get('currency'), this.model.get('price'))});
self = this;
this.model.save(this.model.toJSON(), {
success: function(m, y) {
if(y.status == true) {
self.model = new TB_BB.Request();
Backbone.ModelBinding.bind(self);
Backbone.Validation.bind(self);
$('#add-offer-button').show();
} else {
if(y.errors[0] == 'not logged in') {
this.$("#general_error").html('You are not logged in');
this.$("#general_error").show();
} else {
_.each(y.errors, function(key, val) { this.$("#general_error").html(key) });
this.$("#general_error").show();
}
}
}, error : function(m,y) {
this.$("#general_error").show();
this.$("#general_error").html("Something bad happened - please try again")
}
}
);
}
Greatly appreciate any help!
I noticed this after asking a similar question, which I just deleted. I'm thinking this isn't the way most people are doing it in backbone.
what I did instead was pass my different routes to the same method and if the ids were not null, I'd call the route.
So I have a view
base_view = Backbone.Views.extend({
initialize: function(id,a_id,b_id){
this.id = id;
this.a_id = a_id;
this.b_id = b_id;
Myapp.data = new Myapp.Model.Base();
Myapp.data.url = '/data_url/'+id;
Myapp.data.fetch(Myapp.data, {
success: function(response){
// i have some nested collections, and models so i fill those in here
Myapp.mainModel = new First_Child_Collection(response.attributes.first_child_array);
}, error: function(){
alert('oops couldn't get data');
}
});
Myapp.data.bind("fetched",this.render,this);
},
render: function(){
new Main_View();
new Sub_View_1(this.id);
new Sub_View_2(this.a_id);
new Sub_View_3(this.b_id);
}
});
Then in my routes, rather than having a new route for each, I have
routes: {
"app/new": "new",
"app/:id/edit/a/:a_id/b/:b_id": "edit"
}
edit {
new base_view(id,a_id,b_id);
}
I'm not sure if this is perfect, but I think it is DRY'r than the other options. I just check that a_id or b_id are not undefined before passing those views.
Hope it helps.