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.
Related
I decided to pick up this RxJS tutorial over the weekend to learn about reactive programming. The goal is to set up a simple page that generates an interactive list of users from the Github users API using Observables.
The list displayed is a subset of the total number of users retrieved (in my case 3 out of 30). The list needs to be refreshable (show a new set of users), and you need to be able to remove entries from it by clicking the 'remove' button on each respective entry.
I've set up a chain of Observables to define the behavior of the page. Some act as events to trigger processing, and some publish processed results for use in the app. This chain should dynamically cause my list to be updated. Currently, the default flow is like this:
Startup!
Suggestions refresh triggered! (this is where the data is retrieved)
30 new suggestions available!
List update triggered! (this is where I choose 3 users to display)
List updated! (at this point, the list is showing on the page)
A list update is triggered on startup, by refreshing the list and by removing something from the list. However, when I refresh the list, this happens:
Refresh button clicked!
Suggestions refresh triggered!
30 new suggestions available!
As you can see, the trigger to update the list of users is not set off. As I understand it, by emitting some value at the start of a stream, the rest of the stream should be executed consequently. However, this only seems to be happening the first time I run through the chain. What am I missing?
You can find a running version of my project here.
I think the issue is in the way userStream$ Observable is created.
Filtering users not closed and then taking the first 3 is something that can be done directly on the UserModel[] array passed into the pipe chain by displayEvents$ via filter and slice methods of Array.
If you do so, you remove the need of using the from function to create an Observable<UserModel> on which you then have to apply flatMap (which is currently better known as mergeMap) to apply finally toArray to transform it back into an Array of UserModel.
In other words you can simplify the code as in the following example, which as side effect solves the refresh problem.
this.userStream$ = this.displayEvent$.pipe(
map(users => users
.filter((user: UserModel) => !this.closedUsers.has(user))
.slice(0, this.numberOfUsers))
// flatMap((users: UserModel[]) => from(users))
// // Don't include users we've previously closed.
// , filter((user: UserModel) => !this.closedUsers.has(user))
// , take(this.numberOfUsers)
// , toArray()
, tap(() => console.log('List updated!'))
// Unless we explicitly want to recalculate the list of users, re-use the current result.
, shareReplay(1));
To be honest though I have not fully grasped why your original solution, which is a sort of long detour, does not work.
After reading the docs, this is my understanding of sync.
I instantiate some Backbone.Model and call Collection.create(). create() eventually calls sync() and the Model is POSTed to the server. Then there is a sync in the opposite direction such that the Model on the client is given an id.
Does this update then trigger componentDidUpdate()?
Note: componentDidUpdate is a ReactJS thing, so if that doesn't make sense, the question reduces to "Is the client-side model updated and the view re-rendered?"
Since inside of my componentDidUpdate() I am making a call to save() to keep everything up to date, this subsequently makes a call to sync() which then fires a PUT request (because the Model already has an id).
I'm asking, because in my current application, creating a TodoItem seems to result in a POST and then a PUT which I find redundant. Perhaps it is for an unrelated reason.
It actually fires two POSTS and then two PUTS when adding one item, but that is another question.
The first time you save a model (one which doesn't have an id) it will make a POST, thereafter it will make a PUT (update). I think you are confusing when to use create/add/save:
Use save at any point to save the current client collection/model state to the server
Use add to add Model(s) to a collection (a single Model, an array of Models, or an array of objects which contain attributes and let the collection create them)
Use create as a shorthand for creating a model, adding it to the collection, and syncing the collection to the server.
My guess is that you are calling create and save in one operation - you should be using add and save instead, or just create.
The view will not automatically update for you, you will need to listen to changes or events on the collection/model and update the view yourself - there is no equivalent of componentDidUpdate. For example:
initialize: function() {
this.listenTo(this.collection, 'sync', this.onCollectionSync);
},
onCollectionSync: function() {
this.render();
}
I want my app to fire a method (client side) when a particular subscribed data is changed? for example, the client side has this following subscription
Meteor.subscribe('thePlayers');
thePlayers subscription returns a collection of data which is being displayed in the html through the template.
so whenever the collection get changed, Meteor automatically change the data in the HTML also. Besides this feature, I want my app to fire a method say fire() to be executed as soon as data get changed. What should i do to achieve this?
As David Weldon correctly, cursor.observerChanges is the way to go. Here's how you can use it for your example (assuming your collection is called thePlayers):
client-side
methodCaller = function (methodName) {
return function (/* arguments */) {
Meteor.apply(methodName, arguments)
}
}
var fireCaller = methodCaller('fire')
thePlayers.find().observeChanges({
added: fireCaller,
changed: fireCaller,
removed: fireCaller
})
In case you need this fire() to be run on server, you don't need a method, you can just rely on the observeChanges feature or just observe in your publication. See this question to get an example of how you can achieve that.
In case you need this fire() to be run on client, keep in mind that every helper in your template is reactive, that means it will re-run each time your collection is changed. I assume that it requires that you use the subscription inside it, but that needs to be confirmed.
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
I've a view with knockout.js which has some textboxes and dropdowns.
known when the user changes a value i save the data with a $post
for this i created some computed propties like
self.subjectChanged ko.computed(function () {
var subject self.subject();
//save...
But this also triggers when the subject was loaded from database and set for first time.
What is the best practice for this ?
A similar problem is that i have a function getdata() which depends on two properties.
Now on load this method is raised twice (for each property)
What are best practices to handle this szenarios ?
One way of doing it is to load the page and bind the data as normal, and then use subscriptions to monitor changes to the observable you are interested in.
http://knockoutjs.com/documentation/observables.html#explicitly-subscribing-to-observables
viewModel.subject.subscribe(function(newValue) {
// code you want to run when the value changes...
});
for example http://jsfiddle.net/m8mb5/
This may not be best practice, but in the past I tied a loaded variable to the vm and when the data finished loading from the server I set it to true;
In my computeds I would surround the code that actually did the work in an if that checked the loaded. Computeds can be a little tricky though, you may need to reference the observables outside of the if to ensure they fire correctly.
com = ko.computed(function(){
if(loaded){
var subject = self.subject();
}
// reference observable outside of if to ensure the computed fires when the observable changes
self.subject();
});