How to create custom meta tags in Vue Meta 3? - vue.js

I am using Vue Meta 3 to provide meta data to the website. The API for this is here
I do not understand how to provide custom meta tags( e.g Open Graph tags such as og:type). This is what I have tried to do in a component:
setup() {
useMeta({
title: "Homepage",
meta: [
{name: "hunky:dory", content: "website"}
],
description: "This is the homepage."
})
},
The HTML that gets outputted ends up like this:
<head>
<title>Homepage</title>
<meta name="description" content="This is the homepage.">
<meta name="meta" content="website"> <!-- should be <meta name="hunky:dory content="website"> -->
</head>
If I change the code to this:
setup() {
useMeta({
title: "Homepage",
"hunky:dory": [
{content: "website"}
],
description: "This is the homepage."
})
},
I get illegal HTML output:
<head>
<title>Homepage</title>
<meta name="description" content="This is the homepage.">
<hunky:dory>website</hunky:dory> <!-- total nonsense -->
</head>
How do I get the output to be:
<head>
<title>Homepage</title>
<meta name="description" content="This is the homepage.">
<meta name="hunky:dory" content="website">
</head>

There are 2 parts to getting og meta working -- I think I can help with part 1:
Correct vue-meta syntax
Server-Side Rendering (SSR)
Part 1: vue-meta for Vue 3
I wrote this with vue-class-component, and it seems to be working:
meta = setup(() => useMeta(computed(() => ({
title: this.event?.name ?? 'Event not found',
og: {
image: this.event?.bannerUrl ?? 'http://yourwebsite.com/images/default-banner.png'
}
}))))
...which presumably translates to this in vanilla Vue 3:
setup() {
useMeta(
computed(() => ({
title: this.event?.name ?? 'Event not found',
og: {
image: this.event?.bannerUrl ?? 'http://yourwebsite.com/images/default-banner.png'
}
}))
)
}
Result:
<meta property="og:image" content="http://cloudstorage.com/images/event-123.png">
References:
GitHub -> vue-meta#next -> example for vue-router
Also hinted in the readme
Part 2: SSR
Once I'd done part 1, I realized that I hadn't setup SSR... so I'm only rendering the meta for my users, not for Facebook's crawler (not very useful). I'm afraid I haven't fixed this yet on my project; perhaps someone else can pitch in that part!
Until then, maybe this will get you started:
SSR options
Vue 3's native SSR
Note on SSR in the vue-meta readme
Note: vue-meta is under the Nuxt GitHub organization => you might consider migrating to Nuxt v3 (which is built on top of Vue):
Nuxt v3 tracker issue
Slides suggesting beta this month (June 2021).

A bit late but maybe not useless for anyone facing issues with Vue 3 (and vue-meta). With the below detailed woraround, you are not dependent on any 3rd party lib.
My project is currently in development stage in local environment (so not fully tested), but a probable workaround is using beforeCreate lifecycle hook for adding meta tags if you are using Options API in Vue 3 (with vue-router), SFC way (e.g. If you are using single-file-component views for "pages" and you want them all to have their custom meta info).
In the hook method you can create DOM nodes and append them to the head like:
...
beforeCreate(){
// adding title for current view/page using vue-i18n
let title = document.createElement(`TITLE`)
title.innerText = this.$t(`something`)
document.querySelector(`head`).appendChild(title)
// adding og:image
let ogImage = document.createElement(`META`)
ogImage.setAttribute(`name`,`og:image`)
ogImage.setAttribute(`content`,`YOUR-IMAGE-URL`)
document.querySelector(`head`).appendChild(ogImage)
}
...
I'm not sure yet if this is an efficient way to make it work, but gonna try to update this post when the project is in production.
I have tested this solution with chrome plugins like this one:
Localhost Open Graph Debugger

I was having the same issues then I came across this which solves the problem for me.
Here is the link to the original post: vue3 vue-meta showing name="meta"
In vue js 3 you should use the vue3-meta or alpha version. Then do the
following
metaInfo() {
return {
htmlAttrs: { lang: 'en', amp: true },
title: "page title",
description : "Page description",
twitter: {
title: "twitter title",
description: "twitter description",
card: "twitter card",
image: "twitter image",
},
og: {
title : 'og title!',
description : 'og description!',
type : 'og type',
url : 'og url',
image : 'og image',
site_name : 'og site name',
}
}
}
if you want to use meta name then change the config in main.js
import { createMetaManager, defaultConfig, plugin as metaPlugin } from 'vue-meta'
const metaManager = createMetaManager(false, {
...defaultConfig,
meta: { tag: 'meta', nameless: true },
});
and in your component use the meta name below
metaInfo() {
return {
meta: [
{'name' : 'author', 'content' : 'author'},
{ name: 'description', content: 'authors' },
]
}
}

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?

Custom print style with Vue.JS print plugin

I am trying to print a VueJS component with custom print style.
Three Vue plugins look interesting on this subject:
1.printd
2.vue-print-nb
3.html-to-paper
Out of the three only html-to-paper has a options object that can pass a custom css style in order to dynamically pass some print css.
My issue is that i can't seem to load the custom css, and also bootstrap classes are messed up on print action.
This is basically what i am doing.
import VueHtmlToPaper from 'vue-html-to-paper'
const options = {
name: '_blank',
specs: [
'fullscreen=yes',
'titlebar=yes',
'scrollbars=no'
],
styles: [
'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css',
'./myPrint.css'
]
}
Vue.use(VueHtmlToPaper, options)
Any suggestion is welcomed.
Thanks
I have tried all these three I think the best one is print.js which is not specifically for Vue.js but it is easily install-able and usable in the vue components.
For example
<script>
import print from "print-js";
export default {
methods: {
printing() {
const style =
"#page { margin-top: 400px } #media print { h1 { color: blue } }";
const headerStyle = "font-weight: 300;";
printJS({
printable: "rrr",
type: "html",
header: "Doctor Name",
headerStyle: headerStyle,
style: style,
scanStyles: false,
onPrintDialogClose: () => console.log("The print dialog was closed"),
onError: e => console.log(e)
});
},
printVisit(id) {
this.$htmlToPaper("rrr");
this.$htmlToPaper("rrr", () => {
console.log("Printing completed or was cancelled!");
});
}
}
};
</script>
VueHtmlToPaper opens a new window with its own style tag. So when you pass a CDN it works, if u pass a local file it does not because it tries to access the resource in your web server but in the wrong URL. Let's see how the page looks when we use a CDN and a local CSS file.
CDN
<html>
<head>
<link rel="style" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css">
</head>
<body>
</body>
</html>
Local CSS file
And let's say you are calling the print function from http://localhost:8080/somepage
<html>
<head>
<link rel="style" href="./myPrint.css">
</head>
<body>
</body>
</html>
This will try to open http://localhost:8080/somepage/myPrint.css. Obviously this will not be accessible to print dialogue.
Solution
Put your custom CSS file in the public or static folder (Where you usually keep favicon)
Modify script path in options, prepend server basepath with the CSS file
Sample Option
import VueHtmlToPaper from 'vue-html-to-paper'
/* This will change according to your server */
let basePath= 'http://localhost:8080';
const options = {
name: '_blank',
specs: [
'fullscreen=yes',
'titlebar=yes',
'scrollbars=no'
],
styles: [
'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css',
`${basePath}/myPrint.css`
]
}
Vue.use(VueHtmlToPaper, options)
Also, the simplest way to access root-relative path is to use /. User /style.css instead of ./style.css

Rally Standard Report, Issue with APIKey,but works when logged in

I've created an iteration burn-down chart in the code below. When we try to launch this from a web server with the apikey appended we see a window generated with the Rally Login screen not the graph. If you are already logged into the Rally tool, the graph does generate correctly. We only see this issue with the standard report as code generated using treegrid does work as expected when the APIKey is appended to the path.
Thanks!
Mark
<!DOCTYPE html>
<html>
<head>
<title>iterationburndown</title>
<script type="text/javascript" src="https://rally1.rallydev.com/apps/2.1/sdk.js"></script>
<script type="text/javascript">
Rally.onReady(function() {
Ext.create("Ext.Container", {
context: {},
items: [{
xtype: "rallystandardreport",
width: 750,
height: 500,
reportConfig: {
report: "IterationBurndown",
iteration: "April",
subchart: "hide",
title: "Iteration Burndown"
},
project: "https://rally1.rallydev.com/slm/webservice/v2.0/project/51186097359",
projectScopeUp: !1,
projectScopeDown: !0
}],
renderTo: Ext.getBody().dom
});
Rally.launchApp('CustomApp', {
name: "iterationburndown",
parentRepos: ""
});
});
</script>
<style type="text/css">
</style>
Unfortunately this is a limitation with those old style charts rendered by the Standard Report component. The A1 service those use does not support API Keys.
The best you'll be able to do would be to re-implement the chart using the Rally.ui.chart.Chart component and the Lookback API.
Some resources:
https://help.rallydev.com/apps/2.1/doc/#!/guide/lookback_api
https://help.rallydev.com/apps/2.1/doc/#!/guide/data_visualization
There's also a related app already implemented for a release burndown you could use as a place to get started: https://github.com/RallyApps/app-catalog/tree/master/src/apps/charts/burndown

Is there anyway to use JSON-LD Schema not inlined

Is there anyway to use JSON-LD without including the script inline in the HTML, but still get Google (& other) spiders to find it? Looking around I've seen some conflicting information.
If this was the JSON-LD file:
<script type="application/ld+json">
{
"#context" : "http://schema.org",
"#type" : "WebSite",
"name" : "Example Site",
"alternateName" : "example",
"description" : "Welcome to this WebSite",
"headline" : "Welcome to Website",
"logo" : "https://example.com/public/images/logo.png",
"url" : "https://example.com/"
}
</script>
And I have this in the head of the HTML:
<script src="/public/json-ld.json" type="application/ld+json"></script>
EDIT: I've also tried:
<link href="/public/json-ld.json" rel="alternate" type="application/ld+" />
Google Spiders seem to miss it and so does the testing tool unless I point it directly at the file. I'm trying to work around unsafe-inline in the CSP. And the only thing I can find is this, which would work in Chrome but don't want to be firing console errors on every other browser. Plus, I just like the idea of Schema.org data being abstracted out of the page structure. Would adding the JSON-LD to the sitemap for Google Webmaster Tools help?
Apologies, total noob to JSON-lD and keep ending up in email documentation (this would be for a site) or old documentation.
True, it can not be made external and it is not supported inline, but you can still achieve what you want by injecting it into the DOM via a JavaScript file.
Note: I am using an array for neatness so I can segment all the structured data elements and add an infinite amount of them. I have a more complicated version of this code on my websites and actually have an external server side rendered file masquerading as a JavaScript file.
An yes Google search bot does understand it. May take days for it to register and using Webmaster tools to force a re-crawl does not seem to force a refresh of JSON-LD data - seems like you just have to wait.
var structuredData = {
schema: {
corporation: {
'#context': 'http://schema.org',
'#type': 'Corporation',
'name': 'Acme',
'url': 'https://acme.com',
'contactPoint':
{
'#type': 'ContactPoint',
'telephone': '+1-1234-567-890',
'contactType': 'customer service',
'areaServed': 'US'
}
},
service: {
'#context': 'http://schema.org/',
'#type': 'Service',
'name': 'Code optimization',
'serviceOutput' : 'Externalized json',
'description': 'Inline json to externalized json'
},
},
init: function () {
var g = [];
var sd = structuredData;
g.push(sd.schema.corporation);
g.push(sd.schema.service);
//etc.
var o = document.createElement('script');
o.type = 'application/ld+json';
o.innerHTML = JSON.stringify(g);
var d = document; (d.head || d.body).appendChild(o);
}
}
structuredData.init();

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"));
});
}
};
});