1
0
mirror of https://github.com/ascribe/onion.git synced 2024-12-22 09:23:13 +01:00

Merge pull request #12 from ascribe/AD-1242-Frontend-caching-using-local-storage

Implement functionality for feature-detecting webStorage
This commit is contained in:
Tim Daubenschütz 2015-11-16 17:37:29 +01:00
commit e016ee7446
17 changed files with 209 additions and 87 deletions

View File

@ -10,6 +10,7 @@ queryParams of the piece_list_store should all be reflected in the url and not a
- Use classNames plugin instead of if-conditional-classes
- Instead of using `currentUser && currentUser.email` in an validation that checks whether we user is logged in or now, in the `UserStore` on login we set a boolean property called `isLoggedIn` that can then be used instead of `email`
- Refactor AclProxy to be a generic hide/show element component. Have it take data input and a validation function to assess whether it should show or hide child elements. Move current Acl checks to another place, eg. acl_utils.js.
- Convert all fetchers to [alt.js's sources](http://alt.js.org/docs/async/)
# Refactor DONE
- Refactor forms to generic-declarative form component ✓

View File

@ -1,37 +1,18 @@
'use strict';
import { altUser } from '../alt';
import UserFetcher from '../fetchers/user_fetcher';
class UserActions {
constructor() {
this.generateActions(
'updateCurrentUser',
'deleteCurrentUser'
'fetchCurrentUser',
'successFetchCurrentUser',
'logoutCurrentUser',
'successLogoutCurrentUser',
'errorCurrentUser'
);
}
fetchCurrentUser() {
UserFetcher.fetchOne()
.then((res) => {
this.actions.updateCurrentUser(res.users[0]);
})
.catch((err) => {
console.logGlobal(err);
this.actions.updateCurrentUser({});
});
}
logoutCurrentUser() {
UserFetcher.logout()
.then(() => {
this.actions.deleteCurrentUser();
})
.catch((err) => {
console.logGlobal(err);
});
}
}
export default altUser.createActions(UserActions);

View File

@ -1,29 +1,16 @@
'use strict';
import { altWhitelabel } from '../alt';
import WhitelabelFetcher from '../fetchers/whitelabel_fetcher';
class WhitelabelActions {
constructor() {
this.generateActions(
'updateWhitelabel'
'fetchWhitelabel',
'successFetchWhitelabel',
'errorWhitelabel'
);
}
fetchWhitelabel() {
WhitelabelFetcher.fetch()
.then((res) => {
if(res && res.whitelabel) {
this.actions.updateWhitelabel(res.whitelabel);
} else {
this.actions.updateWhitelabel({});
}
})
.catch((err) => {
console.logGlobal(err);
});
}
}
export default altWhitelabel.createActions(WhitelabelActions);

View File

@ -60,7 +60,7 @@ let LoginForm = React.createClass({
GlobalNotificationActions.appendGlobalNotification(notification);
if(success) {
UserActions.fetchCurrentUser();
UserActions.fetchCurrentUser(true);
}
},

View File

@ -61,7 +61,7 @@ let SignupForm = React.createClass({
// Refactor this to its own component
this.props.handleSuccess(getLangText('We sent an email to your address') + ' ' + response.user.email + ', ' + getLangText('please confirm') + '.');
} else {
UserActions.fetchCurrentUser();
UserActions.fetchCurrentUser(true);
}
},

View File

@ -49,7 +49,11 @@ export default function AuthProxyHandler({to, when}) {
},
componentDidUpdate() {
this.redirectConditionally();
// Only refresh this component, when UserSources are not loading
// data from the server
if(!UserStore.isLoading()) {
this.redirectConditionally();
}
},
componentWillUnmount() {

View File

@ -27,7 +27,7 @@ let AccountSettings = React.createClass({
},
handleSuccess(){
this.props.loadUser();
this.props.loadUser(true);
let notification = new GlobalNotificationModel(getLangText('Settings succesfully updated'), 'success', 5000);
GlobalNotificationActions.appendGlobalNotification(notification);
},

View File

@ -46,8 +46,8 @@ let SettingsContainer = React.createClass({
UserStore.unlisten(this.onChange);
},
loadUser(){
UserActions.fetchCurrentUser();
loadUser(invalidateCache){
UserActions.fetchCurrentUser(invalidateCache);
},
onChange(state) {

9
js/fetchers/README.md Normal file
View File

@ -0,0 +1,9 @@
# README
Recently, we've been discovering [alt.js's sources](http://alt.js.org/docs/async/). As it allows us to implement caching as well as avoid a lot of unnecessary boilerplate code, we do not want to write any more NEW `fetcher`s.
So if you need to query an endpoint, or if you make active changes to one of the `fetcher`s, please consider refactoring it to a `source`.
Also, please read the `NAMING_CONVENTIONS.md` file in there first.
Thanks

View File

@ -1,20 +0,0 @@
'use strict';
import requests from '../utils/requests';
import ApiUrls from '../constants/api_urls';
let UserFetcher = {
/**
* Fetch one user from the API.
* If no arg is supplied, load the current user
*/
fetchOne() {
return requests.get('user');
},
logout() {
return requests.get(ApiUrls.users_logout);
}
};
export default UserFetcher;

View File

@ -1,17 +0,0 @@
'use strict';
import requests from '../utils/requests';
import { getSubdomain } from '../utils/general_utils';
let WhitelabelFetcher = {
/**
* Fetch the custom whitelabel data from the API.
*/
fetch() {
return requests.get('whitelabel_settings', {'subdomain': getSubdomain()});
}
};
export default WhitelabelFetcher;

View File

@ -0,0 +1,70 @@
# Naming conventions for sources
## Introduction
When using [alt.js's sources](http://alt.js.org/docs/async/), we don't want the source's method to clash with the store/action's method names.
While actions will still be named by the following schema:
```
<verb><ObjectToManipulateInTheStore>
```
e.g.
```
fetchCurrentUser
logoutCurrentUser
fetchApplication
refreshApplicationToken
```
we cannot repeat this for a sources' methods as patterns like this would emerge in the stores:
```javascript
onFetchCurrentUser(invalidateCache) {
this.invalidateCache = invalidateCache;
if(!this.getInstance().isLoading()) {
this.getInstance().fetchCurrentUser(); // does not call a flux "action" but a method in user_source.js - which is confusing
}
}
```
Therefore we're introducing the following naming convention:
## Rules
1. All source methods that perform a data lookup of any kind (be it cached or not), are called `lookup<ObjectToManipulateInTheStore>`. As a mnemonic aid, "You *lookup* something in a *source*".
2. Promise-based callback methods - provided by alt.js - prepend their action, followed by `ObjectToManipulateInTheStore` (e.g. `error<ObjectToManipulateInTheStore>`)
2. For all methods that do not fit 1.), we prepend `perform`.
## Examples
### Examples for Rule 1.)
*HTTP GET'ing the current User*
```javascript
UserActions.fetchCurrentUser
UserStore.onFetchCurrentUser
UserSource.lookupCurrentUser
```
### Examples for Rule 2.)
This talks about the naming in an actual `*_source.js` file:
```javascript
lookupCurrentUser: {
success: UserActions.successFetchCurrentUser, // 'success<ObjectToManipulateInTheStore>'
error: UserActions.errorCurrentUser, // 'error<ObjectToManipulateInTheStore>'
},
```
### Examples for Rule 3.)
*HTTP GET'ing a certain user endpoint, that logs the user out :sad_face:(, as this should not be a GET request anyway)*
```javascript
UserActions.logoutCurrentUser
UserStore.onLogoutCurrentUser
UserSource.performLogoutCurrentUser
```

34
js/sources/user_source.js Normal file
View File

@ -0,0 +1,34 @@
'use strict';
import requests from '../utils/requests';
import ApiUrls from '../constants/api_urls';
import UserActions from '../actions/user_actions';
const UserSource = {
lookupCurrentUser: {
remote() {
return requests.get('user');
},
local(state) {
return state.currentUser && state.currentUser.email ? state : {};
},
success: UserActions.successFetchCurrentUser,
error: UserActions.errorCurrentUser,
shouldFetch(state) {
return state.userMeta.invalidateCache || state.currentUser && !state.currentUser.email;
}
},
performLogoutCurrentUser: {
remote() {
return requests.get(ApiUrls.users_logout);
},
success: UserActions.successLogoutCurrentUser,
error: UserActions.errorCurrentUser
}
};
export default UserSource;

View File

@ -0,0 +1,25 @@
'use strict';
import requests from '../utils/requests';
import WhitelabelActions from '../actions/whitelabel_actions';
import { getSubdomain } from '../utils/general_utils';
const WhitelabelSource = {
lookupWhitelabel: {
remote() {
return requests.get('whitelabel_settings', {'subdomain': getSubdomain()});
},
local(state) {
return Object.keys(state.whitelabel).length > 0 ? state : {};
},
success: WhitelabelActions.successFetchWhitelabel,
error: WhitelabelActions.errorWhitelabel,
shouldFetch(state) {
return state.whitelabelMeta.invalidateCache || Object.keys(state.whitelabel).length === 0;
}
}
};
export default WhitelabelSource;

View File

@ -3,19 +3,46 @@
import { altUser } from '../alt';
import UserActions from '../actions/user_actions';
import UserSource from '../sources/user_source';
class UserStore {
constructor() {
this.currentUser = {};
this.userMeta = {
invalidateCache: false,
err: null
};
this.bindActions(UserActions);
this.registerAsync(UserSource);
}
onUpdateCurrentUser(user) {
onFetchCurrentUser(invalidateCache) {
this.userMeta.invalidateCache = invalidateCache;
if(!this.getInstance().isLoading()) {
this.getInstance().lookupCurrentUser();
}
}
onSuccessFetchCurrentUser({users: [user]}) {
this.userMeta.invalidateCache = false;
this.currentUser = user;
}
onDeleteCurrentUser() {
onLogoutCurrentUser() {
this.getInstance().performLogoutCurrentUser();
}
onSuccessLogoutCurrentUser() {
this.currentUser = {};
}
onErrorCurrentUser(err) {
console.logGlobal(err);
this.userMeta.err = err;
}
}
export default altUser.createStore(UserStore, 'UserStore');

View File

@ -2,17 +2,38 @@
import { altWhitelabel } from '../alt';
import WhitelabelActions from '../actions/whitelabel_actions';
import WhitelabelSource from '../sources/whitelabel_source';
class WhitelabelStore {
constructor() {
this.whitelabel = {};
this.whitelabelMeta = {
invalidateCache: false,
err: null
};
this.bindActions(WhitelabelActions);
this.registerAsync(WhitelabelSource);
}
onUpdateWhitelabel(whitelabel) {
onFetchWhitelabel(invalidateCache) {
this.whitelabelMeta.invalidateCache = invalidateCache;
if(!this.getInstance().isLoading()) {
this.getInstance().lookupWhitelabel();
}
}
onSuccessFetchWhitelabel({ whitelabel }) {
this.whitelabelMeta.invalidateCache = false;
this.whitelabel = whitelabel;
}
onErrorCurrentUser(err) {
console.logGlobal(err);
this.whitelabelMeta.err = err;
}
}
export default altWhitelabel.createStore(WhitelabelStore, 'WhitelabelStore');

View File

@ -23,4 +23,4 @@
* @type {bool} Is drag and drop available on this browser
*/
export const dragAndDropAvailable = 'draggable' in document.createElement('div') &&
!/Mobile|Android|Slick\/|Kindle|BlackBerry|Opera Mini|Opera Mobi/i.test(navigator.userAgent);
!/Mobile|Android|Slick\/|Kindle|BlackBerry|Opera Mini|Opera Mobi/i.test(navigator.userAgent);