Parsing UI router states to URLs without Angular - javascript

I have a Angular 1.5.9 web application and a Node.js/Sails.js 0.12 backend.
Inside Angular runs UI router 0.4 to handle states.
The state definitions might look like this (quite vanilla, I'd say):
$stateProvider.state('dogs', {
url: '/ourdogsarecute_{specialIDofDog}
}).
state('dogs.specialDogState', {
url: '/specialinfo_{specialInfoOfDog}'
});
Now, the following situation arises: In the backend (i.e. outside of Angular), I have to transform an Angular UI router state link like
{stateName: 'dogs.specialDogState', stateParams: {specialIDofDog: 11212, specialInfoOfDog: 'likesbones' } } into a valid URL like https://www.our-app.dog/ourdogsarecute_11212/specialinfo_likesbones.
I have no idea how to do that without a lot of manual work. Is there a kind of parser for UI router states as a node module?
I can access the front-end code where the state definitions lie from the backend somehow. that's not the problem. The problem is the transformation from state links into URLs.

UI-Router 1.0 split the code up into ui-router core and ui-router angularjs. You can use ui-router core (which has no external dependencies) on your node backend to generate these urls. Since you already have your states available as a JSON file, you can simply register the states with ui-router core in your backend and then use the state objects to generate URLs.
In your node backend, add ui-router core
npm install --save #uirouter/core
// The library exports most of its code
var UIR = require('#uirouter/core');
// Create the router instance
var router = new UIR.UIRouter();
// Get the state registry
var registry = router.stateRegistry;
var states = [
{ name: 'dogs', url: '/ourdogsarecute_{specialIDofDog}' },
{ name: 'dogs.specialDogState', url: '/specialinfo_{specialInfoOfDog}' },
];
states.forEach(state => registry.register(state));
var params = { specialIDofDog: '11212', specialInfoOfDog: 'lovesbones' };
// Get the internal state object
var stateObj = registry.get('dogs.specialDogState').$$state();
// Generate the URL
console.log(stateObj.url.format(params));

For reference: my solution now looks like this.
First of all, I've put my state definitions into a separate file, making it easier to access it from outside:
var myStates = [
{
name: 'dogs', stateProperties: {
url: '/ourdogsarecute_{specialIDofDog}'
}
}, {
name: 'dogs.specialDogState', stateProperties: {
url: '/specialinfo_{specialInfoOfDog}'
}
}];
And then in my app.config
for(var i = 0; i < myStates.length; i++) {
$stateProvider.state(myStates[i].name, myStates[i].stateProperties);
}
In the backend, I've created this function:
/**
* #description Turns state name and state params into a URL string, using stateLinks definition synchronized from front end (being UI router state definitions)
* #param {string} stateName Something like 'dogs.info.specialAttributes'
* #param {object} stateParams Something like {dogID: 34346346, dogStatus: 'private', dogInfo: 'food'}
* #returns {string} URL
*/
stateLinkResolve: function(stateName, stateParams) {
if(!(stateName && stateName.length > 0)) {
return '/';
}
var resultUrl = '';
var splittedSubStates = stateName.split('.');// split "dogs.info.specialAttributes" into ["dogs","info","specialAttributes"]
var currentStateInHierarchy = '';
for(var i = 0; i < splittedSubStates.length; i++) {
/* Add dot if "in between": not the first, not the last. So that "dogs" remains "dogs", but when going to "dogs.info", we want the dot in between */
if(i > 0 && i < (splittedSubStates.length + 1) ) {
currentStateInHierarchy += '.';
}
currentStateInHierarchy += splittedSubStates[i]; // Add current splitted name (being only the last name part) to the state name in its context. I.e. when doing "info", we want to access "dogs.info"
var currState = _.find(stateDefs,{name: currentStateInHierarchy});
var urlRaw = currState.stateProperties.url;
/* uiRouter URLs may contain wildcards for parameter values like /ourdogs_{dogID:int}_{dogStatus}/:dogInfo.
We go through each of these three types and replace them with their actual content.
*/
for(var currentParam in stateParams) {
urlRaw = urlRaw.replace(':' + currentParam, stateParams[currentParam]); // search for ":paramName" in URL
urlRaw = urlRaw.replace('{' + currentParam + '}', stateParams[currentParam]); // search for "{paramName}" in URL
// search for "{paramName:paramType}" in URL
var uiRouterParamTypes = ["hash", "string", "query", "path", "int", "bool", "date", "json", "any"];
for(var j = 0; j < uiRouterParamTypes.length; j++) {
urlRaw = urlRaw.replace('{' + currentParam + ':' + uiRouterParamTypes[j] + '}', stateParams[currentParam]);
}
}
resultUrl += urlRaw;
}
return resultUrl;
}
The problem is: This might fail for edge cases and it will definitely fail for new features being implemented to UI state router and the way URLs are built there. So, still hoping for a solution which directly uses UI router magic.

Related

Deserialize DeepObject Querystring Keys in Azure Functions / Javascript

We are constructing an API with Azure Functions, and the spec calls for DeepObject references in the GET request's querystring. So, the structure looks like https://example.com/api/persons?name[first]=Todd. The expectation is that some of the query keys may be DeepObject references, others will be flat.
This is a pattern that apparently Express can handle, but Azure Functions uses an ASP.NET router. The expectation is that the reference above should deserialize into req.params: { name: { first: "Todd" } }. However, instead the output looks like req.params: { "name[first]": "Todd" }.
I would really love to not have to regex/search each key, so does anyone know if:
There's a config/flag in ASP.NET to support this structure?
There's a good pattern in Javascript to deserialize this in some functional -- or at least non-idiosyncratic -- way?
Note: Anyone who suggest some use of the eval() method will not be selected. But for playing you will take home a comment with a Nineties reference, because that was the last decade the use of the that method was considered acceptable. :stuck_out_tongue_winking_eye:
For this problem, I don't think we can change some configuration to support this structure. What we can do is to implement in code by ourselves.
Here is my function code:
module.exports = async function (context, req) {
console.log("======query url is:" + req.url);
const result = queryStringToJSON(req.url);
console.log(result);
context.res = {
body: "success"
};
}
function queryStringToJSON(queryUrl) {
if(queryUrl.indexOf('?') > -1){
var queryString = queryUrl.split('?')[1];
}
var pairs = queryString.split('&');
var result = {};
pairs.forEach(function(pair) {
if (pair.indexOf('[') > -1) {
var nameObj = {};
var firstObj = {};
var nameStr = pair.substring(0, pair.indexOf('['));
var firstStr = pair.substring(pair.indexOf('[')+1, pair.indexOf(']'));
firstObj[firstStr] = pair.split('=')[1];
nameObj[nameStr] = firstObj;
Object.assign(result, nameObj);
}else {
pair = pair.split('=');
result[pair[0]] = decodeURIComponent(pair[1] || '');
}
});
return result;
}
Start the function project, I request it with http://localhost:7071/api/HttpTrigger1?name[first]=Todd&email=test#mail.com. The result shows:
After much searching, I wasn't able to find any way to natively implement this in the ASP.NET router. Though there is a great deal of suggestions on how to deserialize this structure directly in your ASP.NET controller functions, I am working in Javascript.
What was helpful was the qs library, available in NPM, which supports a number of nuances related to this query string structure.
const { query } = req;
// => { "name[first]": "Todd" };
const deserializedQuery = qs.parse(query);
// => { name: { first: "Todd" }
Equally helpful to me is that I need a way to restructure my outbound query string in this same format. Qs works with the paramsSerializer attribute in Axios.
const params = { name: { first: "Todd" };
const paramsSerializer = (params) => { return Qs.stringify(params); };
const reqOptions = { params, paramsSerializer };
axios.get("https://example.com/api/persons", reqOptions);
// => GET https://example.com/api/persons?name[first]=Todd
Thanks to #hury-shen for a completely workable solution. It just wasn't turnkey solution I was looking for.

React/Flux and xhr/routing/caching

This is more of a "whats your opinion/Am I correct in thinking this?" question.
Trying to be as strict as possible while understanding Flux, I was trying to figure out where XHR calls are made, websockets/external stimuli handled, routing takes places, etc.
From what I read across articles, interviews and looking through facebook examples there are a few ways of handling these things. Following flux strictly, Action creators are the ones that do all the XHR calls with the possibility of a PENDING/SUCCESS/FAILURE Actions being fired before and after the request completes.
Another was, coming from facebook's Ian Obermiller, all the READ(GETs) requests are handled directly by the Stores(without involvement of an Action creator/dispatcher) and WRITE(POSTs) requests are handled by the Action Creators going through the entire action>dispatcher>store flow.
Some understandings/conclusions we drew/would like to stick to:
Ideally, anything going in/out of the system happens only through Actions.
Async calls leaving/entering the system will have PENDING/PROGRESS(think file uploads)/SUCCESS/FAILURE Actions.
Single dispatcher across the entire App.
Action>Dispatcher>Store calls are strictly synchronous to stick to the dispatches not being able to start another dispatch internally to avoid chaining events/actions.
Stores are persisted across Views(considering its a single page app, you want to be able to reuse data)
A few questions that we came to some conclusion with, but I'm not entirely satisfied with:
If you take the approach where Stores do Reads, and Actions to Writes, how do you handle situations where multiple Stores might be able to use data from a single XHR call?
Example: API calls issued by TeamStore to /api/teams/{id} which returns something like:
{
entities: {
teams: [{
name: ...,
description: ...,
members: [1, 2, 4],
version: ...
}],
users: [{
id: 1
name: ...,
role: ...,
version: ...
},
{
id: 2
name: ...,
role: ...,
version: ...
},
{
id: 3
name: ...,
role: ...,
version: ...
}]
}
}
Ideally, I'd also like to update the MemberStore with the information returned in this API. We maintain a version number for every entity which is updated on updates to the record, which is what we use internally do reject calls on stale data, etc. Using this, I could have an internal logic, where if I as a side effect of some other API call, I know my data is stale, I trigger a refresh on that record.
The solution, it would seem, is that you'd need the store to trigger an action(which would effectively update the other dependent stores). This short circuits the Store>View>Action to Store>Action and I'm not sure if its a good idea. We already have one thing out of sync with Stores doing their own XHR calls. Concessions like these would start creeping into the entire system eventually.
Or Stores that are aware of other stores and be able to communicate with them. But this breaks the Stores have no Setters rule.
A simple solution to the above problem would be that you stick to Actions being the ONLY place external incoming/outgoing stimulus happens. This simplifies the logic of multiple Stores getting updated.
But now, where and how do you handle caching? We came to the conclusion that the caching would happen at the API Utils/DAO level. (if you look at the flux diagram).
But this introduces other problems. To better understand/explain what I mean by example:
/api/teams returns a list of all the teams with which I display a list of all the teams.
On clicking on a team's link, I go its details view which requires data from /api/teams/{id} if it isn't already present in the Store.
If Actions handle all the XHRs, the View would do something like TeamActions.get([id]) which does TeamDAO.get([id]). To be able to return this call immediately(since we have it cached) the DAO would have to do caching but also maintain the relation between collections/items. This logic, by design, is already present in Stores.
Here come the questions:
Do you duplicate this logic in DAOs and Stores?
Do you make DAO's aware of Stores and they can ask the Store if they already have some data and just return a 302 saying, you're good you have the latest data.
How do you handle validation that involves XHR APIs? Something simple like duplicate Team names.
Views directly hit DAOs and do something like TeamDAO.validateName([name]) which returns a promise or do you do you create an Action? If you create an Action through which Store does Valid/Invalid flow back to the View considering its mostly transient data?
How do you handle Routing? I looked through react-router and I'm not sure I like it. I don't necessarily think forcing a react-ish JSX way of providing route mappings/configs are needed at all. Also, apparently, it employs a RouteDispatcher of its own, which ondoes the single dispatcher rule.
The solution I prefer came from some blog posts/SO answers where you have a the route mappings are stored in the RouteStore.
RouteStore also maintains CURRENT_VIEW. The react AppContainer component is registered with RouteStore and replaces its child views with the CURRENT_VIEW on change. Current Views inform the AppContainer when they're fully loaded and AppContainer fires RouteActions.pending/success/failure, possibly with some context, to inform other components of reaching a stable state, show/hide busy/loading indications.
Something that I have not been able to design cleanly was if you were to design routing similar to Gmail, how would you do it? Some observations of Gmail that I'm a big fan of:
URLs don't change until the page is ready to load. It stays on the current URL while its 'Loading' and moves to the new one once the loading has finished. This makes it so that...
On failure, you don't lose you current page at all. So if you're on compose, and the 'Send' fails, you don't lose your mail (i.e. you don't lose your current stable view/state). (they don't do this because auto saving is le pwn, but you get the idea) You have the option of copy/pasting the mail somewhere for safe keeping till you can send again.
Some references:
https://github.com/gaearon/flux-react-router-example
http://ianobermiller.com/blog/2014/09/15/react-and-flux-interview/
https://github.com/facebook/flux
It's my implementation using facebook Flux and Immutable.js that I think responds to many of your concerns, based on few rules of thumb :
STORES
Stores are responsible for maintaining data state through Immutable.Record and maintaining cache through a global Immutable.OrderedMap referencing Record instance via ids.
Stores directly call WebAPIUtils for read operations and trigger actions for write operations.
Relationship between RecordA and FooRecordB are resolved from a RecordA instance through a foo_id params and retrieved via a call such as FooStore.get(this.foo_id)
Stores only expose getters methods such as get(id), getAll(), etc.
APIUTILS
I use SuperAgent for ajax calls. Each request is wrapped in Promise
I use a map of read request Promise indexed by the hash of url + params
I trigger action through ActionCreators such as fooReceived or fooError when Promise is resolved or rejected.
fooError action should certainly contains payloads with validation errors returned by the server.
COMPONENTS
The controller-view component listen for changes in store(s).
All my components, other than controller-view component, are 'pure', so I use ImmutableRenderMixin to only re-render what it's really needed (meaning that if you print Perf.printWasted time, it should be very low, few ms.
Since Relay and GraphQL are not yet open sourced, I enforce to keep my component props as explicit as possible via propsType.
Parent component should only passes down the necessary props. If my parent component holds an object such as var fooRecord = { foo:1, bar: 2, baz: 3}; (I'm not using Immutable.Record here for the sake of simplicity of this example) and my child component need to display fooRecord.foo and fooRecord.bar, I do not pass the entire foo object but only fooRecordFoo and fooRecordBar as props to my child component because an other component could edit the foo.baz value, making the child component re-render while this component doesn't need at all this value !
ROUTING
- I simply use ReactRouter
IMPLEMENTATION
Here is a basic example :
api
apiUtils/Request.js
var request = require('superagent');
//based on http://stackoverflow.com/a/7616484/1836434
var hashUrl = function(url, params) {
var string = url + JSON.stringify(params);
var hash = 0, i, chr, len;
if (string.length == 0) return hash;
for (i = 0, len = string.length; i < len; i++) {
chr = string.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0; // Convert to 32bit integer
}
return hash;
}
var _promises = {};
module.exports = {
get: function(url, params) {
var params = params || {};
var hash = hashUrl(url, params);
var promise = _promises[hash];
if (promise == undefined) {
promise = new Promise(function(resolve, reject) {
request.get(url).query(params).end( function(err, res) {
if (err) {
reject(err);
} else {
resolve(res);
}
});
});
_promises[hash] = promise;
}
return promise;
},
post: function(url, data) {
return new Promise(function(resolve, reject) {
var req = request
.post(url)
.send(data)
.end( function(err, res) {
if (err) {
reject(err);
} else {
resolve(res);
}
});
});
}
};
apiUtils/FooAPI.js
var Request = require('./Request');
var FooActionCreators = require('../actions/FooActionCreators');
var _endpoint = 'http://localhost:8888/api/foos/';
module.exports = {
getAll: function() {
FooActionCreators.receiveAllPending();
Request.get(_endpoint).then( function(res) {
FooActionCreators.receiveAllSuccess(res.body);
}).catch( function(err) {
FooActionCreators.receiveAllError(err);
});
},
get: function(id) {
FooActionCreators.receivePending();
Request.get(_endpoint + id+'/').then( function(res) {
FooActionCreators.receiveSuccess(res.body);
}).catch( function(err) {
FooActionCreators.receiveError(err);
});
},
post: function(fooData) {
FooActionCreators.savePending();
Request.post(_endpoint, fooData).then (function(res) {
if (res.badRequest) { //i.e response return code 400 due to validation errors for example
FooActionCreators.saveInvalidated(res.body);
}
FooActionCreators.saved(res.body);
}).catch( function(err) { //server errors
FooActionCreators.savedError(err);
});
}
//others foos relative endpoints helper methods...
};
stores
stores/BarStore.js
var assign = require('object-assign');
var EventEmitter = require('events').EventEmitter;
var Immutable = require('immutable');
var AppDispatcher = require('../dispatcher/AppDispatcher');
var ActionTypes = require('../constants/BarConstants').ActionTypes;
var BarAPI = require('../APIUtils/BarAPI')
var CHANGE_EVENT = 'change';
var _bars = Immutable.OrderedMap();
class Bar extends Immutable.Record({
'id': undefined,
'name': undefined,
'description': undefined,
}) {
isReady() {
return this.id != undefined //usefull to know if we can display a spinner when the Bar is loading or the Bar's data if it is ready.
}
getBar() {
return BarStore.get(this.bar_id);
}
}
function _rehydrate(barId, field, value) {
//Since _bars is an Immutable, we need to return the new Immutable map. Immutable.js is smart, if we update with the save values, the same reference is returned.
_bars = _bars.updateIn([barId, field], function() {
return value;
});
}
var BarStore = assign({}, EventEmitter.prototype, {
get: function(id) {
if (!_bars.has(id)) {
BarAPI.get(id);
return new Bar(); //we return an empty Bar record for consistency
}
return _bars.get(id)
},
getAll: function() {
return _bars.toList() //we want to get rid of keys and just keep the values
},
Bar: Bar,
emitChange: function() {
this.emit(CHANGE_EVENT);
},
addChangeListener: function(callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener: function(callback) {
this.removeListener(CHANGE_EVENT, callback);
},
});
var _setBar = function(barData) {
_bars = _bars.set(barData.id, new Bar(barData));
};
var _setBars = function(barList) {
barList.forEach(function (barData) {
_setbar(barData);
});
};
BarStore.dispatchToken = AppDispatcher.register(function(action) {
switch (action.type)
{
case ActionTypes.BAR_LIST_RECEIVED_SUCESS:
_setBars(action.barList);
BarStore.emitChange();
break;
case ActionTypes.BAR_RECEIVED_SUCCESS:
_setBar(action.bar);
BarStore.emitChange();
break;
case ActionTypes.BAR_REHYDRATED:
_rehydrate(
action.barId,
action.field,
action.value
);
BarStore.emitChange();
break;
}
});
module.exports = BarStore;
stores/FooStore.js
var assign = require('object-assign');
var EventEmitter = require('events').EventEmitter;
var Immutable = require('immutable');
var AppDispatcher = require('../dispatcher/AppDispatcher');
var ActionTypes = require('../constants/FooConstants').ActionTypes;
var BarStore = require('./BarStore');
var FooAPI = require('../APIUtils/FooAPI')
var CHANGE_EVENT = 'change';
var _foos = Immutable.OrderedMap();
class Foo extends Immutable.Record({
'id': undefined,
'bar_id': undefined, //relation to Bar record
'baz': undefined,
}) {
isReady() {
return this.id != undefined;
}
getBar() {
// The whole point to store an id reference to Bar
// is to delegate the Bar retrieval to the BarStore,
// if the BarStore does not have this Bar object in
// its cache, the BarStore will trigger a GET request
return BarStore.get(this.bar_id);
}
}
function _rehydrate(fooId, field, value) {
_foos = _foos.updateIn([voucherId, field], function() {
return value;
});
}
var _setFoo = function(fooData) {
_foos = _foos.set(fooData.id, new Foo(fooData));
};
var _setFoos = function(fooList) {
fooList.forEach(function (foo) {
_setFoo(foo);
});
};
var FooStore = assign({}, EventEmitter.prototype, {
get: function(id) {
if (!_foos.has(id)) {
FooAPI.get(id);
return new Foo();
}
return _foos.get(id)
},
getAll: function() {
if (_foos.size == 0) {
FooAPI.getAll();
}
return _foos.toList()
},
Foo: Foo,
emitChange: function() {
this.emit(CHANGE_EVENT);
},
addChangeListener: function(callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener: function(callback) {
this.removeListener(CHANGE_EVENT, callback);
},
});
FooStore.dispatchToken = AppDispatcher.register(function(action) {
switch (action.type)
{
case ActionTypes.FOO_LIST_RECEIVED_SUCCESS:
_setFoos(action.fooList);
FooStore.emitChange();
break;
case ActionTypes.FOO_RECEIVED_SUCCESS:
_setFoo(action.foo);
FooStore.emitChange();
break;
case ActionTypes.FOO_REHYDRATED:
_rehydrate(
action.fooId,
action.field,
action.value
);
FooStore.emitChange();
break;
}
});
module.exports = FooStore;
components
components/BarList.react.js (controller-view component)
var React = require('react/addons');
var Immutable = require('immutable');
var BarListItem = require('./BarListItem.react');
var BarStore = require('../stores/BarStore');
function getStateFromStore() {
return {
barList: BarStore.getAll(),
};
}
module.exports = React.createClass({
getInitialState: function() {
return getStateFromStore();
},
componentDidMount: function() {
BarStore.addChangeListener(this._onChange);
},
componentWillUnmount: function() {
BarStore.removeChangeListener(this._onChange);
},
render: function() {
var barItems = this.state.barList.toJS().map(function (bar) {
// We could pass the entire Bar object here
// but I tend to keep the component not tightly coupled
// with store data, the BarItem can be seen as a standalone
// component that only need specific data
return <BarItem
key={bar.get('id')}
id={bar.get('id')}
name={bar.get('name')}
description={bar.get('description')}/>
});
if (barItems.length == 0) {
return (
<p>Loading...</p>
)
}
return (
<div>
{barItems}
</div>
)
},
_onChange: function() {
this.setState(getStateFromStore();
}
});
components/BarListItem.react.js
var React = require('react/addons');
var ImmutableRenderMixin = require('react-immutable-render-mixin')
var Immutable = require('immutable');
module.exports = React.createClass({
mixins: [ImmutableRenderMixin],
// I use propTypes to explicitly telling
// what data this component need. This
// component is a standalone component
// and we could have passed an entire
// object such as {id: ..., name, ..., description, ...}
// since we use all the datas (and when we use all the data it's
// a better approach since we don't want to write dozens of propTypes)
// but let's do that for the example's sake
propTypes: {
id: React.PropTypes.number.isRequired,
name: React.PropTypes.string.isRequired,
description: React.PropTypes.string.isRequired
}
render: function() {
return (
<li>
<p>{this.props.id}</p>
<p>{this.props.name}</p>
<p>{this.props.description}</p>
</li>
)
}
});
components/BarDetail.react.js
var React = require('react/addons');
var ImmutableRenderMixin = require('react-immutable-render-mixin')
var Immutable = require('immutable');
var BarActionCreators = require('../actions/BarActionCreators');
module.exports = React.createClass({
mixins: [ImmutableRenderMixin],
propTypes: {
id: React.PropTypes.number.isRequired,
name: React.PropTypes.string.isRequired,
description: React.PropTypes.string.isRequired
},
handleSubmit: function(event) {
//Since we keep the Bar data up to date with user input
//we can simply save the actual object in Store.
//If the user goes back without saving, we could display a
//"Warning : item not saved"
BarActionCreators.save(this.props.id);
},
handleChange: function(event) {
BarActionCreators.rehydrate(
this.props.id,
event.target.name, //the field we want to rehydrate
event.target.value //the updated value
);
},
render: function() {
return (
<form onSubmit={this.handleSumit}>
<input
type="text"
name="name"
value={this.props.name}
onChange={this.handleChange}/>
<textarea
name="description"
value={this.props.description}
onChange={this.handleChange}/>
<input
type="submit"
defaultValue="Submit"/>
</form>
)
},
});
components/FooList.react.js (controller-view component)
var React = require('react/addons');
var FooStore = require('../stores/FooStore');
var BarStore = require('../stores/BarStore');
function getStateFromStore() {
return {
fooList: FooStore.getAll(),
};
}
module.exports = React.createClass({
getInitialState: function() {
return getStateFromStore();
},
componentDidMount: function() {
FooStore.addChangeListener(this._onChange);
BarStore.addChangeListener(this._onChange);
},
componentWillUnmount: function() {
FooStore.removeChangeListener(this._onChange);
BarStore.removeChangeListener(this._onChange);
},
render: function() {
if (this.state.fooList.size == 0) {
return <p>Loading...</p>
}
return this.state.fooList.toJS().map(function (foo) {
<FooListItem
fooId={foo.get('id')}
fooBar={foo.getBar()}
fooBaz={foo.get('baz')}/>
});
},
_onChange: function() {
this.setState(getStateFromStore();
}
});
components/FooListItem.react.js
var React = require('react/addons');
var ImmutableRenderMixin = require('react-immutable-render-mixin')
var Bar = require('../stores/BarStore').Bar;
module.exports = React.createClass({
mixins: [ImmutableRenderMixin],
propTypes: {
fooId: React.PropTypes.number.isRequired,
fooBar: React.PropTypes.instanceOf(Bar).isRequired,
fooBaz: React.PropTypes.string.isRequired
}
render: function() {
//we could (should) use a component here but this answer is already too long...
var bar = <p>Loading...</p>;
if (bar.isReady()) {
bar = (
<div>
<p>{bar.get('name')}</p>
<p>{bar.get('description')}</p>
</div>
);
}
return (
<div>
<p>{this.props.fooId}</p>
<p>{this.props.fooBaz}</p>
{bar}
</div>
)
},
});
Let's go through an entire loop for FooList:
State 1:
User hits the page /foos/ listing the Foos via the FooListcontroller-view component
FooListcontroller-view component calls FooStore.getAll()
_foos map is empty in FooStore so FooStore performs a request via FooAPI.getAll()
The FooList controller-view component renders itself as loading state since its state.fooList.size == 0.
Here's the actual look of our list :
++++++++++++++++++++++++
+ +
+ "loading..." +
+ +
++++++++++++++++++++++++
FooAPI.getAll() request resolves and triggers the FooActionCreators.receiveAllSuccess action
FooStore receive this action, updates its internal state, and emits change.
State 2:
FooList controller-view component receive change event and update its state to get the list from the FooStore
this.state.fooList.size is no longer == 0 so the list can actually renders itself (note that we use toJS() to explicitly get a raw javascript object since React does not handle correctly mapping on not raw object yet).
We're passing needed props to the FooListItem component.
By calling foo.getBar() we're telling to the FooStore that we want the Bar record back.
getBar() method of Foo record retrieve the Bar record through the BarStore
BarStore does not have this Bar record in its _bars cache, so it triggers a request through BarAPI to retrieve it.
The same happens for all Foo in this.sate.fooList of FooList controller-view component
The page now looks something like this:
++++++++++++++++++++++++
+ +
+ Foo1 "name1" +
+ Foo1 "baz1" +
+ Foo1 bar: +
+ "loading..." +
+ +
+ Foo2 "name2" +
+ Foo2 "baz2" +
+ Foo2 bar: +
+ "loading..." +
+ +
+ Foo3 "name3" +
+ Foo3 "baz3" +
+ Foo3 bar: +
+ "loading..." +
+ +
++++++++++++++++++++++++
-Now let's say the BarAPI.get(2) (requested by Foo2) resolves before BarAPI.get(1) (request by Foo1). Since it's asynchronous it's totally plausible.
- The BarAPI triggers the BAR_RECEIVED_SUCCESS' action via theBarActionCreators.
- TheBarStore` responds to this action by updating its internal store and emits change. That's the now the fun part...
State 3:
The FooList controller-view component responds to the BarStore change by updating its state.
The render method is called
The foo.getBar() call now retrieve a real Bar record from BarStore. Since this Bar record has been effectively retrieved, the ImmutablePureRenderMixin will compare old props with current props and determine that the Bar objects has changed ! Bingo, we could re-render the FooListItem component (a better approach here would be to create a separate FooListBarDetail component to let only this component to re-render, here we also re-rendering the Foo's details that have not changed but for the sake of simplicity let's just do that).
The page now looks like this :
++++++++++++++++++++++++
+ +
+ Foo1 "name1" +
+ Foo1 "baz1" +
+ Foo1 bar: +
+ "loading..." +
+ +
+ Foo2 "name2" +
+ Foo2 "baz2" +
+ Foo2 bar: +
+ "bar name" +
+ "bar description" +
+ +
+ Foo3 "name3" +
+ Foo3 "baz3" +
+ Foo3 bar: +
+ "loading..." +
+ +
++++++++++++++++++++++++
If you want me to add more details from a non detailed part (such as action creators, constants, routing, etc., use of BarListDetail component with form, POST, etc.) just tell me in the comments :).
A few differences in my implementation:
I like stores employing a flyweight pattern. That is, unless forced
to, all operations are "getOrRetrieveOrCreate"
I've had to forgo promise heavy development in favor of
events/state. Async communication should still use promises, that
is, things in actions use them otherwise communication occurs using
events. If a view always renders the current state, then you need a
state like "isLoading" to render a spinner. Or you need an event to
get fired then update a state on a view. I think responding from an
action with a promise may be an anti-pattern (not entirely sure).
URL changes fire the appropriate action. GET should work and be
idempotent so a URL change should generally not result in a failure.
It may however result in a redirect. I have an "authRequired"
decorator for some actions. If you aren't authenticated then we
redirect you to the login page with the target URL listed as a
redirect path.
For validation we are thinking about starting from an action, firing a "xyzModel:willSaveData", before we start; then firing either "xyzModel:didSaveData" or "xyzModel:failedSaveData" events. The store listening to these events will indicate "saving" to the views that care. It may also indicate "hasValidationError" to views that care. If you want to dismiss an error. You can fire an action from a view that indicates that the error "wasReceived", which removes the "hasValidationError" flag or optionally could do something else like clear out all validation errors. Validations are interesting because of the different styles of validation. Ideally, you could create an app that would accept most any input due the limitations imposed by your input elements. Then again, servers may disagree with those choices :/.

Dynamically adjust object names based on parameter

I'm running a script on an apache webserver on a linux box. Based on the parameter I want to change the name of variable(or set it)
The idea is that humDev(lines 11 and 14) is named humDev21 for example. Where devId is the number 21 in this example.
My script looks like this:
function getHumDev(devId){
$.ajax({
async: false,
url: "/url" + devId,
success: function(result) {
var array = result["Device_Num_" + devId].states;
function objectFindByKey(array, key, value) {
for (var i = 0; i < array.length; i++) {
if (array[i][key] === value) {
humDev = array[i].value;
}
}
return humDev;
};
objectFindByKey(array, 'service', 'some');
}
});
};
If Im looking in the wrong direction, please do let me know. Maybe its bad practice what Im trying. The reason I want to have the object a unique name is because this function is called several times by another function, based on the content of an array. But when I have the humDev object named without the number suffix to make it unique, the content of the object is getting mixed up between the different calls.
I may be off base but I am making some assumptions based on what I understand of what you are trying to do.
First, you need to understand how to do file I/O in node.js. So lets start there:
var pathToFile, //set with file path string
fs = require('fs'), //require the file i/o module API
bunchOfHumDevs = {},
fileContents; //we'll cache those here for repeated use
fs.readFile(pathToFile, function(err, result) {
if (err) {
throw new Error(); //or however you want to handle errors
} else {
fileContents = JSON.parse(result); //assumes data stored as JSON
}
});
function getHumDev(devId) {
//first make sure we have fileContents, if not try again in 500ms
if (!fileContents) {
setTimeout(function() {
getHumDev(devId);
}, 500);
} else {
var array = fileContents["Device_Num_" + devId].states,
i = array.length,
//if 'service' and 'some' are variable, make them params of
//getHumDev()
while (i--) {
if (array[i]['service'] === 'some') {
//store uniquely named humDev entry
bunchOfHumDevs['humDev' + devId.toString()] = array[i].value;
break; //exit loop once a match is found
}
}
}
return null;
}
getHumDev(21);
assuming a match is found for the devId 21, bunchOfHumdevs will now have a property 'humDev21' that is the object (value?) in question. Also, the fileContents are now cached in the program so you don't have to reopen it every time you call the function.

How to access callback on grunt task set up in initConfig

I've got an issue that I can't seem to resolve. I have tried a couple different ways, but nothing is working yet.
I am using grunt-messageformat to create my i18n localized copy. I have 2 folders with languages in them and I'd like to have grunt automatically generate the correct output for each folder (language).
The task that got me closest is this:
grunt.registerTask("ReadFolders", "Read the language folders in app/data/i18n/", function () {
// Returns an array of the paths to the language folders.
// ['app/data/i18n/en', 'app/data/i18n/key', ...]
var languageFolders = grunt.file.expand("app/data/i18n/*");
var path;
var languageName;
var i;
for (i = 0; i < languageFolders.length; i++) {
path = languageFolders[i];
languageName = path.substring(path.lastIndexOf("/") + 1, path.length);
grunt.config.set("mFormat.locale", languageName);
grunt.config.set("mFormat.inputdir", "app/data/i18n/" + languageName);
grunt.config.set("mFormat.output", "app-dist/test/js/locales/" + languageName + "/i18n.js");
grunt.task.run("messageformat:all");
}
});
This also uses the following code for my messageformat task, which is set up in initConfig:
messageformat: {
all: {
locale: "<%= mFormat.locale %>",
inputdir: "<%= mFormat.inputdir%>",
output: "<%= mFormat.output%>"
}
}
The problem is that my loop in 'readFolders' runs through twice before the messageFormat task runs, which means the task runs twice, but both times it uses the last values for the mFormat variables.
I don't see any examples of how to access a callback of a task that is set up using initConfig.
Any thoughts? Or other ideas?
Thanks
Well I didn't find a way to do what I had originally asked... But I found a good workaround that fulfills my needs. Instead of running the task for each folder, I instead rewrite the messageformat config dynamically for each language.
grunt.registerTask("ReadFolders", "Read the language folders in app/data/i18n/", function () {
// Returns an array of the paths to the language folders.
// ['app/data/i18n/en', 'app/data/i18n/key', ...]
var languageFolders = grunt.file.expand("app/data/i18n/*");
var path;
var languageName;
var locale;
var messageFormat = {};
var i = 0;
for (i = 0; i < languageFolders.length; i++) {
path = languageFolders[i];
languageName = path.substring(path.lastIndexOf("/") + 1, path.length);
locale = languageName;
if (languageName === "key") {locale = "en"; }
messageFormat[languageName] = {
locale: locale,
inputdir: "app/data/i18n/" + languageName,
output: "app-dist/test/js/locales/" + languageName + "/i18n.js"
};
}
grunt.config.set("messageformat", messageFormat);
grunt.task.run("messageformat");
});
After quite an exhaustive search, I think this is the only possible (and quite frankly, in my case, the better) solution.
Still happy to hear about any other ideas if anyone's got any.

MVC3 Custom Route has issues with Virtual Directory

I have this in my global
//custom route
routes.MapRoute(
"DownloadInstall", // Route name
"{controller}/{action}/{id}/{logonserver}", // URL with parameters
new { controller = "Software",
action = "DownloadInstall" } // Parameter defaults
);
//custom route
routes.MapRoute(
"DownloadHelp", // Route name
"{controller}/{action}/{id}/{logonserver}", // URL with parameters
new { controller = "Software",
action = "DownloadHelp" } // Parameter defaults
);
//default route
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Software", action = "Index",
id = UrlParameter.Optional } // Parameter defaults
);
and I invoke custom routes in javascript (which works great) like this:
window.location.href = '/Software/DownloadHelp/' + #Model.ID +'\/' +
getLogonServer();
However, as soon as I moved this to an IIS7 box which has a virtual directory, my default routes were smart enough to prepend with the virtual name...however my javascript based routes aren't found because the virtual directory isn't prepended.
I would try and use the Url helper if I were you, but I realize the javascript function result will be a problem.
I'm not sure if that will work, but you could try and build up your link like this:
var server = getLogonServer();
window.location.href = '#Url.Action("DownloadHelp", "Software",
new { Model.Id, logonserver = ""})' + '/' + getLogonServer();
What definitely would work is making getLogonServer() an Html helper function instead of a javascript function, but I don't know if that's an option for you.
I resolved it by using #Url.Content helper as such:
window.location.href = '#Url.Content("~/Software/DownloadInstall/")' + #Model.ID +'\/' + getLogonServer();

Categories