How can I control when the component's createContent function is fired? - javascript

I have an app which needs to check if the user has multiple accounts when it is first loaded. Most of them don't, so they should be taken straight into the main app view.
If they do have multiple accounts, they should be taken to a screen which allows them to select with which account they want to continue. The users with only one account should never see this selection page.
I have an OData call which returns the result of a backend check to see if the user has multiple accounts.
How can I dynamically set the rootView property of the component's metadata so it acts according to the behaviour defined above?
My research suggests if the rootView property is not set in the component metadata, the root view must be defined in the createContent() method.
My problem seems to stem around the asynchronous call to the OData, with createContent() executing before the OData result functions have set the variable to decide which view to see.
EDIT: I've changed the title to better reflect the likely cause of my problem. How can I keep the asynchronous calls but prevent createContent() from eventing before those calls are completed? Adding in something like the EventBus still doesn't work as it seems the application won't wait for it, and I just get an error about not being able to find the view.
My code below:
Declaration at the top of the file. By default view is the App since most users don't have multiple accounts. The multiple accounts is a rarer exception.
var sRootView = "view.App"; //default value of root view
sap.ui.core.UIComponent.extend("MY_APP.Component", {
Component.js
init: function(){
...
this.oModel = new sap.ui.model.odata.ODataModel("/odatasvc");
that = this;
var aBatch = [];
aBatch.push(this.oModel.createBatchOperation("/multiple_account_check", "GET"));
makeCall = function(){
var oDef = $.Deferred();
utils.oDataCalls.read(that.oModel, aBatch, oDef);
return oDef.promise();
};
var promise = makeCall();
var aResults = [];
promise.done(function(data) {
for (var i=0; i<data.length; i++) {
aResults.push(data);
}
that.checkAccounts(aResults[0]);
});
The createContent function.
createContent : function(){
var oView = sap.ui.view({
id : "meViewId",
viewName : sRootView,
type : "XML" });
return oView;
},
The function to check OData result for multiple accounts.
checkAccounts : function(aResults){
if(aResults.length == 1){ //one account
that.oRouter.navTo("home");
}
else if(aResults.length > 1){ //multiple accounts
sRootView = "view.multipleAccountsView"; //changed value used in createContent
that.oRouter.navTo("multipleAccounts");
}
}

You should be able to make the OData call synchronously by passing async as false to ODataModel method read. E.g:
oModel.read{"path", {async: false}};
If you implement this inside utils.oDataCalls.read, it might also require reworking the promise in that function.

Related

Javascript / meteor android return objects to templates

I have the following piece of code in my meteor app
if (Meteor.isClient) {
Meteor.startup(function(){
var onSuccess = function(acceleration){
alert( acceleration);
};
var onError = function(acceleration){
return "error";
}
var options = { frequency: 3000 }; // Update every 3 seconds
var getAcc = navigator.accelerometer.watchAcceleration(onSuccess, onError, options);
});
}
What that does is retrieve the android phones accelerometer data every 3 seconds, and on a successful poll it will show the object in an alert. This works fine.
However, I don't want this polling code to be in the startup function. I want to have more control over when this is executed
I have a template where I want to display the accelerometer values. I changed the onSuccess method in the code above to return the object instead of alerting (the rest of the startup code is the same):
var onSuccess = function(acceleration){
return acceleration;
};
My template looks like this:
Template.rawData.helpers({
acc: function(){
alert(getAcc);
return getAcc;
}
});
What I'm expecting to happen is for the accelerometer data to be stored in getAcc in the startup function, but then to return it through acc to my webpage. This does not seem to happen. The alert in the template doesn't occur either
Is there a way to access these cordova plugins from outside of the startup function? Am I just incorrectly returning the objects between the startup and template sections?
I guess my other overarching question is this: I'm not sure how to display those accelerometer values through a template if theyre gathered in the startup function, and not from a template helper
You need to have the template update reactively when the data changes. To do that set up a reactive variable that gets updated by the callback. First, install the package:
meteor add reactive-var
Then, when the template is created, create the reactive variable and start watching the callbacks:
Template.rawData.onCreated(function() {
self = this;
self.rawValue = new ReactiveVar();
self.accWatchID = navigator.accelerometer.watchAcceleration(
function(acc) { self.rawValue.set(acc); }, function() {}, { frequency: 3000 }
);
});
Your template helper can then return the value of the reactive variable, and your template will be updated whenever it changes:
Template.rawData.helpers({
acc: function() {
return Template.instance().rawValue.get();
}
});
(Note that in your original code, since the alert wasn't being called, there must be a problem in your template. Does it have the right name?)
Finally, you should stop the callback when the template is destroyed:
Template.rawData.onDestroyed(function() {
navigator.accelerometer.clearWatch(this.accWatchID);
});
Note that I've just typed that code here without testing it. You may need to fine tune it a little if it doesn't work straight away.

Javascript Scoping, Inline functions, and asynchronous operations

I'm working on a geoprocessing web application. My application will provide users with a specific set of options, the user will provide some data, and then I will process the data on the server and finally return the results. If it matters, I am using the CMV http://docs.cmv.io/en/1.3.3/ as a framework and trying to build my own plugin, but I suspect my problems are more general JS problems. Here is a pseudocode sample (note that this is pseudocode and not my actual code, which is a mess at the moment):
initializeTool: function() {
//here I am able to access my map object through this.map
//and I need it for my output
on(dom.byId("mybutton"), "click", processInput);
}
processInput: function() {
//pull user data from webpage
var userData, queries;
//launch query for all data
for(var i in userData){
queries[i] = query(userData[i]);
}
//deferredlist is from Dojo, doc here: http://dojotoolkit.org/api/?qs=1.10/dojo/DeferredList
new DeferredList(queries).then(function (results) {
//iterate over query responses and perform work
for(var i in queries){
//peform some synchronus operations
}
//and now we're done! but how do I get to my output?
}
}
The desired output in this case is a group of objects that have had various operations done on them, but are only accessible in the scope of the then() block and the inline function. My problem is that the output I am trying to use is only in the scope of the initialize function. I'm not sure what the best way to get my processed data to where I want it to be. This is a problem because the processed data is geometry information - it isn't very human readable as text, so it needs to be displayed on a map.
I've been pouring over JS scoping and looking at references to try and figure out what my issue is, but I seriously cannot figure it out.
One of the main points of promises is that then returns a promise for whatever is eventually returned inside its onFulfill handler. This is what enables you to get the outcome out of your processInput() function and into the world outside it.
So you can (and should) do this:
function processInput() {
//pull user data from webpage
var userData;
//launch query for all data
return Promise.all(userData.map(query))
.then(function (results) {
var theResult;
//iterate over query responses and perform work
results.forEach(function (result) {
//peform some synchronus operations and determine theResult
});
return theResult;
});
}
processInput().then(function (theResult) {
// do something with theResult
});

Extracting values from USGS real time water service

There must be something simple I am missing, but alas, I do not know what I do not know. Below is the code I have thus far for trying to get current streamflow conditions from the USGS.
// create site object
function Site(siteCode) {
this.timeSeriesList = [];
this.siteCode = siteCode;
this.downloadData = downloadData;
this.getCfs = getCfs;
// create reference to the local object for use inside the jquery ajax function below
var self = this;
// create timeSeries object
function TimeSeries(siteCode, variableCode) {
this.variableCode = variableCode;
this.observations = [];
}
// create observation object
function TimeSeriesObservation(stage, timeDate) {
this.stage = stage;
this.timeDate = timeDate;
}
// include the capability to download data automatically
function downloadData() {
// construct the url to get data
// TODO: include the capability to change the date range, currently one week (P1W)
var url = "http://waterservices.usgs.gov/nwis/iv/?format=json&sites=" + this.siteCode + "&period=P1W&parameterCd=00060,00065"
// use jquery getJSON to download the data
$.getJSON(url, function (data) {
// timeSeries is a two item list, one for cfs and the other for feet
// iterate these and create an object for each
$(data.value.timeSeries).each(function () {
// create a timeSeries object
var thisTimeSeries = new TimeSeries(
self.siteCode,
// get the variable code, 65 for ft and 60 for cfs
this.variable.variableCode[0].value
);
// for every observation of the type at this site
$(this.values[0].value).each(function () {
// add the observation to the list
thisTimeSeries.observations.push(new TimeSeriesObservation(
// observation stage or level
this.value,
// observation time
this.dateTime
));
});
// add the timeSeries instance to the object list
self.timeSeriesList.push(thisTimeSeries);
});
});
}
// return serialized array of cfs stage values
function getCfs() {
// iterate timeseries objects
$(self.timeSeriesList).each(function () {
// if the variable code is 00060 - cfs
if (this.variableCode === '00060') {
// return serialized array of stages
return JSON.stringify(this.observations);
}
});
}
}
When I simply access the object directly using the command line, I can access individual observations using:
> var watauga = new Site('03479000')
> watauga.downloadData()
> watauga.timeSeriesList[0].observations[0]
I can even access all the reported values with the timestamps using:
> JSON.stringify(watauga.timeSeriesList[0].observations)
Now I am trying to wrap this logic into the getCfs function, with little success. What am I missing?
I don't see anything in the code above that enforces the data being downloaded. Maybe in whatever execution path you're using to call getCfs() you have a wait or a loop that checks for the download to complete prior to calling getCfs(), but if you're simply calling
site.downloadData();
site.getCfs()
you're almost certainly not finished loading when you call site.getCfs().
You'd need to do invoke a callback from within your success handler to notify the caller that the data is downloaded. For example, change the signature of Site.downloadData to
function downloadData(downloadCallback) {
// ...
Add a call to the downloadCallback after you're finished processing the data:
// After the `each` that populates 'thisTimeSeries', but before you exit
// the 'success' handler
if (typeof downloadCallback === 'function') {
downloadCallback();
}
And then your invocation would be something like:
var watauga = new Site('03479000');
var downloadCallback = function() {
watauga.timeSeriesList[0].observations[0];
};
watauga.downloadData(downloadCallback);
That way, you're guaranteed that the data is finished processing before you attempt to access it.
If you're getting an undefined in some other part of your code, of course, then there may be something else wrong. Throw a debugger on it and step through the execution. Just bear in mind that interactive debugging has many of the same problems as interactively calling the script; the script has time to complete its download in the background before you start inspecting the variables, which makes it look like everything's hunky dory, when in fact a non-interactive execution would have different timing.
The real issue, I discovered through just starting over from scratch on this function, is something wrong with my implementation of jQuery.().each(). My second stab at the issue, I successfully used a standard for in loop. Here is the working code.
function getCfs() {
for (var index in this.timeSeriesList) {
if (this.timeSeriesList[index].variableCode === '00060'){
return JSON.stringify(this.timeSeriesList[index].observations);
}
}
}
Also, some of the stuff you are talking about #Palpatim, I definitely will have to look into. Thank you for pointing out these considerations. This looks like a good time to further investigate these promises things.

how to make a meteor template helper re-run/render after another template has rendered?

I have a template helper called {{renderNav}} in a template Nav
e.g.
Template.Nav.renderNav
and within that helper function I want to parse the rendered output of another helper within a different template
For example the helper
Template.contentWindow.content
which provides the html for
{{content}}
and my renderNav helper wants to part the html that replaces {{content}} to generate the html for
{{renderNav}}
how would I do this? right now the {{renderNav}} helper executes for or runs more quickly and so it is unable to parse the html that replaces {{content}}
#Hugo - I did the following in my code as you suggested
Template.contentWindow.rendered = function() {
debugger;
return Session.set('entryRendered', true);
};
Template.Nav.renderNav = function() {
debugger;
var forceDependency;
return forceDependency = Session.get('entryRendered');
};
When I run it, the debugger first stops when executing the renderNav helper. (Which makes sense with what I am seeing in terms of the race condition). Then contentWindow renders and I hit the breakpoint above the Session.set('entryRendered', true). But then the renderNav doesn't run again as you suggest it should. Did I misinterpret or incorrectly implement your suggestion?
You need a dependency in the template that you want to rerun. There are few possibilities, depending on what data you want to get.
For example, you can set a reactive marker in the content template that will notify renderNav that it's done with drawing.
Template.contentWidnow.rendered = function() {
...
// Set this on the very end of rendered callback.
Session.set('contentWindowRenderMark', '' +
new Date().getTime() +
Math.floor(Math.random() * 1000000) );
}
Template.renderNav.contentData = function() {
// You don't have to actually use the mark value,
// but you need to obtain it so that the dependency
// is registered for this helper.
var mark = Session.get('contentWindowRenderMark');
// Get the data you need and prepare for displaying
...
}
With further information you've provided, we can create such code:
content.js
Content = {};
Content._dep = new Deps.Dependency;
contentWindow.js
Template.contentWidnow.rendered = function() {
Content.headers = this.findAll(':header');
Content._dep.changed();
}
renderNav.js
Template.renderNav.contentData = function() {
Content._dep.depend();
// use Content.headers here
...
}
If you want the navigation to be automatically rebuilt when contentWindow renders, as Hubert OG suggested, you can also use a cleaner, lower level way of invalidating contexts:
var navDep = new Deps.Dependency;
Template.contentWindow.rendered = function() {
...
navDep.changed();
}
Template.renderNav.contentData = function() {
navDep.depend();
// Get the data you need and prepare for displaying
...
}
See http://docs.meteor.com/#deps for more info.
If, on the other hand, you want to render another template manually, you can call it as a function:
var html = Template.contentWindow();
The returned html will not be reactive. If you need reactivity, use:
var reactiveFragment = Meteor.render(Template.contentWindow);
See the screencasts at http://www.eventedmind.com/ on Spark and reactivity for details on how this works.
UPDATE
To add a rendered fragment to your DOM:
document.body.appendChild(Meteor.render(function () {
return '<h1>hello</h1><b>hello world</b>';
}));
You can also access the rendered nodes directly using the DOM API:
console.log(reactiveFragment.childNodes[0]);

Better way than mediator pattern for decoupling widgets in this situation?

I’m trying to figure out which pattern to follow in a certain situation. I have web app that consists of several main widgets that interact with each other somehow. The widgets follow the module pattern.
To let code speak:
MyApp.Widgets.MainLeftBox = (function(){
var self = {};
self.doSomething = function(){
var certainState = MyApp.Widgets.MainRightBox.getCertainState();
if (certainState === 1){
console.log(‘this action happens now’);
}
else {
console.log(‘this action can’t happen because of a certain state in My.App.Widgets.MainRightBox’);
}
}
return self;
})();
As you can see, I have tight coupling here. As we all know, tight coupling is evil. (Except when you have found the one and only! ;-))
I know a lot of decoupling can be achieved by following a pub-sub / custom event pattern. But that’s better suited for situations were A starts something and B can react upon. But I have a situation where A starts something independently but needs to check a certain state from B to proceed.
As I’m striving for maintainability, I’m looking for a way out of this hell.
What first came to my mind is the mediator pattern.
But still, my code would look like this:
MyApp.Widgets.MainLeftBox = (function(mediator){
var self = {};
self.doSomething = function(){
var certainState = mediator.getCertainState();
if (certainState === 1){
console.log(‘this action happens now’);
}
else {
console.log(‘this action can’t happen because of a certain state in mediator’);
}
}
return self;
})(MyApp.Mediator);
This is a little better, because Widgets don't communicate directly but indirectly through the mediator.
However, I still feel that I'm doing it wrong and there must be a better way to achieve decoupling the widgets from each other.
EDIT
Let me sum things up so far!
In general, I do like the MVC approach of separating the views! However, think of this example more like complex modules. Those doesn't really have to be "boxes" in a visual sense. It's just easier to describe this way.
Another given fact should be, that A starts an action independently and needs to check for some state then. It can't subscribe to B's state change and provide the action or doesn't. It has to be like A starts it independently and then needs to check a certain state. Think of this as some complex operation that B needs be asked for.
So I came up with a mixture of custom events/callback/mediator approach and there are some things that I really like about it.
1.) A module doesn't know about any other module
2.) A module doesn't know about a mediator neither
3.) A module that depends on some external state does only know that it depends on some external state - not more
4.) A module really doesn't care who will provide this certain state
5.) A module can determine if that certain state has been provided or not
6.) The request pipeline is straight. In other words the module is the starter of this operation. it doesn't just subscribe to a state change event (Remember A starts the action and then needs a state from B (or somewhere)
I posted some example code here and also provide a jsFiddle: http://jsfiddle.net/YnFqm/
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script>
</head>
<body>
<div id="widgetA"></div>
<div id="widgetB"></div>
<script type="text/javascript">
var MyApp = {};
(function (MyApp){
MyApp.WidgetA = function WidgetA(){
var self = {}, inner = {}, $self = $(self);
//init stuff
inner.$widget = $('#widgetA');
inner.$button = $('<button>Click Me!</button>')
.appendTo(inner.$widget)
.click(function(){self.doAction();});
self.doAction = function(){
//Needs state from WidgetB to proceed
/* Tight coupling
if (MyApp.WidgetB.getState() == 'State A'){
alert('all fine!');
}
else{
alert("can't proceed because of State in Widget B");
}
*/
var state;
$self.trigger('StateNeeded',function(s){state = s});
if (state == 'State A'){
alert('all fine!');
}
else{
alert("can't proceed because of State in Widget B");
}
};
return self;
};
MyApp.WidgetB = function WidgetB(){
var self = {}, inner = {};
//init stuff
inner.$widget = $('#widgetB');
inner.$button = $('<button>State A</button>')
.appendTo(inner.$widget)
.click(function(){
var buttonState = inner.$button.text();
if (buttonState == 'State A'){
inner.$button.text('State B');
}
else{
inner.$button.text('State A');
}
});
self.getState= function(){
return inner.$button.text();
};
return self;
};
MyApp.Mediator = (function(){
var self = {}, widgetA, widgetB;
widgetA = new MyApp.WidgetA();
widgetB = new MyApp.WidgetB();
$(widgetA).bind('StateNeeded', function(event, callback){
//Mediator askes Widget for state
var state = widgetB.getState();
callback(state);
});
return self;
})();
})(MyApp);
</script>
</body>
</html>
You should checkout a great article about large scale JS apps presented by Addy Osmani Patterns For Large-Scale JavaScript Application Architecture and here is a code sample Essential js design patterns
You can still go with the mediator, but implement your business logic inside it. So, instead of mediator.getCertainState(), have a method mediator.canTakeAction() which knows about the widget(s) to query, and determine if the action is allowed.
This will still end up with a mediator which knows the widgets to query, of course. But since we've offloaded the business logic inside the mediator, I think it is OK for it to know of such things. It may even be the entity that creates these widgets. Alternatively, you can use some sort of registration mechanism where you tell your mediator which widget is used for what role when you create them.
EDIT: Providing an example in the spirit of the given code samples.
MyApp.DeleteCommand=(function(itemsListBox, readOnlyCheckBox) {
var self = {};
self.canExecute = function() {
return (not readOnlyCheckBox.checked()) && (itemsListBox.itemCount() > 0);
}
return self;
})(MyApp.Widgets.ItemsList, MyApp.Widgets.ReadOnly);
You can take this two steps further:
Register to state changed events of the source widgets, and update a local cache of the canExecute every time a state change occurs on one of the source widgets.
Also take a reference to a third control (say, to the delete button), and enable or disable the button according to the state.
Assuming I'm understanding the nature of a "box" as a box that's visible on your page, then the box should render a view that represents a state of your application or some piece of it -- the underlying state itself should be maintained by an object that's separate from the view that represents that state in the UI.
So, for example, a box view might render a view of a Person, and the box would be black when the person was sleeping and white when the person was awake. If another box on your was responsible for showing what the Person was eating, then you might want that box to only function when the person was awake. (Good examples are hard and I just woke up. Sorry.)
The point here is that you don't want views interrogating each other -- you want them to care about the state of the underlying object (in this case, a Person). If two views care about the same Person, you can just pass the Person as an argument to both views.
Chances are good that your needs are a tad more complicated :) However, if you can think about the problem in terms of independent views of "stateful objects", rather than two views that need to care directly about each other, I think you'll be better off.
Why can't you use pub-sub model in the following way
LeftBox issues a getStateFromRightBox event.
RightBox has getStateFromRightBox subscriber, which, issues sendStateToLeftBoxAndExecute event with the stateData
LeftBox has a sendStateToLeftBoxAndExecute subscriber which extracts stateData and executes the action conditionally.
A Few Potential Options
I would still recommend using a Mediator -- however, if you're more of an Inheritance fan, you may want to play around with the Template Method, State or Strategy, and Decorator Patterns -- since JavaScript does not have interfaces, these might be useful.
The latter approach might allow you to categorize your procedures into more manageable strategies, however, I'll go on to cover the Mediator since it makes the most sense [to me] in this situation.
You can implement it as EDM (Event-Driven Mediation) or as a classic Mediator:
var iEventHub = function iEventHub() {
this.on;
this.fire;
return this;
};
var iMediator = function iMediator() {
this.widgetChanged;
return this;
};
The only thing I can really advise is to break down your procedures to give Mediator a chance to have a say during the process. The mediation could look more like this:
var Mediator = function Mediator() {
var widgetA = new WidgetA(this)
, widgetB = new WidgetB(this);
function widgetChanged(widget) {
identifyWidget(widget); // magical widget-identifier
if (widgetA.hasStarted) widgetB.isReady();
if (widgetB.isReady) widgetA.proceed("You're proceeding!");
}
return this;
};
var WidgetA = function WidgetA(director) {
function start() {
director.widgetChanged(this);
}
function proceed(message) {
alert(message);
}
this.start = start;
this.proceed = proceed;
return this;
};
var WidgetB = function WidgetB(director) {
function start() {
this.iDidMyThing = true;
director.widgetChanged(this);
}
function isReady() {
return iDidMyThing;
}
this.iDidMyThing = false;
this.start = start;
this.isReady = isReady;
return this;
};
Basically, WidgetA has to get permission from Mediator to proceed, as Mediator will have the high-level view on state.
With the Classic Mediator, you'll likely still need to call director.widgetChanged(this). However, the beauty of using EDM is that you don't necessarily couple to Mediator, itself, but all modules implement an iEventHub interface or couple to a common hub. Alternatively, you can modify the classic Mediator to aid in Module Authorization by refactoring the widgetChanged method:
// Mediator
function widgetChanged(ACTION, state) {
var action = actionMap[ACTION || 'NO_ACTION_SPECIFIED'];
action && action.call && action.call(this, state);
}
// WidgetX
const changes = this.toJSON();
director.widgetChanged('SOMETHING_SPECIFIC_HAPPENED', changes);
I think you're very close -- I hope this helps.

Categories

Resources