I have what is probably a basic question around how the famo.us slideshow tutorial is using requirejs (tutorial here). I don't know much about requirejs, and it's sort of a tertiary tool for the purposes of this tutorial, but I did do a bit of reading to try and wrap my head around what it's doing, but that seemed to only leave me more perplexed.
From the index.html file
<head>
<title>famo.us App</title>
<meta name="viewport" content="width=device-width, maximum-scale=1, user-scalable=no" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<!-- shims for backwards compatibility -->
<script type="text/javascript" src="http://code.famo.us/lib/functionPrototypeBind.js"></script>
<script type="text/javascript" src="http://code.famo.us/lib/classList.js"></script>
<script type="text/javascript" src="http://code.famo.us/lib/requestAnimationFrame.js"></script>
<!-- module loader -->
<script type="text/javascript" src="http://code.famo.us/lib/require.js"></script>
<!-- famous -->
<link rel="stylesheet" type="text/css" href="http://code.famo.us/famous/0.3.0/famous.css" />
<script type="text/javascript" src="http://code.famo.us/famous/0.3.0/famous.min.js"></script>
<!-- app code -->
<link rel="stylesheet" type="text/css" href="css/app.css" />
<script type="text/javascript">
require.config({
baseUrl: './src/'
});
require(['main']);
</script>
</head>
As I understand it we are doing two things from a requirejs perspective:
a) indicating all our modules will be located in the "src" folder
b) indicating the inital js file for execution will be 'src/main.js'
here is main.js:
define(function(require, exports, module) {
var Engine = require('famous/core/Engine');
// import the AppView class using require
var AppView = require('views/AppView');
var mainContext = Engine.createContext();
// create a new instance of app view
var appView = new AppView();
// add the instance to the context
mainContext.add(appView);
});
And here is one iteration of the appview module:
define(function(require, exports, module) {
var View = require('famous/core/View');
var Surface = require('famous/core/Surface');
var Transform = require('famous/core/Transform');
var StateModifier = require('famous/modifiers/StateModifier');
// import the SlideshowView class
var SlideshowView = require('views/SlideshowView');
function AppView() {
View.apply(this, arguments);
// create a new instance of slideshow view
var slideshowView = new SlideshowView();
// add the instance to app view
this.add(slideshowView);
}
AppView.prototype = Object.create(View.prototype);
AppView.prototype.constructor = AppView;
AppView.DEFAULT_OPTIONS = {};
module.exports = AppView;
});
My confusion is with these module imports in appview:
var View = require('famous/core/View');
Since we set the base folder to the 'src' folder, I would think require would expect this module to be in 'src/famous/core', though there is no such directory. In fact, the famous directory is a few levels above the src folder in the file hierarchy. So how did require find the famous directory?
'famous/core/View' is just a module name, not a path as you would expect. It is defined in dist/famous.js:5009. They used the / convention probably as a namespace.
For more info about defining a module with a name go to the requirejs docs.
In short terms, one could do the following in a file named foo.js :
define('foo/bar', function () {
return {
name: 'bar'
}
});
define('foo/baz', function () {
return {
name: 'baz'
}
});
And if that file foo.js is loaded at some point, one could require the following, without the need of having a folder/file structure foo/bar.js and foo/baz.js:
require(['foo/bar', 'foo/baz', function (Bar, Baz) {
console.log(Bar.name, Baz.name)
});
p.s. i strongly suggest you read the docs, requirejs is just awesome
Related
What is wrong with this code? It displays the says: hello bar from the out.js console.log but does not run the rest of the script doesn't add the link inside <div id="link"></div>
If I put the script directly in the code it works, but not in a .js file
teste2.js
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const dom = new JSDOM(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="link"></div>
<p>Hello world</p>
<script src="http://localhost/2022/jsdom/out.js"></script>
</body>
</html>`, { resources: "usable", runScripts: "dangerously"});
const document = dom.window.document;
console.log(document.getElementsByTagName('html')[0].innerHTML);
out.js
let T = document.getElementById('link');
T.innerHTML = 'LINK';
console.log('bar says: hello');
JSDOM loads sub-resources asynchronously, so your Node.js code is accessing the DOM before the <script> code has executed.
This is also why the log of document.getElementsByTagName('html')[0].innerHTML appears before the log of 'bar says: hello'.
You can handle this by explicitly waiting for the load event:
const document = dom.window.document;
document.addEventListener('load', () => {
console.log(document.getElementsByTagName('html')[0].innerHTML);
});
You'll need more complex logic if the script itself does anything asynchronously. Firing a custom event that you can listen for is a good approach.
I want to embed PitchPrint app on a React website. They have a vanilla html/js integration tutorial here. I added script tags with links to jQuery and their app file in my index.html file, as they require and then created a separate jsx file that suposed to return a button witch opens the app. The problem is, when I try to build, it throws an error 'PitchPrintClient' is not defined witch suposed to come from their files.
My index.html file:
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://pitchprint.io/rsc/js/client.js"></script>
<title>App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
My jsx file:
import React from 'react';
const AppButton = () => {
let _launchButton = document.getElementById('launch_btn');
let _previewDiv = document.getElementById('pp_preview_div');
let _loaderDiv = document.getElementById('pp_loader_div');
_launchButton.setAttribute('disabled', 'disabled');
var ppclient = new PitchPrintClient({
apiKey: 'f80b84b4eb5cc81a140cb90f52e824f6', //Kinldy provide your own APIKey
designId: '3d8f3899904ef2392795c681091600d0', //Change this to your designId
custom: true
});
//Function to run once the app is validated (ready to be used)
var appValidated = () => {
_launchButton.removeAttribute('disabled'); //Enable the Launch button
_launchButton.onclick = () => ppclient.showApp(); //Attach event listener to the button when clicked to show the app
_loaderDiv.style.display = 'none'; //Hide the loader
};
//Function to run once the user has saved their project
var projectSaved = (_val) => {
let _data = _val.data; //You can console.log the _data varaible to see all that's passed down
if (_data && _data.previews && _data.previews.length) {
_previewDiv.innerHTML = _data.previews.reduce((_str, _prev) => `${_str}<img src="${_prev}">`, ''); //Show the preview images
}
};
ppclient.on('app-validated', appValidated);
ppclient.on('project-saved', projectSaved);
return <div>
<div id="pp_loader_div"><img src="https://pitchprint.io/rsc/images/loaders/spinner_new.svg" /></div>
<button id="launch_btn" >Launch Designer</button>
<div id="pp_preview_div"></div>
</div>;
};
export default AppButton;
PS: I know getElementById does not realy work with react, I'll deal with that later, for now I just want to initialize this app.
that's because the component is not mounted yet.
you need to call document.getElementById once the component is mounted, and in order to access dom elements inside the component you need to call it inside useEffect hook
useEffect(() => {
let _launchButton = document.getElementById("launch_btn");
let _previewDiv = document.getElementById("pp_preview_div");
let _loaderDiv = document.getElementById("pp_loader_div");
_launchButton.setAttribute("disabled", "disabled");
var ppclient = new PitchPrintClient({
apiKey: "f80b84b4eb5cc81a140cb90f52e824f6", //Kinldy provide your own APIKey
designId: "3d8f3899904ef2392795c681091600d0", //Change this to your designId
custom: true,
});
//Function to run once the app is validated (ready to be used)
var appValidated = () => {
_launchButton.removeAttribute("disabled"); //Enable the Launch button
_launchButton.onclick = () => ppclient.showApp(); //Attach event listener to the button when clicked to show the app
_loaderDiv.style.display = "none"; //Hide the loader
};
//Function to run once the user has saved their project
var projectSaved = (_val) => {
let _data = _val.data; //You can console.log the _data varaible to see all that's passed down
if (_data && _data.previews && _data.previews.length) {
_previewDiv.innerHTML = _data.previews.reduce(
(_str, _prev) => `${_str}<img src="${_prev}">`,
""
); //Show the preview images
}
};
ppclient.on("app-validated", appValidated);
ppclient.on("project-saved", projectSaved);
}, []);
Read more about React hooks and their constraints.
https://reactjs.org/docs/hooks-intro.html
also make sure to access global variables using window.PitchPrintClient
also, make sure your app is mounted once the dom is ready. by moving your script tags to the end of the body tag or using jquery on ready callback.
PS: The answer is not considering the best practices of writing react components, but I encourage you to use refs and minimize accessing to dom as much as you could.
Using the polymer-cli tool, and the shopping cart boilerplate as a starting point, I made a simple mock-up to illustrate the use case.
Assume your index.html file includes "test-app.html" and the matching tag
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>My App</title>
<link rel="shortcut icon" sizes="32x32" href="/images/app-icon-32.png">
<meta name="theme-color" content="#fff">
<link rel="manifest" href="/manifest.json">
<script>
// setup Polymer options
window.Polymer = {lazyRegister: true, dom: 'shadow'};
// load webcomponents polyfills
(function() {
if ('registerElement' in document
&& 'import' in document.createElement('link')
&& 'content' in document.createElement('template')) {
// browser has web components
} else {
// polyfill web components
var e = document.createElement('script');
e.src = '/bower_components/webcomponentsjs/webcomponents-lite.min.js';
document.head.appendChild(e);
}
})();
// load pre-caching service worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/service-worker.js');
});
}
</script>
<!-- <link rel="import" href="/src/bewi-app.html"> -->
<link rel="import" href="/src/test-app.html">
<style>
body {
margin: 0;
font-family: 'Roboto', 'Noto', sans-serif;
line-height: 1.5;
min-height: 100vh;
background-color: #eee;
}
</style>
</head>
<body>
<span id="browser-sync-binding"></span>
<test-app id="test"></test-app>
</body>
</html>
Now, assume test-app.html containing the following (again a mere simplified copy of my-app.html):
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../bower_components/app-route/app-location.html">
<link rel="import" href="../bower_components/app-route/app-route.html">
<link rel="import" href="test-element.html">
<dom-module id="test-app">
<template>
<app-location route="{{route}}"></app-location>
<app-route
route="{{route}}"
pattern="/:page"
data="{{routeData}}"
tail="{{subroute}}"></app-route>
test-element is loaded bellow
<test-element></test-element>
<div>
</div>
</template>
<script>
Polymer({
is: 'test-app',
properties: {
page: {
type: String,
reflectToAttribute: true,
observer: '_pageChanged'
},
baseUrl: {
type: String,
value: '/'
},
siteUrl: {
type: String,
value: 'http://fqdn.local'
}
},
observers: [
'_routePageChanged(routeData.page)'
],
_routePageChanged: function(page) {
this.page = page || 'view1';
},
_pageChanged: function(page) {
// load page import on demand.
this.importHref(
this.resolveUrl('my-' + page + '.html'), null, null, true);
}
});
</script>
</dom-module>
Now, the test-element.html
<link rel="import" href="../../bower_components/polymer/polymer.html">
<dom-module id="test-element">
<template>
<div> I am a test </div>
</template>
<script>
(function() {
'use strict';
Polymer({
is: 'test-element',
ready: function() {
console.log('READY');
console.log('find #test using document.querySelector', document.querySelector('#test')); // OK
console.log('find #test .siteUrl using document.querySelector', document.querySelector('#test').siteUrl); // undefined
console.log('find #test .siteUrl using Polymer.dom', Polymer.dom(document.querySelector('#test')).siteUrl); // undefined
console.log('find #test .siteUrl using Polymer.dom().node', Polymer.dom(document.querySelector('#test')).node.siteUrl); // undefined
console.log('find #test .siteUrl using Polymer.dom().properties', Polymer.dom(document.querySelector('#test')).node.properties); // {object} but, I'm guessing not the computed values of the properties
// So, how may I access the #test app's "siteUrl" property from within a custom element?
}
});
})();
</script>
</dom-module>
So, the real question is, how may test-element access the property "siteUrl" in the main app?
I'm planning to make this variable readOnly, and access it from other custom elements.
I'ld prefer this approach VS passing the siteUrl as an attribute to the test-element element..
What do you think?
The right way to pass information through elements is using the Data Binding system, i.e. "passing the siteUrl as an attribute to the test-elemet element"
You'll accomplish the Read Only requirement surrounding the variable with square brackets, like this [[siteUrl]] as described in Property change notification and two-way binding.
You can set a variable in a global environment as you said like
<script>
var myGlobalVar = 'is accessible in any element'
Polymer({
is: 'test-app',
// ...
});
</script>
and you can access it in every element.
BUT, global variables are not recommended as you may know. References about why in the links below.
Global Variables Are Bad
Why are global variables considered bad practice?
I've Heard Global Variables Are Bad, What Alternative Solution Should I Use?
I'm going over the sapui5 walkthrough tutorials and have managed to get to step 9 where it teaches you how to use Component.js file in your app.
Now, prior to using Component.js everything in the app was working fine. However, once I try to use component I get this error:
Uncaught TypeError: this.getView is not a function
Referring to UIComponent.js line 6. Even though my component file is just called Component.js. I also get:
GET http://localhost:58736/InvoicesApp/invoicesapp/Component-preload.js 404 (Not Found)
But I'm not sure they're related
Here is my index.html
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv='Content-Type' content='text/html;charset=UTF-8'/>
<script src="resources/sap-ui-core.js"
id="sap-ui-bootstrap"
data-sap-ui-libs="sap.m"
data-sap-ui-theme="sap_bluecrystal"
data-sap-ui-bindingSyntax="complex"
data-sap-ui-compatVersion="edge"
data-sap-ui-preload="async"
>
</script>
<!-- only load the mobile lib "sap.m" and the "sap_bluecrystal" theme -->
<script>
sap.ui.localResources("invoicesapp");
sap.ui.getCore().attachInit(function () {
new sap.ui.core.ComponentContainer({
name : "invoicesapp"
}).placeAt("content");
});
</script>
</head>
<body class="sapUiBody" id="content"/>
</html>
My Component.js
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/model/json/JSONModel",
"sap/ui/model/resource/ResourceModel"
], function (UIComponent, JSONModel, ResourceModel) {
"use strict";
return UIComponent.extend("invoicesapp.Component", {
metadata: {
rootView:"invoicesapp.view.App"
},
init : function () {
// call the init function of the parent
UIComponent.prototype.init.apply(this, arguments);
// set data model on view
var oData = {
recipient : {
name : "World"
}
};
var oModel = new JSONModel(oData);
this.getView().setModel(oModel);
// set i18n model on view
var i18nModel = new ResourceModel({
bundleName: "invoicesapp.i18n.i18n"
});
this.getView().setModel(i18nModel, "i18n");
}
});
});
My controller
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast",
"sap/ui/model/json/JSONModel",
"sap/ui/model/resource/ResourceModel"
], function (Controller, MessageToast, JSONModel, ResourceModel) {
"use strict";
return Controller.extend("invoicesapp.controller.App", {
onShowHello : function () {
// read msg from i18n model
var oBundle = this.getView().getModel("i18n").getResourceBundle();
var sRecipient = this.getView().getModel().getProperty("/recipient/name");
var sMsg = oBundle.getText("helloMsg", [sRecipient]);
// show message
MessageToast.show(sMsg);
}
});
});
In the component you can't call this.getView() because there is no getView(), the api docs at https://openui5beta.hana.ondemand.com/#docs/api/symbols/sap.ui.core.Component.html
Instead, you set the model directly on the component itself. In other words, just call
this.setModel(oModel);
and
this.setModel(i18nModel, "i18n");
By the way: in the walktrough it's done the same way.
this.getView() will not be defined in component.js as view is yet to be instantiated.
You can simply set model with just this.setModel(oModel) and you can set names model by this.setModel(oModel,"modelName");
I also had the same problem while going through the walk through.
View is not instantiated in the Component.
You can rewrite as
this.setModel("myModel);
And for i18n
this.setModel(i18nModel,"i18n");
This error was there in walk through but now its fixed it seems. There were few errors with the SAP UI5 Tutorials while I was learning but they have fixed most of them now.
This is also a good source to start with UI5. Almost similar to Demokit.
UI Development Toolkit for HTML5 (SAPUI5)
Happy Coding
Febin Dominic
In order to load missing modules, YUI allows us to specify them in use(...) method, pass in a callback and perform our actions when all modules are loaded - asynchronously. This presents a number of problems in my case. More specifically, I find it impossible to instantiate my class outside of the current file if I have my classes created inside the callback (no guarantee that they will be ready by the time "new" happens). My work-around was to wrap only certain method calls in YUI.use(...) but this creates another problem with extending objects. Ideally, what I need to do is load all modules synchronously before any of my code executes. Below is my code that currently fails succeeds (EDIT: Allow Rollups).
HTML:
<html>
<head>
<!-- Built using YUI dep configurator -->
<!-- JS -->
<script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/yui/yui-min.js"></script>
<script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/oop/oop-min.js"></script>
<script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/event-custom/event-custom-base-min.js"></script>
<script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/event/event-base-min.js"></script>
<script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/dom/dom-min.js"></script>
<script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/dom/dom-style-ie-min.js"></script>
<script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/pluginhost/pluginhost-min.js"></script>
<script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/node/node-min.js"></script>
<script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/event/event-base-ie-min.js"></script>
<script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/event/event-delegate-min.js"></script>
<!-- My JS -->
<script type="text/javascript" src="test.js"></script>
<script type="text/javascript">
var test = new MyNS.ExtendingClass();
</script>
</head>
<body>
<h1>Hello World!</h3>
</body>
</html>
test.js
//namespace
if (!MyNS) var MyNS = {};
(function(){
var Y = YUI().use('node', 'io', 'autocomplete');
MyNS.BaseClass = function() {
console.log('Base class newed. Y: ' + Y);
var self = this;
self.init();
};
MyNS.BaseClass.prototype = {
init: function() {
console.log('Initting! Y: ' + Y);
}
, test: function() {
console.log('test fired!');
}
};
})();
(function(){
var Y = YUI().use('node');
MyNS.ExtendingClass = function() {
console.log('Extended class newed. Y: ' + Y);
var self = this;
MyNS.ExtendingClass.superclass.constructor.call(self);
};
MyNS.ExtendingClass.prototype = {
testExtended: function() {
console.log('testExtended fired!');
}
};
Y.extend(MyNS.ExtendingClass, MyNS.BaseClass);
})();
This code now works but requires 10 (!!!) js files to make it happen. Is there a way to make sure all dependencies are loaded dynamically and before my code is executed? There must be, right?
You can solve this by putting each of your classes inside their own YUI module and use YUI to do the namespacing.
Create a new file my-classes.js, containing both your class definitions:
YUI().add('baseClass', function(Y) {
// constructor
Y.namespace('NS').BaseClass = function () {
this.msg = 'hi!';
}
}, '1', {requires: ['oop', 'node', 'event']}); // dependencies for your class
YUI().add('extendingClass', function(Y) {
// constructor
Y.namespace('NS').ExtendingClass = function () {
Y.NS.ExtendingClass.superclass.constructor.call(this);
alert(this.msg);
}
Y.extend(Y.NS.ExtendingClass, Y.NS.BaseClass);
}, '1', {requires: ['baseClass']});
Include the YUI seed file in your page:
<script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/yui/yui-min.js"></script>
Also include your class file and an init file:
<script type="text/javascript" src="my-classes.js"></script>
<script type="text/javascript" src="my-init.js"></script>
In your init file:
YUI().use('extendingClass', function(Y) {
Y.test = new Y.NS.ExtendingClass();
})
Now all the dependencies should be resolved and loaded up, before your code executes. It is asynchronous, however you asked for a solution that would ensure everything was loaded before your code executed.
Hope this helps.
Use Google Closure Compiler to compress and pack everything into single file. You could import multiple files into the compressor.
With advanced mode of Compression, Google Closure Compiler compresses 20-25% more than the YUI compressor in general for any library.