Mink+Behat: missing session - behat

I was just trying to follow the http://docs.behat.org/cookbook/behat_and_mink.html tutorial
I installed Mink+Behat using the following composer.json:
"require": {
"behat/behat": "2.4.*#stable",
"behat/mink": "1.4.*#stable",
"behat/mink-extension": "*",
"behat/mink-goutte-driver": "*",
"behat/mink-selenium2-driver": "*"
},
"minimum-stability": "dev",
"config": {
"bin-dir": "bin/"
}
}
I did the init that created the features directory.
bin/behat --init
I created a search.features in the features directory:
Feature: Search
In order to see a word definition
As a website user
I need to be able to search for a word
Scenario: Searching for a page that does exist
Given I am on "/wiki/Main_Page"
When I fill in "search" with "Behavior Driven Development"
And I press "searchButton"
Then I should see "agile software development"
Scenario: Searching for a page that does NOT exist
Given I am on "/wiki/Main_Page"
When I fill in "search" with "Glory Driven Development"
And I press "searchButton"
Then I should see "Search results"
I updated the features/bootstrap/FeatureContext.php to extend MinkContext.
If i run bin/behat -dl i see the following definitions loaded:
Given /^(?:|I )am on (?:|the )homepage$/
When /^(?:|I )go to (?:|the )homepage$/
Given /^(?:|I )am on "(?P<page>[^"]+)"$/
When /^(?:|I )go to "(?P<page>[^"]+)"$/
When /^(?:|I )reload the page$/
When /^(?:|I )move backward one page$/
When /^(?:|I )move forward one page$/
When /^(?:|I )press "(?P<button>(?:[^"]|\\")*)"$/
When /^(?:|I )follow "(?P<link>(?:[^"]|\\")*)"$/
When /^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with "(?P<value>(?:[^"]|\\")*)"$/
When /^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with:$/
When /^(?:|I )fill in "(?P<value>(?:[^"]|\\")*)" for "(?P<field>(?:[^"]|\\")*)"$/
When /^(?:|I )fill in the following:$/
When /^(?:|I )select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/
When /^(?:|I )additionally select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/
When /^(?:|I )check "(?P<option>(?:[^"]|\\")*)"$/
When /^(?:|I )uncheck "(?P<option>(?:[^"]|\\")*)"$/
When /^(?:|I )attach the file "(?P[^"]*)" to "(?P<field>(?:[^"]|\\")*)"$/
Then /^(?:|I )should be on "(?P<page>[^"]+)"$/
Then /^(?:|I )should be on (?:|the )homepage$/
Then /^the (?i)url(?-i) should match (?P<pattern>"([^"]|\\")*")$/
Then /^the response status code should be (?P<code>\d+)$/
Then /^the response status code should not be (?P<code>\d+)$/
Then /^(?:|I )should see "(?P<text>(?:[^"]|\\")*)"$/
Then /^(?:|I )should not see "(?P<text>(?:[^"]|\\")*)"$/
Then /^(?:|I )should see text matching (?P<pattern>"(?:[^"]|\\")*")$/
Then /^(?:|I )should not see text matching (?P<pattern>"(?:[^"]|\\")*")$/
Then /^the response should contain "(?P<text>(?:[^"]|\\")*)"$/
Then /^the response should not contain "(?P<text>(?:[^"]|\\")*)"$/
Then /^(?:|I )should see "(?P<text>(?:[^"]|\\")*)" in the "(?P<element>[^"]*)" element$/
Then /^(?:|I )should not see "(?P<text>(?:[^"]|\\")*)" in the "(?P<element>[^"]*)" element$/
Then /^the "(?P<element>[^"]*)" element should contain "(?P<value>(?:[^"]|\\")*)"$/
Then /^the "(?P<element>[^"]*)" element should not contain "(?P<value>(?:[^"]|\\")*)"$/
Then /^(?:|I )should see an? "(?P<element>[^"]*)" element$/
Then /^(?:|I )should not see an? "(?P<element>[^"]*)" element$/
Then /^the "(?P<field>(?:[^"]|\\")*)" field should contain "(?P<value>(?:[^"]|\\")*)"$/
Then /^the "(?P<field>(?:[^"]|\\")*)" field should not contain "(?P<value>(?:[^"]|\\")*)"$/
Then /^the "(?P<checkbox>(?:[^"]|\\")*)" checkbox should be checked$/
Then /^the checkbox "(?P<checkbox>(?:[^"]|\\")*)" (?:is|should be) checked$/
Then /^the "(?P<checkbox>(?:[^"]|\\")*)" checkbox should not be checked$/
Then /^the checkbox "(?P<checkbox>(?:[^"]|\\")*)" should (?:be unchecked|not be checked)$/
Then /^the checkbox "(?P<checkbox>(?:[^"]|\\")*)" is (?:unchecked|not checked)$/
Then /^(?:|I )should see (?P<num>\d+) "(?P<element>[^"]*)" elements?$/
Then /^print current URL$/
Then /^print last response$/
Then /^show last response$/
But I try to actually test the search.features , i get the error:
PHP Fatal error: Call to a member function getSession() on a non-object in /home/nishant/behat/vendor/behat/mink-extension/src/Behat/MinkExtension/Context/RawMinkContext.php on line 103
I did a Google of what the error could be, but didnot find anything more useful that telling to put the behat.yml in the right place.
My bin/behat -> ../vendor/behat/behat/bin/behat is this symlink and i have tried putting behat.yml in vendor/behat/behat/behat.yml. content on behat.yml
default:
extensions:
Behat\MinkExtension\Extension:
base_url: http://en.wikipedia.org
goutte: ~
selenium2: ~
Any help would be much appreciated!!
If I go to vendor/behat/behat and create the search.feature and run bin/behat I get the error:
[ReflectionException]
Class Guzzle\Http\Client does not exist

Ok.. i managed to fix my problem !
Added these lines in my __construct() method in features/bootstrap/FeatureContext.php
$clientOptions = array();
$client = new \Behat\Mink\Driver\Goutte\Client();
$client->setClient(new \Guzzle\Http\Client('', $clientOptions));
$driver = new \Behat\Mink\Driver\GoutteDriver($client);
Also updated my composer.json to
{
"require": {
"behat/behat": "2.4.*#stable",
"behat/mink": "1.4.*#stable",
"behat/mink-extension": "*",
"behat/mink-goutte-driver": "1.0.*",
"behat/mink-selenium2-driver": "*",
"guzzle/http": "*"
},
"minimum-stability": "dev",
"config": {
"bin-dir": "bin/"
}
}

Related

Teams Message Extension: Embedding Content from Tab App into Task Module with Authentication

I have created a Tab App in Teams. Now I want to make a dialog from one tab accessible via a Message Extension App. This works partially now through embedding the contentUrl of the specific tab as an iFrame in the Task Module of the Message Extension like here: https://learn.microsoft.com/en-us/microsoftteams/platform/messaging-extensions/how-to/action-commands/create-task-module?tabs=dotnet#create-a-task-module-with-an-embedded-web-view
. The issue is the authentication. Api Calls won't work and the dialog is not able to retreive or send data.
In the manifest.json of the Tab App are the contentUrls of the tabs in the "staticTabs" section:
"staticTabs": [
{
"entityId": "dashboard",
"name": "Dashboard",
"contentUrl": "https://cdne-stcsfeedbackuidev.azureedge.net/tabs/dashboard.html?app.locale={locale}&page.subPageId={subEntityId}&app.theme={theme}",
"scopes": [
"personal"
]
}
],
I gave the dialog a specific Route via React Router so that you can access the dialog via subPageId. This works fine.
The Problem is, if you access the contentUrl, you won't be authenticated and API calls to the Graph API and own API won't work. This issue does not go away if I embed the tab via contentUrl in a Task Module for the Message Extension to give it a Teams Context:
public async handleTeamsMessagingExtensionFetchTask(
context: any,
action: any
): Promise<any> {
return {
task: {
type: 'continue',
value: {
width: 925,
height: 925,
title: 'Feedback Dialog',
url: "https://2e70-37-201-241-91.ngrok.io/feedbackDialog.html",
fallbackUrl: "https://cdne-stcsfeedbackuidev.azureedge.net/tabs/dashboard.html?page.subPageId=feedbackDialog"
}
}
};
}
Directly embedding the url like in "fallbackUrl" will result in an empty Task Module so I embedded the Url in a like this in feedbackDialog.html:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Hello World Feedback!</title>
</head>
<body>
<script src="https://statics.teams.microsoft.com/sdk/v1.7.0/js/MicrosoftTeams.min.js"></script>
<script>
microsoftTeams.initialize();
microsoftTeams.getContext((context) => {
console.log(context);
microsoftTeams.authentication.getAuthToken({
successCallback: (token) => {
console.log(token);
},
failureCallback: (reason) => {
console.error(reason);
}
});
});
</script>
<div style="padding:50px;">
<iframe id="feedbackDialog" width="800" height="800", frameBorder="0"
src="https://cdne-stcsfeedbackuidev.azureedge.net/tabs/dashboard.html?page.subPageId=feedbackDialog"></iframe>
</div>
</body>
</html>
The dialog gets displayed in the Task Module this way like if I open the contentUrl in the browser directly. But the Authentication does not work. The Teams context can be retreived but all API calls return with the Error: "Error: SDK initialization timed out".
Is there a simple way for me to authenticate the user here because this runs in Teams as a MessageExtension App and the embedded content in the Task Module comes from a Teams Tab App. Or do I need to manually implement something using MSAL?

Is it possible to add a custom Cloud file service (like OneDrive/Dropbox) to Outlook.com (Office 365)

I've seen that when you add an Attachment in Outlook, you can automaticly add an attachment from a cloud service.
Is there a possible way to add a custom entry to this list?
I offer to you to use OneDrive file picker , with this you can simply add button to your add-in html and then it's open the log-in page and after user is log-in he could choose the files from one drive.
The steps wrote in the link but I write the main steps here:
1.register your app in Microsoft Application Registration Portal .
2.Add the js refrence in your js file of your add-in:
<script type="text/javascript" src="https://js.live.net/v7.0/OneDrive.js">
</script>
3.Add button to your add-in html page:
<button class="oneDriveButton" id="btnOneDrive" ><img src="https://js.live.net/v5.0/images/SkyDrivePicker/SkyDriveIcon_white.png" style="margin-right: 10px; height: 20px;">Open from OneDrive</button>
4.In your js file of add-in open the picker:
$('#btnOneDrive').click(function () {
var odOptions = {
clientId: "your client id from your app registration ",
action: "download",
multiSelect: true,
openInNewWindow: true,
linkType: "query",
advanced: { redirectUri: "your redirect uri from app registration" },
success: function (files) {
},
cancel: function () { /* cancel handler */
},
error: function (e) { /* error handler */
}
};
OneDrive.open(odOptions); });
Put attention:
Your clientId and your redirectUri must be equal to this you
set when you register your app in the first step.
You could change the option by what you want ,look here under
picker options .
Thats all , you get the files in sucess handler function and you could do whatever you want with them.
Good luck!

Removing Checkout Steps in Sylius

I'm working on removing both the shipping and the payment method steps from checkout in Sylius. There is a guide on how to remove the shipping step from checkout included in the Sylius docs here: http://docs.sylius.org/en/latest/cookbook/checkout.html
I've followed this guide and made additional changes to remove the payment method step as well (my shop will always use one payment method, no need for the user to select this). What's happening is that when I click next on the 'Address' step, I'm taken to the 'Complete' step, but then I'm immediately redirected back to the 'Address' step again, without error. My presumption is that I'll need to make the system aware of which shipping and which payment methods are to be used, but I don't see that code in the Sylius documentation anywhere.
Here are my changes:
app/Resources/SyliusCoreBundle/config/app/state_machine/sylius_order_checkout.yml:
states:
cart: ~
addressed: ~
completed: ~
transitions:
address:
from: [cart, addressed]
to: addressed
complete:
from: [payment_selected]
to: completed
I then ran this command, as suggested, to verify the state machine updates:
php bin/console debug:config winzou_state_machine
I can successfully see the two steps are removed in my sylius_order_config:
sylius_order_checkout:
class: Sylius\Component\Core\Model\Order
property_path: checkoutState
graph: sylius_order_checkout
state_machine_class: Sylius\Component\Resource\StateMachine\StateMachine
states:
cart: null
addressed: null
completed: null
transitions:
address:
from:
- cart
- addressed
to: addressed
complete:
from:
- payment_selected
to: completed
app/config/config.yml:
sylius_shop:
checkout_resolver:
route_map:
cart:
route: sylius_shop_checkout_address
addressed:
route: sylius_shop_checkout_complete
app/Resources/SyliusShopBundle/views/Checkout/_steps.html.twig:
{% if active is not defined or active == 'address' %}
{% set steps = {'address': 'active', 'complete': 'disabled'} %}
{% else %}
{% set steps = {'address': 'completed', 'complete': 'active'} %}
{% endif %}
<div class="ui four steps">
<a class="{{ steps['address'] }} step" href="{{ path('sylius_shop_checkout_address') }}">
<i class="map icon"></i>
<div class="content">
<div class="title">{{ 'sylius.ui.address'|trans }}</div>
<div class="description">{{ 'sylius.ui.fill_in_your_billing_and_shipping_addresses'|trans }}</div>
</div>
</a>
<div class="{{ steps['complete'] }} step" href="{{ path('sylius_shop_checkout_complete') }}">
<i class="checkered flag icon"></i>
<div class="content">
<div class="title">{{ 'sylius.ui.complete'|trans }}</div>
<div class="description">{{ 'sylius.ui.review_and_confirm_your_order'|trans }}</div>
</div>
</div>
</div>
app/Resources/SyliusShopBundle/config/routing/checkout.yml:
# This file is a part of the Sylius package.
# (c) Paweł Jędrzejewski
sylius_shop_checkout_start:
path: /
defaults:
_controller: FrameworkBundle:Redirect:redirect
route: sylius_shop_checkout_address
sylius_shop_checkout_address:
path: /address
methods: [GET, PUT]
defaults:
_controller: sylius.controller.order:updateAction
_sylius:
event: address
flash: false
template: SyliusShopBundle:Checkout:address.html.twig
form:
type: sylius_checkout_address
options:
customer: expr:service('sylius.context.customer').getCustomer()
repository:
method: find
arguments: [expr:service('sylius.context.cart').getCart()]
state_machine:
graph: sylius_order_checkout
transition: address
redirect:
route: sylius_shop_checkout_complete
parameters: []
#
#sylius_shop_checkout_select_shipping:
# path: /select-shipping
# methods: [GET, PUT]
# defaults:
# _controller: sylius.controller.order:updateAction
# _sylius:
# event: select_shipping
# flash: false
# template: SyliusShopBundle:Checkout:selectShipping.html.twig
# form: sylius_checkout_select_shipping
# repository:
# method: find
# arguments: [expr:service('sylius.context.cart').getCart()]
# state_machine:
# graph: sylius_order_checkout
# transition: select_shipping
# redirect:
# route: sylius_shop_checkout_select_payment
# parameters: []
#
#sylius_shop_checkout_select_payment:
# path: /select-payment
# methods: [GET, PUT]
# defaults:
# _controller: sylius.controller.order:updateAction
# _sylius:
# event: payment
# flash: false
# template: SyliusShopBundle:Checkout:selectPayment.html.twig
# form: sylius_checkout_select_payment
# repository:
# method: find
# arguments: [expr:service('sylius.context.cart').getCart()]
# state_machine:
# graph: sylius_order_checkout
# transition: select_payment
# redirect:
# route: sylius_shop_checkout_complete
# parameters: []
sylius_shop_checkout_complete:
path: /complete
methods: [GET, PUT]
defaults:
_controller: sylius.controller.order:updateAction
_sylius:
event: summary
flash: false
template: SyliusShopBundle:Checkout:complete.html.twig
repository:
method: find
arguments: [expr:service('sylius.context.cart').getCart()]
state_machine:
graph: sylius_order_checkout
transition: complete
redirect:
route: sylius_shop_order_pay
parameters:
paymentId: expr:service('sylius.context.cart').getCart().getLastNewPayment().getId()
form:
type: sylius_checkout_complete
options:
validation_groups: 'sylius_checkout_complete'
I cleared my cache, and the two additional steps are removed from checkout from what I can tell. Clicking next on the address step does send me to the final 'checkout' step, it just 302's me right back to the address step without an error.
I can spot one bug in your config file:
states:
cart: ~
addressed: ~
completed: ~
transitions:
address:
from: [cart, addressed]
to: addressed
complete:
from: [payment_selected] # <- here
to: completed
The complete transition should be done from addressed state instead of payment_selected (which does not exist in your configuration). It should resolve your problem.
You are also right, that default method resolvers are not documented yet. There are two classes responsible for assigning a default shipping and payment methods to order (DefaultPaymentMethodResolver and DefaultShippingMethodResolver). Both will assign the first available method. It should be an expected behaviour if you have only one method available. But feel free to override these classes to provide your custom logic :)

Handling popups inside casperjs test

I'm trying to make a test for a login webpage where there is the possibility of using Thirdparties social login. When you click on facebook icon, for example, a new popup appears asking for user/password. I'm using waitForPopup and withPopup as specified by the documentation to handle that, but is not working. Is never finding the element (via xpath) inside the xpath, so I can never log in using facebook in our test.
This is an example code that check if the facebook button is there, click on it and wait for the popup:
casper.then(function() {
test.comment("When we click facebook button");
casper.waitForSelector(x(facebookButton), function() {
test.assertExists(x(facebookButton), "Facebook icon is showing");
casper.click(x(facebookButton));
}, function timeout() { // step to execute if check has failed
casper.test.fail("Timeout loading login page");
});
});
casper.then(function() {
casper.waitForPopup(/facebook\.com\/login/, function() {
test.comment("And we fill facebook login info");
casper.withPopup(/facebook\.com\/login/, function() {
this.viewport(1600, 900);
casper.sendKeys(x(facebookEmail), facebookLogin[0]);
casper.sendKeys(x(facebookPassword), facebookLogin[1]);
casper.click(x(facebookLogin));
});
}, function timeout() { // step to execute if check has failed
casper.test.fail("Timeout loading faceebook login");
});
});
The output of the test is:
# When we click facebook button
PASS Facebook icon is showing
# And we fill facebook login info
FAIL Cannot get informations from xpath selector: //input[#id='email']: element not found.
# type: uncaughtError
# file: casper/import-login-testing.js:1058
# error: Cannot get informations from xpath selector: //input[#id='email']: element not found.
# CasperError: Cannot get informations from xpath selector: //input[#id='email']: element not found.
# at getElementInfo (/Users/ginogalotti/testing-presentation/node_modules/casperjs/modules/casper.js:1058)
# at /Users/ginogalotti/testing-presentation/node_modules/casperjs/modules/casper.js:1589
# at casper/import-login-testing.js:84
# at runStep (/Users/ginogalotti/testing-presentation/node_modules/casperjs/modules/casper.js:1553)
# at checkStep (/Users/ginogalotti/testing-presentation/node_modules/casperjs/modules/casper.js:399)
# stack: not provided
For me, that means that is finding the popup, the waitForPopup is triggering and is just not using the popup to look for the facebookEmail element. I'm still learning about casperjs, so probably this is not even the best way to approach the problem; but I would really thank some guidance.
Thanks in advance,
Example website that I'm testing: https://import.io/login

understanding dojo AMD loading- functions are undefined

I have been trying to get someone to explain to me how the dojo AMD loading works and to get a simple piece of code to work. I understand that if using for example the CDN, one has to call the dojo library and load all modules you wish to use. I have tried to implement other javascript functions based on activity from the main page and I will always get the function either undefined or an error related to a dojo control undefined. It seems that all the modules that initially load are not available to the rest of the code. Any helpful explanations would be really appreciated.
<link rel="stylesheet" type=
"text/css" href="http://ajax.googleapis.com/ajax/libs/dojo/1.8.4/dojo/resources
/dojo.css" />
<link rel="stylesheet" type=
"text/css" href="http://ajax.googleapis.com/ajax/libs/dojo/1.8.4/dijit/themes/
tundra/tundra.css" />
<link rel="stylesheet" type=
"text/css" href="http://ajax.googleapis.com/ajax/libs/dojo/1.8.4/dojox/mobile/themes/
iphone/iphone.css" />
<title> DOJO </title>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/dojo/1.8.4/
dojo/dojo.js"
data-dojo-config="async:true"></script>
<script type="text/javascript" src="Scripts/login.js"></script>
<script type="text/javascript">
require(["dojox/mobile/parser",
"dojo/parser",
"dojo/on",
"dojo/request/xhr",
"dijit/form/Form",
"dojo/store/Observable",
"dojo/store/Memory",
"dijit/Toolbar",
"dijit/Dialog",
"dojo/io/script",
"dojo/query",
"dojo/_base/lang",
"dijit/layout/ContentPane",
"dojox/mobile/Button",
"dojox/mobile/deviceTheme",
"dojox/mobile/compat",
"dojox/mobile/Heading",
"dojox/mobile/TextBox",
"dojox/mobile/Opener",
"dijit/form/TextBox",
"dijit/form/HorizontalSlider",
"dijit/form/ValidationTextBox",
"dijit/Calendar",
"dojox/mobile/ScrollableView",
"dojo/dom",
"dojo/domReady!",
"dojox/mobile"],
function (dom, domReady ,mobile, ScrollableView,
parser, query, domClass, domStyle, on, event, xhr,Form,
lang, Button, deviceTheme, compat, Heading) {
dojox.mobile.parser.parse();
});
</script>
From my understanding is that the way I have the code above is that my interface will load correctly and all widgets in the body of html will be displayed and it works fine. The problem is that I have a form that gets input from the user and on a button click event calls a function that handles the webrequests. I could not get this to work and it is merely a problem with where I am placing this function. I have added a simplified version:
What I have done is add that function to a script file to separate it from the rest of the code:
var dojoXhr;
function correctInput(div, td, msg) {
dojo.domStyle.set(div, 'display', '');
td.innerHTML = msg;
}
require(["dojo/_base/declare", "dojo/parser", "dojo/query", "dojo/dom-class",
"dojo/dom-style", "dojo/on",
"dojo/_base/event",
"dojo/request/xhr", "dijit/form/ValidationTextBox", "dojo/domReady!"],
function chklogin(declare, parser, query, dom-class, dom-style,
on, event, xhr,ValidationTextBox, domReady) {
var lname = dijit.byId('login').get('value');
var psswd = dijit.byId('password').get('value');
var feedback = document.getElementById('feedback');
var feedbackTD = dojo.query('td.feedback')[0];
if (lname == '' || psswd == '') {
correctInput(feedback, feedbackTD, 'Please enter a valid login!');
dojo.domStyle.set(feedback, 'display', '');
dojo.domStyle.set(document.getElementById('msgBodyOutter'), 'display', 'none');
feedbackTD.innerHTML = "Please enter a valid login!";
return;
}
if (!(lname == 'login') || !(psswd == 'password')) {
correctInput(feedback, feedbackTD, 'Please enter a valid login!');
return;
}
else {
dojo.domStyle.set(feedback, 'display', '');
dojo.domStyle.set(document.getElementById('msgBodyOutter'), 'display', 'none');
feedbackTD.innerHTML = "THATS IT BRO!";
return;
}
});
I got advice on the dojo forum to put my function in a define function and then use a require to call it all. I could not figure out how to do this.
It seems that all the modules that initially load are not available to
the rest of the code.
You are using a CDN to load the dojo toolkit. When you use CDN you are required to define the location of the module packages. You need to edit the dojoConfig for the code to work.
See this article about Using Custom Modules with a CDN. The important part is the packages object.
<script data-dojo-config="async: 1, dojoBlankHtmlUrl: '/blank.html',
packages: [ {
name: 'custom',
location: location.pathname.replace(/\/[^/]+$/, '') + '/js/custom'
} ]"
src="//ajax.googleapis.com/ajax/libs/dojo/1.9.1/dojo/dojo.js">
</script>
Edit: Below is a simple dojo application.
So in my case create a module called chklogin, then require it, and
when the user clicks the button it will call that module chklogin from
within the main require[] function. Correct?
I would say yes. You are correct. I think your concept is a viable option. I hope this example helps with implementing define() to create your own modules. I will try to help where I can as you develop your idea. You can download the project here while available.
Directory Structure:
/index.html
/js/config.js
/js/controller/Controller.js
/js/modules/MyFirstModule.js
/index.html
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Demo</title>
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/dojo/1.9.1/dijit/themes/claro/claro.css">
<script src="js/config.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/dojo/1.9.1/dojo/dojo.js"></script>
<script>
require(["app/Controller", "dojo/domReady!"], function(Controller) {
//Initiate the entire application by calling main method of our Controller class.
Controller.main();
//Call our getter method of the Controller class to show how to access a private variable.
console.log(Controller.getWelcomeMessage());
});
</script>
</head>
<body class="claro" id="appBody"></body>
</html>
/js/config.js
We use packages to reference the CDN dojo files. Now we can call dojo classes by our package name
For example, "dojo/domReady!", "dijit/form/Button", "dojox/app/main". The dojo files
are stored on the google servers, which is referenced by the
<script src='http://ajax.googleapis.com/ajax/libs/dojo/1.9.1/dojo/dojo.js'>< /script>
in the index.html file.
Here we create our own custom packages. This could be for your modules, widgets, etc. The package
locations will map to the javascript directory that you store your custom dojo files in.
For example, myModules can be found in the /js/modules directory. You will reference any custom
dojo files via "myModules/MyModule", which locates and loads "/myModules/MyModule.js" file.
For an explanation of the baseURL, see: http://dojotoolkit.org/documentation/tutorials/1.9/hello_dojo/
"Defining and Requiring Modules". This code registers the correct location of our own packages so
we can load Dojo from the CDN whilst still being able to load local modules.
I created a package called "app" as you can see below. This is how I initialize my app in my project.
This was designed to allow me to keep the separation of code the best I know how. It is loaded and
called in the index.html page. So i give it a package name of app. It is physically located in the
js/controller/Controller.js file.
This dojoConfig object is used in the index.html and must be loaded prior to < script src='...dojo.js' > tag.
var dojoConfig = {
async: true,
tlmSiblingOfDojo: false,
baseUrl: location.pathname.replace(/\/[^/]*$/, ''),
packages: [
{ name: "myModules", location: "js/modules" },
{ name: "app", location: "js/controller", main: "Controller" }
]
};
if you choose to host the dojo files on your own server, you can reference them like below. Assuming the dojo js files are located in the "/js/dojo/*" directory.
packages: [
{ name: "dojo", location: "dojo/dojo" },
{ name: "dijit", location: "dojo/dijit" },
{ name: "dojox", location: "dojo/dojox" },
{ name: "myModules", location: "js/modules" },
{ name: "app", location: "js/controller", main: "Controller" }
]
/js/controller/Controller.js
Here is the controller which I use to initialize the web app.
define(["myModules/MyFirstModule"], function(MyFirstModule) {
//Private Variables...
var privateVariable1 = "Welcome to my Dojo Application!";
var privateVariable2;
/**
* init. This is a private function that is only available within this object.
*/
init = function() {
// proceed directly with startup
console.log("Startup functions are firing...");
//Render our "form" which only contains a single text box.
renderForm();
},
renderForm = function() {
MyFirstModule.createForm("appBody");
}
/**
* Enclose all public methods in the return object
*/
return {
/**
* main. This is a public function that can be called from other code.
*/
main: function() {
//Run init() method.
init();
},
/**
* getWelcomeMessage. This public function returns the value of the privateVariable1.
* This mimics a getter method.
*/
getWelcomeMessage: function() {
return privateVariable1;
}
};
}); //end define
/js/modules/MyFirstModule.js
This is an example of a custom Module. It is required by the Controller class as a dependency.
define([
//The required dependencies for this module.
"dojo/dom", "dojo/on", "dijit/form/TextBox", "dijit/form/Button"
], function(dom, on, TextBox, Button){
// Once all modules in the dependency list have loaded, this
// function is called to define the myModules/myFirstModule module.
//
// The dojo/dom module is passed as the first argument to this
// function; additional modules in the dependency list would be
// passed in as subsequent arguments (on, TextBox, and Button).
// Private variables
var firstNameTextBox;
var submitButton;
privateFunction = function() {
console.log("I am a private function. I can only be called from this class.");
};
// This returned object becomes the defined value of this module when called elsewhere.
return {
/**
* createForm. This method creates a simple form. Textbox and button.
* #param placeMeHere This is where to place the form elements. In this demo, the are placed in the
* body of the html document. This is executed in the Controller class.
*/
createForm: function(placeMeHere) {
//Create new TextBox.
firstNameTextBox = new TextBox({
name: "firstname",
value: "" /* no or empty value! */,
placeHolder: "type in your name"
}, "firstname");
//Place me in the DOM.
firstNameTextBox.placeAt(placeMeHere);
//Render
firstNameTextBox.startup();
//Create Button
submitButton = new Button({
label: "Say Hi"
}, "submitButton");
submitButton.placeAt(placeMeHere);
submitButton.startup();
//Greet the user.
on(submitButton, "click", function(evt){
console.log("Hi there, " + firstNameTextBox.get("value"));
});
}
};
});