I've defined a module (module1) which is supposed to load the value of a property asynchronously. How can I use this property in my app as soon as it is defined and only after it is defined?
My setup (simplified)
v1
app.js
require(['module1'], function(mod) {
document.getElementById("greeting").value = mod.getPersonName();
});
module1.js
define(['jquery'], function($) {
_person;
$.get('values/person.json')
.done(function(data) {
_person = data
});
return {
getPersonName: function() { return _person.name; }
}
values/person.json
{ name: 'John Doe', age: 34 }
This only works if the GET happens nearly instantaneously, otherwise it fails because _person is undefined when getPersonName is called.
v2
To counter this, I figured I would register a callback to notify the app when person was loaded.
app.js
require(['module1'], function(mod) {
mod.onPersonLoaded(function() {
document.getElementById("greeting").value = mod.getPersonName();
});
});
module1.js
define(['jquery'], function($) {
_person;
_onLoaded;
$.get('values/person.json')
.done(function(data) {
_person = data;
_onLoaded();
});
return {
getPersonName: function() { return _person.name; },
onPersonLoaded: function(cb) { _onLoaded = cb; }
}
}
This works if the GET is slow, however, if it's quick _onLoaded is undefined when .done() is called.
Is there a good way to use _person values in app.js as soon as they are defined and only once they are defined?
I'm using RequireJS, but my question is generally applicable to AMD.
Edit
In simplifying my example, I removed a layer which may be important. I'm using RactiveJS for the UI.
Setup (slightly less simplified)
app.js
require(['Ractive', 'module1'], function(Ractive, mod) {
var ractive = new Ractive({
...
data : {
name: mod.getPersonName()
}
});
ractive.observe(...);
ractive.on(...);
});
Edit 2
My current solution, subject to change. Register a callback that notifies app.js when person is loaded. Callback is called immediately if person is already loaded when callback is registered.
app.js
require(['Ractive', 'module1'], function(Ractive, mod) {
var ractive = new Ractive({
...
data : {}
});
mod.watchPerson(function() {
ractive.set('person.name', mod.getPersonName());
});
ractive.observe(...);
ractive.on(...);
});
module1.js
define(['jquery'], function($) {
_person;
_onLoaded;
$.get('values/person.json')
.done(function(data) {
_person = data;
try {
_onLoaded();
} catch (e) {
// that was fast!
// callback will be called when it is registered
});
return {
getPersonName: function() { return _person.name; },
watchPerson: function(cb) {
_onLoaded = cb;
if(_person != null) {
_onLoaded();
}
}
}
}
Promises are a good choice here because callbacks are always called asynchronously - you never encounter that confusing situation where _onLoaded() gets called before it's designed.
Unfortunately, jQuery's promise implementation doesn't adhere to the Promises/A+ specification, so you don't get that guarantee. If possible, you could use an alternative AJAX library like Reqwest, and do something like
app.js
define(['module1'], function (mod) {
mod.then(function(person) {
// do something with person.name
}
});
module1.js
define(['reqwest'], function (reqwest) {
return reqwest('values/person.json');
});
Using the text loader plugin
Another option, since you're already using AMD, would be to use a loader plugin, such as text:
app.js
define(['module1'], function (person) {
// do something with person.name
});
module1.js
define(['text!values/person.json'], function (personJSON) {
return JSON.parse(personJSON);
});
Using the text loader plugin
In fact there's even a JSON plugin, so you could do away with module1 entirely in this example situation:
app.js
define(['json!values/person'], function (person) {
// do something with person.name
});
This is how I would do this. Basically, it is not much different from your V2, but it adds more incapsulation.
app.js
require(['module1'], function(mod) {
mod.loadPerson(function(person) {
document.getElementById("greeting").value = person.getPersonName();
});
});
module1.js
define(['jquery'], function($) {
return {
loadPerson : function(callback) {
$.get('values/person.json').done(function(data) {
_person = data;
callback({
getPersonName: function() { return _person.name; }
});
});
}
}
}
You may also use promises promises instead of simple callback.
Related
I'm working on a UI automation project using Intern js . And I'm implementing it using page object model . I have a simple scenario where user enters credentials in login page and is navigated to welcome page.
In my script i have a 'doLogin()' function in LoginPage which is responsible for entering credentials and clicking submit button.
Now the problem is i want doLogin() to return WelcomePage and i'm unable to figure out the way to do this.
Heres my code setUp:
LoginPage.js
define([],
function () {
function LoginPage(remote) {
this.remote = remote;
}
LoginPage.prototype = {
constructor: LoginPage,
// Login Page related Methods
doLogin: function(username,password){
this
.findById(json.locators.username).
.type(username).end()
.findById(json.locators.password)
.type(username).end()
.findByXpath(json.locators.sumit).click().end()
.sleep(1000).then(function(){
return welcomePage// this is not working
})
}
};
return LoginPage;
});
WelcomePage.js
define([],
function () {
function WelcomePage(remote) {
this.remote = remote;
}
WelcomePage.prototype = {
constructor: WelcomePage,
doSomething: function(){
//welcome page related method
}
};
return WelcomePage;
});
Now what i actually want to achive is to do something like:
loginpage.doLogin(usrname,password).doSomething();
can somebody help on this??
A more flexible way to implement page objects is to make them groups of helper functions (like Leadfoot's pollUntil) rather than Command-like objects.
Using that model, your LoginPage object might look like:
define([], function () {
return {
doLogin: function (username, password) {
return function () {
return this.parent
.findById(json.locators.username).
.type(username).end()
.findById(json.locators.password)
.type(username).end()
.findByXpath(json.locators.sumit).click().end()
.sleep(1000).then(function(){
return welcomePage// this is not working
});
}
}
};
});
WelcomePage might look like
define([], function () {
return {
doSomething: function () {
return function () {
return this.parent
// more stuff
}
}
};
});
You can call this.parent in the returned function because when the helper function is called (as a Command then callback) it's context will be set to the Command that's calling it.
You would use them like
define([
'./WelcomePage',
'./LoginPage',
// ...
], function (
welcomePage,
loginPage,
// ...
) {
// ...
this.remote
.get('some page')
.then(loginPage.doLogin('username', 'password'))
.then(welcomePage.doSomething())
// other stuff
// ...
});
I'm using generator-react-webpack to create a React web app. This web app relies on JSON feeds - one of which is hosted on a CDN that does not support JSONP and the CDN url is a subdomain of the webapp. Is there any way to return the JSON data from within the React Component?
Basic React Component:
var AppComponent = React.createClass({
loadData: function() {
jQuery.getJSON(jsonFile.json?callback=?)
.done(function(data) {
console.log(data);
}.bind(this));
},
render: function(){
return ( ... );
}
});
I've tried a few solutions, and have come to the conclusion that I need to define my own callback on the JSON file like so:
JSON:
handleData({
"data": "hello World"
})
Is there a way for the handleData callback to be defined in the react component, or the response accessed from the react component? Any thoughts as to how I can get this to work are much appreciated. Thanks!
This looks like an odd way to do things, especially the part where you're using jQuery. That's a client-side utility to overcome not knowing where everything is and not having direct access to your elements. It makes no sense to use it when you're using React weith Webpack for bundling: React already knows where everything is (using refs) and Webpack means you can just use regular universal Node modules for everything that you need to do.
I'd recommend using something like, using request or a similar universal fetch API:
// loadData.js
var request = require('request');
var loadData = function(urlYouNeed, handler) {
request(urlYouNeed, function(error, response, body) {
if (error) {
return handler(error, false);
}
// do anything processing you need on the body,
var data = process(body);
handler(false, data);
};
So: just a module you can require in any component you define with require('./loadData'). And then in your actual component you do this:
var loadData = require('./loadData');
var AppComponent = React.createClass({
getDefaultProps: function() {
return {
jsonURL: "cdn://whateverjson.json"
};
},
getInitialState: function() {
loadData(this.props.jsonURL, this.updateData);
return {
data: []
}
},
updateData: function(err, data) {
if (err) {
return console.error(err);
}
data = secondaryEnsureRightFormat(data);
this.setState({ data: data });
},
render: function(){
var actualThings = this.state.data.map((entry, pos) => {
return <Whatever content={entry} key={entry.dontUseThePosVariableUpThere}/>
});
return (
<div>
...
{actualThings}
...
</div>
);
}
});
Much cleaner.
If I understand correctly the question, you only have to change your loadData this way :
loadData: function() {
var c = this
jQuery.getJSON(jsonFile.json?callback=?)
.done(function(data) {
c.handleData(data)
});
},
handleData: function(data) {
/* Implement here the function to handle the data */
},
I'm developing by Openui5 a portal. My portal have 2 apps. I I have organized the code in folders:
root
|
|____app1
|____app2
In app1 folder I have util1.js with a function getInfo() and in app2 folter I have util2.js with function getInfo() (same name).
Now my portal, at root login, load all js files in all subfolders, and only the last loaded getInfo() function work (correct???)
I think in the future to dynamically load the js file of appX only when the user enter in the appX.
In this way it work correctly??
Don't make your getInfo functions global. (This would be good advice even if you only had one of them, the global namespace is very crowded.)
Instead, ideally, have a single global for your portal's apps, perhaps called Apps, which is an object. Then have each app add itself to that object as a property, using the app's name. So:
portal.js:
var Apps = {};
app1.js:
Apps.App1 = {
getInfo: function() {
// ...
},
somethingElse: function() {
// ...
}
// ...and so on...
};
app2.js:
Apps.App2 = {
getInfo: function() {
// ...
},
somethingElse: function() {
// ...
}
// ...and so on...
};
Or you might go further and have an app registration function:
portal.js:
var MyPortal = {
apps: {},
register: function(name, app) {
if (this.apps.hasOwnProperty(name)) {
throw new Error("There is already an app called '" + name + "' registered.");
}
this.apps[name] = app;
}
};
app1.js:
MyPortal.register("App1", {
getInfo: function() {
// ...
},
somethingElse: function() {
// ...
}
// ...and so on...
});
app2.js:
MyPortal.registerApp("App2", {
getInfo: function() {
// ...
},
somethingElse: function() {
// ...
}
// ...and so on...
});
I am trying to implement a service inside of my WinJS Windows 8 App, the service needs to call winjs httpClient. I want my service to return a promise while it waits for the promise returned by httpClient . My service code is as follows
(function () {
"use strict";
var _httpClient = new Windows.Web.Http.HttpClient();
var _infoUri = new Windows.Foundation.Uri("https://news.google.com/");
var _getLatestInfo = function () {
return WinJS.Promise(function (completeCallback, errorCallback, progressCallback) {
// invoke the httpclient here
_httpClient.getAsync(_infoUri)
.then(function complete(result) {
completeCallback(result);
}, function error(result) {
errorCallback(result);
}, function progress(result) {
progressCallback(result);
});
});
};
WinJS.Namespace.define("DataService", {
getLatestInfo: _getLatestInfo
});
})();
And I call my service method as follows
(function () {
"use strict";
WinJS.UI.Pages.define("/pages/home/home.html", {
// This function is called whenever a user navigates to this page. It
// populates the page elements with the app's data.
ready: function (element, options) {
// TODO: Initialize the page here.
DataService.getLatestInfo().then(function () { }, function () { }, function () { });
}
});
})();
This does not work and I get an error like this
Exception was thrown at line 2018, column 13 in ms-appx://microsoft.winjs.2.0/js/base.js
0x800a01b6 - JavaScript runtime error: Object doesn't support property or method '_setState''
I tried simplifying my service as follows with no luck.
(function () {
"use strict";
var _getLatestInfo = function () {
return WinJS.Promise(function (a, b, c) { });
};
WinJS.Namespace.define("DataService", {
getLatestInfo: _getLatestInfo
});
})();
I don't know what the error is trying to tell me and how to correct this.
I just figured it out, I was missing the 'new' just before WinJS.Promise. Since WinJS.Promise is a class it apparently need to be newed. The error message only confused me more.
I'm trying to use requireJS in an existing project. I have an App module that has information about current locale, url ... and want load another module if current locale is something special:
// App.js
define([], function() {
return {
setEnv: function(obj) { //obj: {locale:'en'}
this.env = obj;
that = this;
if( this.env.locale == 'fa' )
require(['locale/fa'], function(fa) {
that.number = fa.number
};
else
this.number = function( n ) { return n+'' }
}
}
});
Locale files are like:
// locale/fa.js
define([], function() {
var enToFaDigit(n) { // a function converts a number to persian representation };
// more stuff
return {
number: enToFaDigit
};
});
Now the problem is i don't know when App module loaded number method. If i was sure locale/fa module should load at some point, i could wrap it using require(['locale/fa'] or use shim. Currently I'm using old blocking way to load appropriate locale/*.js and PHP to choose right file, without problem. But i want know how requireJS programmers write similar code. Probably there is some way better than this code:
require(['app'], function(App) {
if(App.env.locale == 'fa') {
require(['locale/fa'], function(fa) {
// 8-S
}
}
});
This sounds like a case for the i18n plugin! I believe you are allowed to define functions as well as strings. I would define a bundle like so:
//nls/formatters.js
define({
root: {
currency: function(num) {
// implementation for default currency format (en-US often)
}
},
"fa": true
})
And here's your Persian override:
//nls/fa/formatters.js
define({
currency: function(num) {
// implementation for farsi currency format
}
})
In your app.js file:
require(['app','i18n!nls/formatters'], function(App, Formatters) {
Formatters.currency(5.00); // works!
});