Upgrading a Model (backbone.js) - javascript

Right now I have a generic User model for all users. When a user logs it's determined wether they are user type 1 or user type 2. These two types need totally different models to represent them but still include everything found in the generic User model.
My goal is up upgrade the User model for each type of user by passing the state from the current User model into the Type1 or Type2 model.
(in coffeescript)
class Type1 extends User
#add super set of methods
#user arrives
user = new User
#after logging in
state = user.toJSON()
#do I need to unbind/delete the current user model?
user = new Type1(state)
Is this the best way to achieve this?
Thanks!

You could do that. You could also just copy over the attributes:
userCopy = new Type1
userCopy.attributes = user.attributes
delete user
Note that with either approach, you'll lose event bindings, etc. A safer, more JavaScript-y approach would be to just extend user in-place with additional methods, rather than having a Type1 class at all. (Of course, there are downsides to that to, such as losing the ability to invoke super from those methods. See my answer to a similar question here.)

Related

How to structure the Model for Redux ORM

I'm getting the response similar to the following format from server.
{"channels": [{"name":"discovery", "id":"12",
"details":{"src":"link", "logo":"imagelink"}}]
I'm planning to use Redux-Orm to manage the state in the store. When I'm trying to define the model, I'm having confusions. One way is to define Channel Model with name and id as attributes, details as one to one mapping and Details Model with src, logo attributes as below.
const channel = class Channel extends Model {};
channel.fields = {
name: attr(),
id: attr(),
details: oneToOne('details', 'channels')
}
const details = class Details extends Model {};
details.fields = {
src: attr(),
logo: attr()
}
Or Should I define a single model class which represents the response as is? If so, how to define and access it?
If you want to have a Detail model, your backend must identify it with an id like the Channel model, and then you may do a oneToOne relation.
That being said, using a single model or two is totally depending on how they'll interact in your app, and may grow. If your details field won't grow much more, my totally personal point of view would be to keep it in a single Channel model. you'd access it through channel.details or channel.details.src transparently.
IMO, oneToOne simple relation like that does not need a specific model.

Proper way to bind to data object in Angular 2 service?

I am building an angular 2 application. The documentation has changed quite a bit since the released which has caused confusion. The best I can do is explain what I am trying to do (Which was easy in Angular 1) and hope someone can help me out.
I have created a login service using JWT's.
Once login is successful, I return a user object.
I have a loginComponent ( binds data to template ) and loginService ( which handles the https calls )
I have a userService which maintains the user object.
I have a userComponent which renders the user data.
The problem is, once the user has logged in, I am unclear on the best approach for letting the userService retrieve the new data in an object called "user", then the userComponent update its user object on the template. This was easy in angular 1 simply by putting a watcher on the userService.user object.
I tried Inputs and Outputs to no avail, eventEmitters, Observables and getters and setters. The getters and setters work, but force me to store everything in a "val()"
Can someone please tell me the best way to achieve this?
User Component renders template with user.firstName, user.lastName etc.
Initially user if an empty Object
The login service needs to set the UserService.user
The userComponent Needs to detect the change and update the DOM.
Thanks in ADVANCE!
If I'm not wrong, you are looking for a way to 'listen' to changes in your UserService.user to make appropriate updates in your UserComponent. It is fairly easy to do that with Subject (or BehaviorSubject).
-In your UserService, declare a property user with type Subject<User>.
user: Subject<User> = new Subject();
-Expose it to outside as observable:
user$: Observable<User>
...
this.user$ = this.user.asObservable();
-Login function will update the private user Subject.
login(userName: string, password: string) {
//...
this.user.next(new User("First name", "Last name"));
}
-In your UserComponent, subscribe to UserServive's user$ observable to update view.
this.userService.user$.subscribe((userData) => {this.user = userData;});
-In your view, simply use string interpolation:
{{user?.firstName}} {{user?.lastName}}
Here is the working plunker: http://plnkr.co/edit/qUR0spZL9hgZkBe8PHw4?p=preview
There are two rather different approaches you could take:
1. Share data via JavaScript reference types
If you create an object in your UserService
#Injectable()
export class UserService {
public user = new User();
you can then share that object just by virtue of it being a JavaScript reference type. Any other service or component that injects the UserService will have access to that user object. As long as you only modify the original object (i.e., you don't assign a new object) in your service,
updateUser(user:User) {
this.user.firstName = user.firstName;
this.user.lastName = user.lastName;
}
all of your views will automatically update and show the new data after it is changed (because of the way Angular change detection works). There is no need for any Angular 1-like watchers.
Here's an example plunker.
In the plunker, instead of a shared user object, it has a shared data object. There is a change data button that you can click that will call a changeData() method on the service. You can see that the AppComponent's view automatically updates when the service changes its data property. You don't have to write any code to make this work -- no getter, setter, Input, Output/EventEmitter, or Observable is required.
The view update automatically happens because (by default) Angular change detection checks all of the template bindings (like {{data.prop1}}) each time a monkey-patched asynchronous event fires (such as a button click).
2. "Push" data using RxJS
#HarryNinh covered this pretty well in his answer. See also Cookbook topic Parent and children communicate via a service. It shows how to use a Subject to facilitate communications "within a family".
I would suggest using a BehaviorSubject instead of a Subject because a BehaviorSubject has the notion of "the current value", which is likely applicable here. Consider, if you use routing and (based on some user action) you move to a new route and create a new component, you might want that new component to be able check the "current value" of the user. You'll need a BehaviorSubject to make that work. If you use a regular Subject, the new component will have no way to retrieve the current value, since subscribers to a Subject can only get newly emitted values.
So, should we use approach 1. or 2.? As usual, "it depends". Approach 1. is a lot less code, and you don't need to understand RxJS (but you do need to understand JavaScript reference types). Approach 2. is all the rage these days.
Approach 2. could also be more efficient than 1., but because Angular's default change detection strategy is to "check all components", you would need to use the OnPush change detection strategy and markForCheck() (I'm not going to get into how to use those here) to make it more efficient than approach 1.

Form validation when using flux

I am using flux in my application where I use Backbone.View as the view layer.
Generally there is a store instance for the whole page, the store save the data(or the state) of the application, and the view will listener to the change event of the store, when the store trigger a change event, the view will re-render itself accordingly.
So far so good, however I meet some problems when I use the form, when use try to submit the form or a blur event triggered for an element, I want to validate the input in the server and display the errors as soon as possible, this is what I have done:
when user hit the submit button or value changed for an element,I will dispatch an action like:
dispatch({type:"validate",value:"value"});
The store will respond to this action and send request to server
When the response get back,I will update the store and trigger the change event:
store.validate_response=response;
store.trigger("change");
The View(form in the example) will re-render itself.
I can display the errors but I can not keep the value of the element since the elements in the form are re-rendered which means they will display the origin value rather than the value the user typed.
I have thought that save the typed values too when dispatch the validate action like this:
dispatch({type:"validate",value:"value",userTypedValueForEveryElement:"....."});
It works when use hit the submit button, since generally when they hit the button they will not type anything in the form, but how about this situation:
<input type="text" id="A" />
<input type="text" id="B" />
User type avalue in input A, then type bv in input B, at the same time I will do the validation, and send both the value when dispatch the action:
{a:"avalue",b:"bv"}
The store will keep these values.
And during the request, user keep typing for element B, now the value is bvalue, and at the same time the validation response returned, then the form will re-render, and it will set avalue for A and bv for B, this is the point, the value of the B is lost, user will be surprised, they do not know what happened.
Any idea to fix that?
It seems that the flux manner:
view trigger action -->
store respond to actions -->
store trigger changed -->
view respond to store(re-render in most case) -->
view trigger action"
make this kind of requirement complex than that before. You will have to do more extra job to keep the state of the view once there are to much interactive for your view.
Is this true or beacuse I miss anything?
It sounds like you have a few different issues in play here, but they're all solvable. This is a little long, but hopefully it addresses all the issues you're running into.
Store design: First, what information is your Store actually meant to hold? Try not to think of a Flux store like you would a Backbone Model, because their purposes aren't quite the same. A Flux store should store part of an application's state (not necessarily part of a UI component's state), and shouldn't know or care about any views using it. Keeping this in mind can help you put behavior and data in the right places. So let's say your store is keeping track of the user's input into a specific form. Since your application cares about whether input is valid or not, you need to represent that in the store somehow. You could represent each input as an object in the store, like {val: 'someInput', isValid: false}. However you store it, it has to be there; any part of your app should be able to pull data from the store and know what input is valid/invalid.
I agree with #korven that putting lots of application logic in Stores is a poor choice. I put my AJAX calls into the action creation logic, with AJAX response callbacks creating the actual actions on the Dispatcher; I've seen this recommended more than once.
Preserving user input: For one, you only want to render the form inputs when the user has finished typing - otherwise, the render will change the text as they're typing it. That's easy enough -- throttle or debounce (debounce is probably preferable here) the input validation handler for user input events. (If you're using focus or blur events, timing is less likely to be an issue, but you should still consider it.) Have the store update only after validation is done. And, of course, only render when the store updates. So we only modify an input's value in the DOM when a user has stopped typing and we have validated their input.
Even with throttling/debouncing, since the validation requests are async and the user could (potentially) trigger many validation requests in a short period of time, you can't rely on the responses coming back in order. In other words, you can't process each response as they come back; if they come back out of order you'll overwrite recent input with old input. (I've run into this in real life. It may be an edge case for you but when it happens the bug will be confusing enough that it's worth addressing up front.) Fortunately, we only care about the most recent thing the user typed. So we can ignore all responses to our validation requests except the response for the most recent request. You can easily integrate this logic with whatever makes the requests by keeping track of a 'key' for each request. Here's an example of how I've solved this:
// something in your view
this.on(keyup, function() {
var input = this.getUserInput();
validationService.validate(input);
}
// within validationService
validate: function(input) {
// generate random int between 1 and 100
var randKey = Math.floor(Math.random() * (100 - 1)) + 1;
this.lastRequestKey = randKey;
this.doAjaxRequest({
data: {input: input},
callback: function() {
if (randKey !== this.lastRequestKey) {
// a newer request has modified this.lastRequestKey
return;
}
// do something to update the Store
});
}
In this example, the object responsible for the validation service 'remembers' only the most recently set 'key' for a request. Each callback has its original key in scope thanks to its closure, and it can check if its original key equals the one set on the service object. If not, that means another request has happened, and we no longer care about this response. You'll want the 'keys' to be set per-field, so that a new request for field B doesn't override an older request for field A. You can solve this in other ways, but the point is, discard all but the last request's response for any given input. This has the added bonus of saving some update/render cycles on those discarded responses.
Multiple fields rendering: When you're doing Flux right, you should never 'lose' data because all changes come from the Dispatcher/Store, and because the Dispatcher won't send a new update to stores until the previous update is completely finished. So as long as you update the Store with each new input, you won't lose anything. You don't have to worry about a change to input B causing you to lose a change to input A that was in progress, because the change to input A will flow from the Dispatcher to the Store to the View and finish rendering before the Dispatcher will allow the change to input B to begin processing. (That means renders should be fast, as they'll block the next operation. One of the reasons React goes well w/Flux.)
As long as you put everything into the store -- and don't put the wrong thing into the store, which the input and async handling stuff above addresses -- your UI will be accurate. The Flux pattern treats each change as an atomic transaction that's guaranteed to complete before the next change occurs.
When writing my react application I faced the exactly same issue. As a result I end up writing a small library to achieve the same.
https://www.npmjs.com/package/jsov
all you need to do is this, as soon as store triggers the change with the data they typed. There will be an onChange function in your component that will be listening to this change from store (and probably setting the state) now what you would do here is before setting the state use
onChange:function(){
var validated_response=JsOV.schemaValidator(Schema,Store.getResponse());
this.setState({data:validated_response});
}
P.S: To save the pain I have also provided a schema generator function in the library. It takes a dummy response and generates the schema boilerplate, to which you can add your own validations.

How to inject state parameter automatically

Abstract
Hi, I'm using angular + ui-router in my project, I have huge amount of nested states and different views that in turn contain huge amount of different inputs, a user fills these inputs incrementally step by step.
The problem
Sometimes users require additional info that is located on the previous step, and browsers "back" button helps the user to sneak peek into that data, but as soon as the user presses it, the info he already entered is lost due to state transition, which is obviously a bad thing.
Strategy
In order to overcome described problem I have the following plan:
Associate each user's "navigation" (guess this is a proper term) with a random id
To prevent scope-inheritance-and-serialization issues, instead of putting viewmodel into $scope use ordinary javascript object that will be storing immediate values that are bound to UI.
Add watcher to look for changes on that "storage object"
As soon as the change spotted, serialize the object and persist it
Explanations
Why do we need a random parameter in URL?
We don't want to store all data in URL, since there might be quite some amount of data that wont fit into URL. So in order to provide the guarantees the URL won't break, we put only small random GUID/UUID into it that later allows obtaining the data associated with current "navigation" by this random GUID/UUID.
The storage
There are multitude of storage scenarios available out there: LocalStorage, IndexedDB, WebSQL, Session Storage, you name it, but due to their cross-tab, cross-browser, browser-specific nature it would be hard to manipulate and manage all of the data that gets into the storage. The implementation will be buggy / might require server-side support.
So the most elegant storage strategy for this scenario would be storing data in special window.name variable which is capable of storing data in-between requests. So the data is safe until you close your tab.
The Question
On behalf of everything written above, I have the root view called "view" that has a state parameter id (this is the random GUID/UUID)
$stateProvider.state('view', {
url: '/view/{id}',
controller: 'view',
templateUrl: 'views/view.html'
});
All of the other views derive from this view, is there way to make ui-sref directive to automatically inject a random GUID/UUID into id state parameter of my root view, instead of writing each time ui-sref's like:
<a ui-sref="view({id:guid()}).someNestedView({someNestedParam: getParam()})"
I would like to have something like:
<a ui-sref="view.someNestedView({someNestedParam: getParam()})"
The AOP and Decorator pattern are the answer. The comprehensive description could be found here:
Experiment: Decorating Directives by Jesus Rodriguez
Similar solution as described below, could be observed:
Changing the default behavior of $state.go() in ui.router to reload by default
How that would work? There is a link to working example
In this case, we do not solve from which source the random GUID comes from. Let's just have it in runtime:
var guidFromSomeSource = '70F81249-2487-47B8-9ADF-603F796FF999';
Now, we can inject an Decorator like this:
angular
.module('MyApp')
.config(function ($provide) {
$provide.decorator('$state', function ($delegate) {
// let's locally use 'state' name
var state = $delegate;
// let's extend this object with new function
// 'baseGo', which in fact, will keep the reference
// to the original 'go' function
state.baseGo = state.go;
// here comes our new 'go' decoration
var go = function (to, params, options) {
params = params || {};
// only in case of missing 'id'
// append our random/constant 'GUID'
if (angular.isUndefined(params.id)) {
params.id = guidFromSomeSource;
}
// return processing to the 'baseGo' - original
this.baseGo(to, params, options);
};
// assign new 'go', right now decorating the old 'go'
state.go = go;
return $delegate;
});
})
Code should be self explanatory, check it in action here

backbone.js and states that are not in models

In the data-driven paradigm of Backbone, the backbone views/routers should subscribe to model changes and act based on the model change events. Following this principle, the application's views/routers can be isolated from each other, which is great.
However, there are a lot of changes in the states of the applications that are not persisted in the models. For example, in a to-do app, there could be buttons that lets you look at tasks that are "completed", "not completed", or "all". This is an application state not persisted in the model. Note that the completion state of any task is persisted, but the current filter in the view is a transient state.
What is a good way to deal with such application state? Using a plain, non-backboned state means that the views/routers cannot listen to the changes in this state, and hence become difficult to code in the data-driven paradigm.
Your buttons filter example can be properly solved using Model events.
I suppose your buttons handlers have access to the tasks Collection. Then filter the collection and trigger events over the selected Models like:
model.trigger( "filter:selected" )
or
model.trigger( "filter:un-selected" )
The ModelView can be listening to these events on its Model and acts accordingly.
This is following your requirements of respecting the not use or "attributes that are not persistent" like selected but I don't have any trauma to use special attributes even if they shouldn't be persistent. So I also suggest to modify the selected attribute of your Models to represent volatile states.
So in your buttons handlers filter the collection and modify the selected attribute in your Models is my preferred solution:
model.set( "selected", true )
You can always override Model.toJSON() to clean up before sync or just leave this special attributes to travel to your server and being ignored there.
Comment got long so I'll produce a second answer to compare.
First, I feel like "completed", "not completed" are totally item model attributes that would be persisted. Or maybe if your items are owned by many users, each with their own "completed" "not completed" states, then the item would have a completedState submodel or something. Point being, while #fguillen produced two possible solutions for you, I also prefer to do it his second way, having models contain the attributes and the button / view doing most of the work.
To me it doesn't make sense for the model to have its own custom event for this. Sounds to me like a filter button would only have to deal with providing the appropriate views. (Which items to show) Thus, I would just make the button element call a function that runs a filter on the collection more or less directly.
event: {
'click filterBtnCompleted':'showCompleted'
},
showCompleted: function(event) {
var completedAry = this.itemCollection.filter(function(item) {
return item.get('completed');
});
// Code empties your current item views and re-renders them with just filtered models
}
I tend to tuck away these kind of convenience filter functions within the collection themselves so I can just call:
this.ItemCollection.getCompleted(); // etc.
Leaving these attributes in your model and ignoring them on your server is fine. Although again, it does sound to me like they would be attributes you want to persist.
One more thing, you said that using plain non-backboned states sacrifices events. (Grin :-) Not so! You can easily extend any object to have Backbone.Event capabilities.
var flamingo = {};
_.extend(flamingo, Backbone.Events);
Now you can have flamingo trigger and listen for events like anything else!
EDIT: to address Router > View data dealings -------------------//
What I do with my router might not be what you do, but I pass my appView into the router as an options. I have a function in appView called showView() that creates subviews. Thus my router has access to the views I'm dealing with pretty much directly.
// Router
initialize: function(options) {
this.appView = options.appView;
}
In our case, it may be the itemsView that will need to be filtered to present the completed items. Note: I also have a showView() function that manages subviews. You might just be working directly with appView in your scenario.
So when a route like /items/#completed is called, I might do something like this.
routes: {
'completed':'completed'
},
completed: {
var itemsView = ItemCollectionView.create({
'collection': // Your collection however you do it
});
this.appView.showView(itemsView);
itemsView.showCompleted(); // Calls the showCompleted() from View example way above
}
Does this help?

Categories

Resources