YUI3 - how to load modules synchronously? - javascript

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.

Related

Creating a JS library with IIFE namespacing

If we don't want to use ES6 import nor any third party libraries (like require.js etc.) nor any package builder, what is the classical way to pack a library so that the user can use it with what seems to be a library-namespace?
index.html (from the perspective of the user of the library)
<canvas id="mycanvas"></canvas>
<script src="canvas.js"></script>
<script>
canvas.setup({"config1": true, "config2": false});
var mycanvas1 = canvas.get("#mycanvas");
</script>
I sometimes see code similar to
// canvas.js
(function (window, something, else) {
window.canvas.setup = function() { ... } // how to export functions setup and get?
})(window, ..., ...);
How to do this IIFE properly to create this canvas.js library example?
Note: with ES6 import, we can package this way a library called canvas.js:
const canvas = {
setup: config => { console.log("config"); },
get: id => { console.log("get"); }
};
export default canvas;
and use it a index.html this way:
<canvas id="mycanvas"></canvas>
<script type="module">
import canvas from "./canvas.js";
canvas.setup({"config1": true, "config2": false});
var mycanvas1 = canvas.get("#mycanvas");
</script>
but in this question here I am curious about the pre-ES6 solution.
A solution seems to be:
// canvas.js
canvas = (function(window) { // assign the result of the IIFE to a global var to make
// it available for the user of the lib
var internal_variable; // not accessible to the user of the lib
var state; // this variable will be available to the user of the lib
function setup(arg) {
console.log("setup");
}
function get(arg) {
console.log("get"); // + other lines using window object
}
return {setup: setup, get: get, state: state}; // important: here we choose what we
// export as an object
})(window);
Now the user can use the library this way:
<canvas id="mycanvas"></canvas>
<script src="canvas.js"></script> // object canvas is now available
<script>
canvas.setup({"config1": true, "config2": false});
var mycanvas1 = canvas.get("#mycanvas");
console.log(canvas.state);
</script>

how can I run an JavaScript function once a <script> appear

hey so I have a problem with my HTML script
so first I had this
index.html
<script src="index.js"></script>
<script src="loadplayer.js"></script>
index.js
const script = document.createElement("div")
script.innerHTML = urlvid
const player = document.getElementById("head")
player.appendChild(script)
in the script, there is a function named loadvideoplayer()
loadplayer.js
function loadvideoplayer(title){
let array = {title}
console.log(array)
console.log(array.title.sources[0].file)
console.log("Loaded")
}
the problem is the function on the script tag from index.js is not executed after the is created
how can i make it executed after the created ?
btw the urlvid contain this
loadvideoplayer(
{
title : '',
tracks: [{
file: '',
kind: 'captions',
'default': true
}],
sources: [{'file':'vidurl','type':'video/mp4'}],
image: "imgurl",
captions:
{
color:'#FFFF00',fontSize:17,backgroundOpacity:50
},
}
);
Move your HTML declaration like this:
<!-- import libraries before executing code -->
<script src="loadplayer.js"></script>
<script src="index.js"></script>
Then you can do something like this:
function loadVideoPlayer(title) {
console.log('Loaded Video: ', title);
}
let script = document.createElement('script');
script.innerHTML = 'console.log("Appended script executed!"); myObject = { title: "Wonderful Stack Overflow Answers" };'
document.head.appendChild(script);
loadVideoPlayer(myObject.title)
Obviously you must know in advance what are the variables in the script you are injecting into your page (like myObject in the example). Without knowing the script you are loading you can't use its variables.
Note that as we are using innerHTML and not src in the appended script, you can just trigger the function just after appended the child into the document head.
If you use src, the browser will ask asynchronously the script, so you must do:
I also warn you on the fact that injecting a script into a page is always a security risk, so you must rely on what you are injecting (it is a secure source?).
script.addEventListener('load', function() {
loadVideoPlayer(myObject.title)
});
Side Note: you could also use eval (MDN), many will state that eval is evil, but you are actually ignoring the same security risks by evaluating the code in the script tag:
function loadVideoPlayer(title) {
console.log('Loaded Video: ', title);
}
let script = 'console.log("Appended script executed!"); myObject = { title: "Wonderful Stack Overflow Answers" };'
eval(script);
loadVideoPlayer(myObject.title)

Laravel - making vue work with other plugins

I have a project where I would like to use this theme. I just downloaded it and put its scripts into resources/assets/js directory. This is how I am calling all the scripts, after I run gulp, that I need for the page:
<!-- Scripts -->
<script
type="text/javascript" src="https://code.jquery.com/jquery-3.1.1.min.js"
integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8="
crossorigin="anonymous"></script>
<script src="/js/bootstrap.min.js" type="text/javascript"></script>
<script type="text/javascript" src="/js/material/material.min.js"></script>
<script type="text/javascript" src="/js/material/ripples.min.js"></script>
<script>$.material.init()</script>
<!-- Checkbox, Radio & Switch Plugins -->
<script src="/js/bootstrap-checkbox-radio.js"></script>
<!-- Notifications Plugin -->
<script src="/js/bootstrap-notify.js"></script>
<!-- Paper Dashboard Core javascript and methods for Demo purpose -->
<script src="/js/paper-dashboard.js"></script>
<script type="text/javascript">
$(document).ready(function(){
$.notify({
icon: 'ti-gift',
message: "Welcome to <b>Paper Dashboard</b> - a beautiful Bootstrap freebie for your next project."
},{
type: 'success',
timer: 4000
});
});
</script>
<script src="/js/app.js"></script>
But then I can't get bootstrap notify or tooltip work, if I remove app.js I get it working again, but then vue components are not working.
This is the app.js:
/**
* First we will load all of this project's JavaScript dependencies which
* include Vue and Vue Resource. This gives a great starting point for
* building robust, powerful web applications using Vue and Laravel.
*/
require('./bootstrap');
var VueResource = require('vue-resource');
/**
* Next, we will create a fresh Vue application instance and attach it to
* the body of the page. From here, you may begin adding components to
* the application, or feel free to tweak this setup for your needs.
*/
Vue.component('video-upload', require('./components/VideoUpload.vue'));
Vue.component('video-player', require('./components/VideoPlayer.vue'));
Vue.component('video-voting', require('./components/VideoVoting.vue'));
Vue.use(VueResource);
const app = new Vue({
el: 'body',
data: window.videoApp
});
And this is the gulpfile:
const elixir = require('laravel-elixir');
require('laravel-elixir-vue');
/*
|--------------------------------------------------------------------------
| Elixir Asset Management
|--------------------------------------------------------------------------
|
| Elixir provides a clean, fluent API for defining some basic Gulp tasks
| for your Laravel application. By default, we are compiling the Sass
| file for our application, as well as publishing vendor resources.
|
*/
elixir(mix => {
mix.copy('resources/assets/js', 'public/js');
mix.copy('resources/assets/css', 'public/css');
mix.sass('app.scss')
.webpack('app.js');
});
Update
I have required as craig_h suggested at the bottom of my bootstrap.js files like this:
require('./bootstrap-checkbox-radio.js');
require('./bootstrap-notify.js');
require('./paper-dashboard.js');
But I get an error:
paper-dashboard.js?16eb:26Uncaught ReferenceError: lbd is not
defined(…)
This is the script paper-dashboard.js:
var fixedTop = false;
var navbar_initialized = false;
$(document).ready(function(){
window_width = $(window).width();
// Init navigation toggle for small screens
if(window_width <= 991){
lbd.initRightMenu();
}
// Activate the tooltips
$('[rel="tooltip"]').tooltip();
});
// activate collapse right menu when the windows is resized
$(window).resize(function(){
if($(window).width() <= 991){
lbd.initRightMenu();
}
});
lbd = {
misc:{
navbar_menu_visible: 0
},
initRightMenu: function(){
if(!navbar_initialized){
$off_canvas_sidebar = $('nav').find('.navbar-collapse').first().clone(true);
$sidebar = $('.sidebar');
sidebar_bg_color = $sidebar.data('background-color');
sidebar_active_color = $sidebar.data('active-color');
$logo = $sidebar.find('.logo').first();
logo_content = $logo[0].outerHTML;
ul_content = '';
// set the bg color and active color from the default sidebar to the off canvas sidebar;
$off_canvas_sidebar.attr('data-background-color',sidebar_bg_color);
$off_canvas_sidebar.attr('data-active-color',sidebar_active_color);
$off_canvas_sidebar.addClass('off-canvas-sidebar');
//add the content from the regular header to the right menu
$off_canvas_sidebar.children('ul').each(function(){
content_buff = $(this).html();
ul_content = ul_content + content_buff;
});
// add the content from the sidebar to the right menu
content_buff = $sidebar.find('.nav').html();
ul_content = ul_content + '<li class="divider"></li>'+ content_buff;
ul_content = '<ul class="nav navbar-nav">' + ul_content + '</ul>';
navbar_content = logo_content + ul_content;
navbar_content = '<div class="sidebar-wrapper">' + navbar_content + '</div>';
$off_canvas_sidebar.html(navbar_content);
$('body').append($off_canvas_sidebar);
$toggle = $('.navbar-toggle');
$off_canvas_sidebar.find('a').removeClass('btn btn-round btn-default');
$off_canvas_sidebar.find('button').removeClass('btn-round btn-fill btn-info btn-primary btn-success btn-danger btn-warning btn-neutral');
$off_canvas_sidebar.find('button').addClass('btn-simple btn-block');
$toggle.click(function (){
if(lbd.misc.navbar_menu_visible == 1) {
$('html').removeClass('nav-open');
lbd.misc.navbar_menu_visible = 0;
$('#bodyClick').remove();
setTimeout(function(){
$toggle.removeClass('toggled');
}, 400);
} else {
setTimeout(function(){
$toggle.addClass('toggled');
}, 430);
div = '<div id="bodyClick"></div>';
$(div).appendTo("body").click(function() {
$('html').removeClass('nav-open');
lbd.misc.navbar_menu_visible = 0;
$('#bodyClick').remove();
setTimeout(function(){
$toggle.removeClass('toggled');
}, 400);
});
$('html').addClass('nav-open');
lbd.misc.navbar_menu_visible = 1;
}
});
navbar_initialized = true;
}
}
}
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function() {
timeout = null;
if (!immediate) func.apply(context, args);
}, wait);
if (immediate && !timeout) func.apply(context, args);
};
};
My apologizes if this is a begginers question, but I have not used webpack or browserify before so I don't know how to setup all of this.
I don't use webpack I use browserify instead, but I think the problem is that you are using packages that rely on global variables, if you want to do that then you need to use an importer, see shimming modules
However, you can usually just get away with requiring them in: /resources/assets/js/bootstrap.js like so:
require('./bootstrap-checkbox-radio.js')
require('./bootstrap-notify.js')
require('./paper-dashboard.js')
Then just running gulp
To solve this:
paper-dashboard.js?16eb:26Uncaught ReferenceError: lbd is not
defined(…)
try edit paper-dashboard.js and define lbd to the global scope using window like this:
...
window['lbd'] = {
misc:{
navbar_menu_visible: 0
},
...

Defining Polymer element after importing ES6 code via System.js

I'm creating an HTML element using Polymer, and I want it to be able to work with an ES6 class I've written. Therefore, I need to import the class first and then register the element, which is what I do:
(function() {
System.import('/js/FoobarModel.js').then(function(m) {
window.FoobarModel = m.default;
window.FoobarItem = Polymer({
is: 'foobar-item',
properties: {
model: Object // instanceof FoobarModel === true
},
// ... methods using model and FoobarModel
});
});
})();
And it works well. But now I want to write a test HTML page to display my component with some dummy data:
<!DOCTYPE html>
<html lang="en">
<head>
<script src="/bower_components/webcomponentsjs/webcomponents.js"></script>
<script src="/bower_components/system.js/dist/system.js"></script>
<script>
System.config({
map:{
traceur: '/bower_components/traceur/traceur.min.js'
}
});
</script>
<link rel="import" href="/html/foobar-item.html">
</head>
<body>
<script>
(function() {
var data = window.data = [
{
city: {
name: 'Foobar City'
},
date: new Date('2012-02-25')
}
];
var view;
for (var i = 0; i < data.length; i++) {
view = new FoobarItem();
view.model = data[i];
document.body.appendChild(view);
}
})();
</script>
</body>
</html>
Which isn't working for one simple reason: the code in the <script> tag is executed before Polymer registers the element.
Thus I'd like to know if there's a way to load the ES6 module synchronously using System.js or even better, if it's possible to listen to a JavaScript event for the element registration (something like PolymerElementsRegistered)?
I've tried the following without success:
window.addEventListener('HTMLImportsLoaded', ...)
window.addEventListener('WebComponentsReady', ...)
HTMLImports.whenReady(...)
In the app/scripts/app.js script from the polymer starter kit, they use auto-binding template and dom-change event
// Grab a reference to our auto-binding template
var app = document.querySelector('#app');
// Listen for template bound event to know when bindings
// have resolved and content has been stamped to the page
app.addEventListener('dom-change', function() {
console.log('Our app is ready to rock!');
});
Also check this thread gives alternatives to the polymer-ready event.

A web component with ECMA6

index.html
<!DOCTYPE html>
<html>
<head>
<script src="https://google.github.io/traceur-compiler/bin/traceur.js"></script>
<script src="https://google.github.io/traceur-compiler/src/bootstrap.js"></script>
<script>
traceur.options.experimental = true;
</script>
<link rel="import" href="x-item.html">
</head>
<body>
<x-item></x-item>
</body>
</html>
and my web component:
x-item.html
<template id="itemtemplate">
<span>test</span>
</template>
<script type="module">
class Item extends HTMLElement {
constructor() {
let owner = document.currentScript.ownerDocument;
let template = owner.querySelector("#itemtemplate");
let clone = template.content.cloneNode(true);
let root = this.createShadowRoot();
root.appendChild(clone);
}
}
Item.prototype.createdCallback = Item.prototype.constructor;
Item = document.registerElement('x-item', Item);
</script>
and I get no error nor what I expect to be displayed, any idea if this should actually work?
Is this how one would extend an HTMLElement in ECMA6 syntax?
E: putting it altogether in one page solves the problem at least now I know its the right way to create a custom component, but the problem is having it in a separate file I think it has to do with how traceur handles <link rel="import" href="x-item.html"> I tried adding the type attribute to the import with no luck.
Traceur's inline processor does not appear to have support for finding <script> tags inside <link import>. All of traceur's code seems to access document directly, which results in traceur only looking at index.html and never seeing any <scripts> inside x-item.html. Here's a work around that works on Chrome. Change x-item.html to be:
<template id="itemtemplate">
<span>test</span>
</template>
<script type="module">
(function() {
let owner = document.currentScript.ownerDocument;
class Item extends HTMLElement {
constructor() {
// At the point where the constructor is executed, the code
// is not inside a <script> tag, which results in currentScript
// being undefined. Define owner above at compile time.
//let owner = document.currentScript.ownerDocument;
let template = owner.querySelector("#itemtemplate");
let clone = template.content.cloneNode(true);
let root = this.createShadowRoot();
root.appendChild(clone);
}
}
Item.prototype.createdCallback = Item.prototype.constructor;
Item = document.registerElement('x-item', Item);
})();
</script>
<script>
// Boilerplate to get traceur to compile the ECMA6 scripts in this include.
// May be a better way to do this. Code based on:
// new traceur.WebPageTranscoder().selectAndProcessScripts
// We can't use that method as it accesses 'document' which gives the parent
// document, not this include document.
(function processInclude() {
var doc = document.currentScript.ownerDocument,
transcoder = new traceur.WebPageTranscoder(doc.URL),
selector = 'script[type="module"],script[type="text/traceur"]',
scripts = doc.querySelectorAll(selector);
if (scripts.length) {
transcoder.addFilesFromScriptElements(scripts, function() {
console.log("done processing");
});
}
})();
</script>
Another possible solution would be to pre-compile the ECMA6 into ECMA5 and include the ECMA5 only. This would avoid the problem of traceur not finding the <script> tags in the import and would remove the need for the boilerplate.

Categories

Resources