How can I set additional data in an action function in a Meteor Application that uses IronRouter ? See comments in emailWelcome and emailContract functions below...
Code:
EmailController = RouteController.extend({
template: 'emailPage',
waitOn: function() {
return [
Meteor.subscribe('customers'),
];
},
data: function() {
var request = Requests.findOne(this.params._id);
if (!request)
return;
var customer = Customers.findOne({'_id': request.customerId});
if (!customer)
return;
return {
sender: Meteor.user(),
recipient: Customers.findOne({_id:Session.get('customerId')})
};
},
emailWelcome: function() {
// Set var in the context so that emailTemplate = 'welcomeEmail' here
this.render('emailPage');
},
emailContract: function() {
// Set var in the context so that emailTemplate = 'contractEmail' here
this.render('emailPage');
}
});
You can get access to the data with this.getData() in your action functions:
emailWelcome: function() {
var data = this.getData(); // get a reference to the data object
data.emailTemplate = 'welcomeEmail';
this.render('emailPage');
},
emailContract: function() {
var data = this.getData(); // get a reference to the data object
data.emailTemplate = 'contractEmail';
this.render('emailPage');
}
be careful not to call this.data(), as that will regenerate the
data instead of getting you a reference to the already generated data
object.
also be careful not to call this.setData(newData) within an action as that will invalidate the old data object, initiating a reactivity reload, and lead to an infinite loop!
Related
In my split app the detail view does not bind any model.
In the component.js I instantiate a named model like this:
// creation and setup of the oData model
var oConfig = {
metadataUrlParams: {},
json: true,
defaultBindingMode : "TwoWay",
defaultCountMode : "Inline",
useBatch : false
}
// ### tab-employee ###
var oModelEmpl = new sap.ui.model.odata.v2.ODataModel("/sap/opu/odata/sap/EMP_SRV"), oConfig);
oModelEmpl.attachMetadataFailed(function() {
this.getEventBus().publish("Component", "MetadataFailedEMPL");
}, this);
this.setModel(oModelEmpl, "EMPL");
The method onSelect in der master-view controller is fired by clicking on an listitem.
onSelect: function(oEvent) {
this.showDetail(oEvent.getParameter("listItem") || oEvent.getSource());
}
This will call the method showDetail
showDetail: function(oItem) {
var bReplace = jQuery.device.is.phone ? false : true;
this.getRouter().navTo("detail", {
from: "master",
entity: oItem.getBindingContext('EMPL').getPath().substr(1),
}, bReplace);
},
In the controller of the detail-view I've these two methods for updating the binding. onRouteMatched calls bindView, where I get the error-message TypeError: oView.getModel(...) is undefined.
onRouteMatched: function(oEvent) {
var oParameters = oEvent.getParameters();
jQuery.when(this.oInitialLoadFinishedDeferred).then(jQuery.proxy(function() {
var oView = this.getView();
if (oParameters.name !== "detail") {
return;
}
var sEntityPath = "/" + oParameters.arguments.entity;
this.bindView(sEntityPath);
}, this));
},
bindView: function(sEntityPath) {
var oView = this.getView();
oView.bindElement(sEntityPath);
//Check if the data is already on the client
if (!oView.getModel().getData(sEntityPath)) {
// Check that the entity specified was found.
oView.getElementBinding().attachEventOnce("dataReceived", jQuery.proxy(function() {
var oData = oView.getModel().getData(sEntityPath);
if (!oData) {
this.showEmptyView();
this.fireDetailNotFound();
} else {
this.fireDetailChanged(sEntityPath);
}
}, this));
} else {
this.fireDetailChanged(sEntityPath);
}
},
I've tried to implement this split app relative to the template generated by WebIDE. Any idea what is missing?
As you wrote yourself, you are creating a "named Model" with the name "EMPL".
In the Controller you have to use the same name to get the Model:
this.getView().getModel("EMPL");
Likewise when calling bindElement() you have to give the model name:
// Assuming sEntityPath = "/items/0"
this.getView().bindElement("EMPL>" + sEntityPath);
I have a local storage service I've created which looks like this
import Ember from 'ember';
import ENV from 'bidr/config/environment';
const stringify = JSON.stringify;
const parse = JSON.parse;
function setItem(key, value) {
localStorage.setItem(key, stringify(value));
}
function getItem(key) {
var result = localStorage.getItem(key);
if (result) {
return parse(result);
}
}
export default Ember.Service.extend({
namespace: ENV.APP.LocalStorageKey,
user: null,
init: function() {
this.set('user',this.getItem('user'));
},
setItem: function (key, object) {
var ttlOptions = arguments[2];
if (ttlOptions) {
this._setTTLKey(key, ttlOptions);
}
this.set(key,object);
setItem(this._namespacedKey(key), object);
},
getItem: function (key) {
return getItem(this._namespacedKey(key));
},
keyExpired: function (key, now) {
var ttl = this.getItem(`_ttl_${key}`);
if (ttl) {
var expiry = new Date(ttl.lastUpdated)
.setTime(new Date(ttl.lastUpdated)
.getTime() + ttl.ttl);
now = now || new Date();
return now > expiry;
}
return true;
},
_setTTLKey: function (key, ttlOptions) {
var dateTime = new Date();
setItem(this._namespacedKey(`_ttl_${key}`),
{ttl: ttlOptions.ttl, lastUpdated: dateTime}
);
},
_namespacedKey: function (key) {
return this.get('namespace') + `.${key}`;
}
});
The user object is so I can access it directly in a template or as a computed property value.
The problem is another computed property that depends on a property of user in the service is not being notified when it gets changed. In my application controller I have an action that updates a property on the user property in the service
updateActiveEvent(eventInfo) {
var currentUserInfo = this.get('localStorage').getItem('user');
currentUserInfo.active_auction = eventInfo.eid;
this.get('localStorage').setItem('user',currentUserInfo);
},
(this comes in from a socket service) this is working properly as after this action fires I can check the browsers localstorage object and see the active_auction property of it has changed. But in my template where I am displaying that (or a computed property based on that) it's not updating.
I saw there is possibly a way I can force ember to notify of property changes but couldn't seem to get this to work, not sure if I was doing it right.
I am new in ReactJS and "reactive programming". I tried to create a dispatcher, action and store according to this project, but I don't know how to pass data to component.
In this example it doesn't work.
var data = [1, 2, 3, 4, 5];
var AppDispatcher = Kefir.emitter();
function DataActions() {
this.getAllData = function () {
AppDispatcher.emit({
actionType: "GET_ALL"
});
};
}
var Actions = new DataActions();
var getAllDataActionsStream = AppDispatcher.filter(function (action) {
return action.actionType === "GET_ALL";
}).map(function (action) {
return function (data) {
return data;
};
});
var dataStream = Kefir.merge([getAllDataActionsStream]).scan(function (prevData, modificationFunc) {
return modificationFunc(prevData);
}, {});
var Content = React.createClass({
getInitialState: function() {
this.onDataChange = this.onDataChange.bind(this);
return {componentData: []};
},
componentDidMount: function() {
dataStream.onValue(this.onDataChange);
},
componentWillMount: function(){
dataStream.offValue(this.onDataChange);
console.log(Actions.getAllData());
},
onDataChange(newData) {
this.setState({componentData: newData});
},
render: function() {
console.log(this.state);
var list = this.state.componentData.map(function (item, i) {
return (
<li key={i}>{item}</li>
);
});
return <ul>{list}</ul>;
}
});
React.render(<Content />, document.getElementById('container'));
Before I begin to answer in length I want to answer this part up front:
but I don't know how to pass data to component.
In the example you linked the author passes in the Todos into the main component using React's props, not with an action. So that is the approach I take in my example as well.
Now here is my example. I highly reccommend looking at the example and reading along to what I've written below.
var data = [ 1, 2, 3, 4, 5 ];
// This will now log all events of the AppDispatcher in the console with the prefix 'Kefer: '
var AppDispatcher = Kefir.emitter().log("Kefir: ");
function DataActions() {
// Our application has an action of emitting a random number.
this.emitNumber = function() {
AppDispatcher.emit({
actionType: "EMIT_NUMBER"
})
};
}
var Actions = new DataActions();
var emitNumberActionStream = AppDispatcher
.filter(function(action) {
return action.actionType === "EMIT_NUMBER";
})
.map(function(action) {
console.log("EMIT_NUMBER ACTION OCCURRED!!");
return Math.floor(Math.random() * (10)) + 1;
});
// Only one stream, no need to merge right now.
//var dataStream = Kefir.merge([ getAllDataActionsStream ]);
var Content = React.createClass({
getInitialState: function() {
// Set initial componentData using the data passed into this component's via props
return { componentData: this.props.data };
},
componentDidMount: function() {
// On each emitted value run the this.onDataChange function
emitNumberActionStream.onValue(this.onDataChange);
// Every second emit a number using the Actions we created earlier
setInterval(function() {
Actions.emitNumber();
}, 1000);
},
onDataChange: function(emittedNumber) {
console.log('state on change:', this.state);
// Update the state by appending the emitted number to the current state's componentData
this.setState({ componentData: this.state.componentData.concat([emittedNumber])});
console.log('updated state: ', this.state);
console.log('-----------------');
},
render: function() {
console.log('RENDER AGAIN!');
var list = this.state.componentData.map(function(item, i) {
return (
<li key={i}>{item}</li>
);
});
return <ul>{list}</ul>;
}
})
;
// Pass in initial data using props 'data={data}'
React.render(<Content data={data}/>, document.getElementById('container'));
I modified the example you gave that wasn't working so that it works and makes a little more sense (hopefully).
The Actions and Stores work like this:
Actions:
Request a number be emitted
Stores
Listen for "EMIT_NUMBER" actions and emit a random number
And the actual component runs like this:
It gets the initial 5 numbers passed into the component via props.
Once mounted it begins listening to the store and creates a setInterval that calls the action dispatcher's emitNumber() action. The interval is to show the reactivity at work, you could imagine that there was a button to press that would call emitNumber() instead.
The store observes the action dispatcher emit "EMIT_NUMBER" and emits a number.
The component observes the store emitted a number and updates the component's state.
The component observes that its state has changed and it rerenders.
I believe the issue is that you're using ES6 syntax (which is what the example was written in... notice the Readme). You'll need to either use a transpiler like Babel or convert your method(param => console.log(param)) syntax into normal JS (ie, method(function(param) { console.log(param) });).
How could one pass a parameter through the parse/fetch function?
I want to pass the variable VARIABLE_PARAMETER in the lower Initialize-part.
Otherwise I have to write three mostly identical Collections.
Thank you for you help.
app.js
//--------------
// Collections
//--------------
DiagnoseApp.Collections.Param1_itemS = Backbone.Collection.extend({
model: DiagnoseApp.Models.Param1_item,
url: 'TestInterface.xml',
parse: function (data) {
var parsed = [];
$(data).find(/*VARIABLE_PARAMETER*/).find('PARAMETER').each(function (index) {
var v_number = $(this).attr('Number');
var v_Desc_D = $(this).attr('Desc_D');
parsed.push({ data_type: v_data_type, number: v_number, Desc_D: v_Desc_D});
});
return parsed;
},
fetch: function (options) {
options = options || {};
options.dataType = "xml";
return Backbone.Collection.prototype.fetch.call(this, options);
}
});
This is the way I initialize the app:
//--------------
// Initialize
//--------------
var VARIABLE_PARAMETER = "OFFLINE";
var offline_Collection = new DiagnoseApp.Collections.Param1_itemS();
var offline_Collection_View = new DiagnoseApp.Views.Param1_itemS({collection: offline_Collection});
//VARIABLE_PARAMETER has to be passed here in fetch I guess ??
offline_Collection.fetch({
success: function() {
console.log("JSON file load was successful", offline_Collection);
offline_Collection_View.render();
},
error: function(){
console.log('There was some error in loading and processing the JSON file');
}
});
The fetch method accepts an option argument : http://backbonejs.org/#Collection-fetch
The parse method also accepts an option argument: http://backbonejs.org/#Collection-parse
These objects are actually the same. So you may write:
parse: function (data, options) {
var parsed = [];
$(data).find(options.variableParameter).find('PARAMETER').each(function (index) {
var v_number = $(this).attr('Number');
var v_Desc_D = $(this).attr('Desc_D');
parsed.push({ data_type: v_data_type, number: v_number, Desc_D: v_Desc_D});
});
return parsed;
},
Not sure I understand your question, but if you want to "pass a parameter" from fetch to parse, and if that parameter value doesn't change for a given collection, you could just store it in the collection. You could pass the parameter to fetch as an additional property in options:
fetch: function (options) {
options = options || {};
options.dataType = "xml";
this.variableParameter = options.variableParameter;
return Backbone.Collection.prototype.fetch.call(this, options);
},
And then simply retrieve it
parse: function (data) {
// do something useful with this.variableParameter
// ...
}
What's the best way to set state based on the data received from observe()?
It seems setting state via componentWillMount() won't work as observe() runs after this and the data isn't available to set state.
I'm using the observe function as advised when using Parse
E.g.:
var DragApp = React.createClass({
getInitialState: function () {
return {
activeCollection : ''
};
},
observe: function() {
return {
collections: (collectionsQuery.equalTo("createdBy", currentUser))
};
},
_setactiveCollection: function(collection) {
this.setState({
activeCollection : collection
});
},
componentWillMount: function () {
var collection = this.data.collections[0];
this._setActiveCollection(collection);
},
)}
I went the wrong way about this.
I shouldn't be storing this.data into state. I can pass it into components via render.
To get round this.data not being ready before rendering, I make use of the ParseReact function pendingQueries() inside render. E.g.
if (this.pendingQueries().length > 0) {
content = 'loading...'
} else {
content = 'hello world I am' + this.data.name
}
Try:
var DragApp = React.createClass({
observe: function() {
var collections = collectionsQuery.equalTo("createdBy", currentUser);
return {
collections: collections,
activeCollection: collections[0]
};
},
render: function () {
// do something with this.data.collections and/or this.data.activeCollection
},
)}