I'm using editor js (a block-style editor) for my app. You can install plugins for this editor to extend is functionality. On GitHub there is a math block plugin that I was trying to use there. But I can't get it to work. Hower here is my code:
First I include the plugin (which is the bundle.js on GitHub):
<script type="text/javascript" src="./plugins/editorjs/blocks/math.js"></script><!-- Math -->
Then I just folowed the normal setup (which worked for the other official plugins)
var editor = new EditorJS({
...
tools: {
...
Math: {
class: Math,
},
}
...
});
When I try to type something I get the error TypeError: Math.floor is not a function. Did I miss something?
Edit
https://flaming-cl.github.io/editorPlugin/
There is this example which works, but I don't understand it, there are so many files. I need more like a basic example with just the necessary CDN files.
Okey, I just remade the Plugin with Katex and it's working fine now. Here is my code, maybe it helps someone. Please keep in mind that this code may have bugs etc...
formala.js
class Formula {
static get toolbox() {
return {
title: 'Formula',
icon: '<?xml version="1.0" encoding="iso-8859-1"?>\r\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\r\n<svg version="1.1" id="fxicon" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"\r\n\t width="15px" height="15px" viewBox="0 0 142.514 142.514" style="enable-background:new 0 0 142.514 142.514;"\r\n\t xml:space="preserve">\r\n<g>\r\n\t<g>\r\n\t\t<path d="M34.367,142.514c11.645,0,17.827-10.4,19.645-16.544c0.029-0.097,0.056-0.196,0.081-0.297\r\n\t\t\tc4.236-17.545,10.984-45.353,15.983-65.58h17.886c3.363,0,6.09-2.726,6.09-6.09c0-3.364-2.727-6.09-6.09-6.09H73.103\r\n\t\t\tc1.6-6.373,2.771-10.912,3.232-12.461l0.512-1.734c1.888-6.443,6.309-21.535,13.146-21.535c6.34,0,7.285,9.764,7.328,10.236\r\n\t\t\tc0.27,3.343,3.186,5.868,6.537,5.579c3.354-0.256,5.864-3.187,5.605-6.539C108.894,14.036,104.087,0,89.991,0\r\n\t\t\tC74.03,0,68.038,20.458,65.159,30.292l-0.49,1.659c-0.585,1.946-2.12,7.942-4.122,15.962H39.239c-3.364,0-6.09,2.726-6.09,6.09\r\n\t\t\tc0,3.364,2.726,6.09,6.09,6.09H57.53c-6.253,25.362-14.334,58.815-15.223,62.498c-0.332,0.965-2.829,7.742-7.937,7.742\r\n\t\t\tc-7.8,0-11.177-10.948-11.204-11.03c-0.936-3.229-4.305-5.098-7.544-4.156c-3.23,0.937-5.092,4.314-4.156,7.545\r\n\t\t\tC13.597,130.053,20.816,142.514,34.367,142.514z"/>\r\n\t\t<path d="M124.685,126.809c3.589,0,6.605-2.549,6.605-6.607c0-1.885-0.754-3.586-2.359-5.474l-12.646-14.534l12.271-14.346\r\n\t\t\tc1.132-1.416,1.98-2.926,1.98-4.908c0-3.59-2.927-6.231-6.703-6.231c-2.547,0-4.527,1.604-6.229,3.684l-9.531,12.454L98.73,78.391\r\n\t\t\tc-1.89-2.357-3.869-3.682-6.7-3.682c-3.59,0-6.607,2.551-6.607,6.609c0,1.885,0.756,3.586,2.357,5.471l11.799,13.592\r\n\t\t\tL86.647,115.67c-1.227,1.416-1.98,2.926-1.98,4.908c0,3.589,2.926,6.229,6.699,6.229c2.549,0,4.53-1.604,6.229-3.682l10.19-13.4\r\n\t\t\tl10.193,13.4C119.872,125.488,121.854,126.809,124.685,126.809z"/>\r\n\t</g>\r\n</g>\r\n<g>\r\n</g>\r\n<g>\r\n</g>\r\n<g>\r\n</g>\r\n<g>\r\n</g>\r\n<g>\r\n</g>\r\n<g>\r\n</g>\r\n<g>\r\n</g>\r\n<g>\r\n</g>\r\n<g>\r\n</g>\r\n<g>\r\n</g>\r\n<g>\r\n</g>\r\n<g>\r\n</g>\r\n<g>\r\n</g>\r\n<g>\r\n</g>\r\n<g>\r\n</g>\r\n</svg>'
};
}
constructor({data}){
this.data = data;
}
render(){
this.wrapper = document.createElement('div');
const input = document.createElement('input');
const inner = document.createElement('div');
this.wrapper.classList.add('formula-block');
this.wrapper.appendChild(inner).classList.add('inner-formula-wrapper');
this.wrapper.appendChild(input).classList.add('cdx-input');
input.placeholder = 'Enter a LaText formala here...';
var value = this.data && this.data.text ? this.data.text : '';
input.value = value;
//.replace(/\\/g,"\\\\");
this._createFormala(this.data && this.data.text ? this.data.text : 'eqation: ');
var eventList = ["change", "keyup", "paste", "input", "propertychange", "..."];
for(event of eventList) {
input.addEventListener(event, (event) => {
this._createFormala(input.value.replace(/\$/g, ''));
});
}
inner.onclick = function() {
this.closest('.formula-block').classList.toggle('is-edit');
}
return this.wrapper;
}
_createFormala(text){
const input = document.createElement('input');input.value = text;
var rendered_formula = katex.renderToString(text, {
throwOnError: false
});
input.placeholder = 'Caption...';
this.wrapper.getElementsByClassName('inner-formula-wrapper').item(0).innerHTML = rendered_formula;
}
save(blockContent){
const input = blockContent.querySelector('input');
return {
text: input.value.replace(/\$/g, '')
}
}
validate(savedData){
if (!savedData.text.trim()){
return false;
}
return true;
}
}
Tools list config
var editor = EditorJS({
...
tools: {
...
formula: Formula,
}
...
});
Load the Katext Files and formala.js
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex#0.12.0/dist/katex.min.css" integrity="sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X" crossorigin="anonymous">
<script defer src="https://cdn.jsdelivr.net/npm/katex#0.12.0/dist/katex.min.js" integrity="sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4" crossorigin="anonymous"></script>
<script type="text/javascript" src="./plugins/editorjs/blocks/formula.js"></script><!-- Formula -->
Data Input Usage
{
"type": "formula",
"data": {
"text": '\\pm\\sqrt{a^2 + b^2} \\newline a ',
}
},
Related
I'm learning vuejs and trying to do all without jquery
I need to get a value of a css style line-height.
In jquery i would do:
let x = $(this).css("line-height");
How can I get this value using vuejs 2.5?
I was exploring this.$el in this structure, but can't find solution to get this value:
data: function () {
return {
lineHeight: null
}
},
mounted(){
this.lineHeight = ?
}
tl;dr
// with jQuery: $(this).css("line-height");
// with Vue:
mounted() {
this.lineHeight = window.getComputedStyle(this.$el).getPropertyValue('line-height');
}
If the component (this.$el) may be inside an iframe or popup, or if you want to be extra careful, read on.
JSFiddle demo here.
new Vue({
el: '#app',
data: {
lineHeightTLDR: '',
lineHeightFull: '',
},
mounted(){
this.lineHeightTLDR = window.getComputedStyle(this.$el).getPropertyValue('line-height');
this.lineHeightFull = this.css('line-height');
},
methods: {
css(propertyName) {
let view = this.$el.ownerDocument.defaultView;
if ( !view || !view.opener ) {
view = window;
}
let computed = view.getComputedStyle(this.$el);
return computed.getPropertyValue(propertyName) || computed[propertyName];
}
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<pre>lineHeight tl;dr..: {{ lineHeightTLDR }}</pre>
<pre>lineHeight full...: {{ lineHeightFull }}</pre>
</div>
Background
Simplest way to mimic jQuery is just to take a look at its source. The returned value from .css() is, roughly:
ret = computed.getPropertyValue( name ) || computed[ name ];
Which uses CSSStyleDeclaration.getPropertyValue on computed. And computed is:
return function( elem ) {
var view = elem.ownerDocument.defaultView;
if ( !view || !view.opener ) {
view = window;
}
return view.getComputedStyle( elem );
}
Which uses Window.getComputedStyle() As you can see, the returned value is something around:
ret = view.getComputedStyle(elem).getPropertyValue( name ) || view.getComputedStyle(elem)[name];
Where view is most probably window but could be something else (elem.ownerDocument.defaultView).
In the end of the day, if you want to be extra certain and do very close to jQuery.css(), use:
// with jQuery: $(this).css("line-height");
// with Vue:
mounted(){
this.lineHeight = this.css('line-height');
},
methods: {
css(propertyName) {
let view = elem.ownerDocument.defaultView;
if ( !view || !view.opener ) {
view = window;
}
let computed = view.getComputedStyle(this.$el);
ret = computed.getPropertyValue(propertyName) || computed[propertyName];
}
}
But if you know your usage does not rely on iframes or popups (as it is very unusual for a Vue instance JavaScript code to run at a window and have the $el it is attached to on another), go with the tl;dr version.
I'm working on an app with both Firebase (web app) and the youtube API. The idea is to let users share their playlists and interact.
1) First, the user must identify himself. This part works very well (firebase part)
2) Then, the user must accept some conditions (the scopes) from the youtube API.
3) The Youtube API return the result of the request.
The issue is that youtube API recreate for every item of the array the HTML structure : HTML > HEAD > BODY. Plus, the response skips my header and display the all thing in a blank page instead of reacting like an include PHP (i know this is javascript but still.. ).
I know the solution rest in the function executerequest but i can't figurate how to do it.
The code of the request is in the middle of the body but for the purpose of my post, i did separate it.
var GoogleAuth;
var SCOPE = 'https://www.googleapis.com/auth/youtube.force-ssl';
function handleClientLoad() {
// Load the API's client and auth2 modules.
// Call the initClient function after the modules load.
gapi.load('client:auth2', initClient);
}
function initClient() {
// Retrieve the discovery document for version 3 of YouTube Data API.
// In practice, your app can retrieve one or more discovery documents.
var discoveryUrl = 'https://www.googleapis.com/discovery/v1/apis/youtube/v3/rest';
// Initialize the gapi.client object, which app uses to make API requests.
// Get API key and client ID from API Console.
// 'scope' field specifies space-delimited list of access scopes.
gapi.client.init({
'apiKey': '...',
'discoveryDocs': [discoveryUrl],
'clientId': '....',
'scope': SCOPE
}).then(function () {
GoogleAuth = gapi.auth2.getAuthInstance();
// Listen for sign-in state changes.
GoogleAuth.isSignedIn.listen(updateSigninStatus);
// Handle initial sign-in state. (Determine if user is already signed in.)
var user = GoogleAuth.currentUser.get();
setSigninStatus();
// Call handleAuthClick function when user clicks on
// "Sign In/Authorize" button.
$('#sign-in-or-out-button').click(function() {
handleAuthClick();
});
$('#revoke-access-button').click(function() {
revokeAccess();
});
});
}
function handleAuthClick() {
if (GoogleAuth.isSignedIn.get()) {
// User is authorized and has clicked 'Sign out' button.
$('#sign-in-or-out-button').html('Sign out');
$('#revoke-access-button').css('display', 'inline-block');
GoogleAuth.signOut();
} else {
// User is not signed in. Start Google auth flow.
GoogleAuth.signIn();
}
}
function revokeAccess() {
GoogleAuth.disconnect();
}
function setSigninStatus(isSignedIn) {
var user = GoogleAuth.currentUser.get();
var isAuthorized = user.hasGrantedScopes(SCOPE);
if (isAuthorized) {
$('#sign-in-or-out-button').html('Sign out');
$('#revoke-access-button').css('display', 'inline-block');
$('#auth-status').html('Connecté ' +
' Granted');
defineRequest();
console.log('connecté');
} else {
$('#roomRed').html('display', 'block');
$('#sign-in-or-out-button').html('Sign In/Authorize');
$('#revoke-access-button').css('display', 'none');
$('#auth-status').html('Déconnecté' +
' Denied');
console.log('déconnecté');
}
// This helper method displays a message on the page.
}
function updateSigninStatus(isSignedIn) {
setSigninStatus();
}
function createResource(properties) {
var resource = {};
var normalizedProps = properties;
for (var p in properties) {
var value = properties[p];
if (p && p.substr(-2, 2) == '[]') {
var adjustedName = p.replace('[]', '');
if (value) {
normalizedProps[adjustedName] = value.split(',');
}
delete normalizedProps[p];
}
}
for (var p in normalizedProps) {
// Leave properties that don't have values out of inserted resource.
if (normalizedProps.hasOwnProperty(p) && normalizedProps[p]) {
var propArray = p.split('.');
var ref = resource;
for (var pa = 0; pa < propArray.length; pa++) {
var key = propArray[pa];
if (pa == propArray.length - 1) {
ref[key] = normalizedProps[p];
} else {
ref = ref[key] = ref[key] || {};
}
}
};
}
return resource;
}
function removeEmptyParams(params) {
for (var p in params) {
if (!params[p] || params[p] == 'undefined') {
delete params[p];
}
}
return params;
}
function executeRequest(request) {
request.execute(function(response) {
console.log(response);
for(var i = 0; i< response.items.length; i++){
console.log(response.items[i].player.embedHtml);
document.write(response.items[i].player.embedHtml);
}
});
}
function buildApiRequest(requestMethod, path, params, properties) {
params = removeEmptyParams(params);
var request;
if (properties) {
var resource = createResource(properties);
request = gapi.client.request({
'body': resource,
'method': requestMethod,
'path': path,
'params': params
});
} else {
request = gapi.client.request({
'method': requestMethod,
'path': path,
'params': params
});
}
executeRequest(request);
}
/***** END BOILERPLATE CODE *****/
function defineRequest() {
// See full sample for buildApiRequest() code, which is not
// specific to a particular youtube or youtube method.
buildApiRequest('GET',
'/youtube/v3/playlists',
{
'mine': 'true',
'maxResults': '25',
'part': 'snippet,contentDetails,player',
'onBehalfOfContentOwner': '',
'onBehalfOfContentOwnerChannel': ''
});
/*
buildApiRequest('GET',
'/youtube/v3/playlistItems',
{
'playlistId': "PLsvlo6Soc2pc2ZlereiehdPRhm0eKjSxI",
'maxResults': '25',
'part': 'snippet,contentDetails'
});
*/
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Mes vidéo </title>
<style>
</style>
</head>
<body>
<button id="sign-in-or-out-button"
style="margin-left: 25px">Sign In/Authorize</button>
<button id="revoke-access-button"
style="display: none; margin-left: 25px">Revoke access</button>
<div id="auth-status" style="display: inline; padding-left: 25px"></div><hr>
<div id="video-container"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script async defer src="https://apis.google.com/js/api.js"
onload="this.onload=function(){};handleClientLoad()"
onreadystatechange="if (this.readyState === 'complete') this.onload()">
</script>
</body>
</html>
Thank you by advance
I have used the azure media player with my project, it will play multiple adaptive bit rate streamed videos in an asp.net page, the best part is, it is working superb in html5 and flash but it will get stuck at spinner image in silverlight fallback.
Below is the code I have used.
I have also tried to get errors but it is not hitting the event listener code added for errors, but the play and pause events are working fine where flash and html5 is used, but silverlight fallback is not working at all.
<link href="https://amp.azure.net/libs/amp/1.3.0/skins/amp-default/azuremediaplayer.min.css" rel="stylesheet">
<script src="https://amp.azure.net/libs/amp/1.3.0/azuremediaplayer.min.js"></script>
<div class="marginBlock">
<h3>
<asp:Label ID="lblTitle" runat="server"><%=Title.ToString()%></asp:Label>
</h3>
<video id="<%=mediaPlayerID %>" class="azuremediaplayer amp-default-skin amp-big-play-centered">
<p class="amp-no-js">
To view this video please enable JavaScript, and consider upgrading to a web browser that supports HTML 5 video.
</p>
</video>
</div>
<p>
<asp:Label ID="lblDescription" runat="server"><%=Description.ToString()%>
</asp:Label>
</p>
<script>
$(document).ready(function () {
var playOptions = {
"nativeControlsForTouch": false,
techOrder: ['azureHtml5JS', 'flashSS', 'silverlightSS', 'html5'],
autoplay: false,
controls: true,
width: '100%',
height: '400',
logo: { enabled: false },
poster: "<%=ImageSelector%>"
}
var azurePlayer = amp('<%=mediaPlayerID%>', playOptions);
azurePlayer.src([{
src: "<%=VideoURL%>",
type: 'application/vnd.ms-sstr+xml'
}]);
azurePlayer.addEventListener("error", function () {
var errorDetails = azurePlayer.error();
var code = errorDetails.code;
var message = errorDetails.message;
alert(errorDetails + ' ' + code + " " + message);
if (azurePlayer.error().code & amp.errorCode.abortedErrStart) {
console.log("abortedErrStart");
}
else if (azurePlayer.error().code & amp.errorCode.networkErrStart) {
// MEDIA_ERR_NETWORK errors
console.log("networkErrStart");
}
else if (azurePlayer.error().code & amp.errorCode.decodeErrStart) {
// MEDIA_ERR_DECODE errors
console.log("decodeErrStart");
}
else if (azurePlayer.error().code & amp.errorCode.srcErrStart) {
// MEDIA_ERR_SRC_NOT_SUPPORTED errors
console.log("srcErrStart");
}
else if (azurePlayer.error().code & amp.errorCode.encryptErrStart) {
// MEDIA_ERR_ENCRYPTED errors
console.log("encryptErrStart");
}
else if (azurePlayer.error().code & amp.errorCode.srcPlayerMismatchStart) {
// SRC_PLAYER_MISMATCH errors
console.log("srcPlayerMismatchStart");
}
else {
// unknown errors
console.log("unknown");
}
});
azurePlayer.addEventListener('play', function () {
console.log('play');
});
azurePlayer.addEventListener('pause', function () {
console.log('pause');
});
});
Updated
Noticed I am getting following error for IE < 11.
Also I disabled the flash in firefox and removed the silverlight from techOrder, then it should hit the error event listener, it is not hitting.
This is also important for me to handle the analytics for error.
Play and Pause event listener are working fine.
Update 8/28/2015:
Fixed the JS error, it is because of multiple call to cdn of azure mentioned in link above, moved the code in master page and load it only once, browser like chrome handles duplicity of code easily but not IE.
After all the research I am lost why it is not working.
So added the following JS that will check for Silverlight and Flash and gracefully handle the error and update our analytics as well.
function getBrowserInformation() {
var ua = navigator.userAgent, tem, M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
if (/trident/i.test(M[1])) {
tem = /\brv[ :]+(\d+)/g.exec(ua) || [];
return { name: 'IE ', version: (tem[1] || '') };
}
if (M[1] === 'Chrome') {
tem = ua.match(/\bOPR\/(\d+)/)
if (tem != null) { return { name: 'Opera', version: tem[1] }; }
}
M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?'];
if ((tem = ua.match(/version\/(\d+)/i)) != null) { M.splice(1, 1, tem[1]); }
return {
name: M[0],
version: M[1]
};};
function checkForAzureErrors() {
function isSilverlightInstalled() {
var isSilverlightInstalled = false;
try {
//check on IE
try {
var slControl = new ActiveXObject('AgControl.AgControl');
isSilverlightInstalled = true;
}
catch (e) {
//either not installed or not IE. Check Firefox/Safari
if (navigator.plugins["Silverlight Plug-In"]) {
isSilverlightInstalled = true;
}
}
}
catch (e) {
console.log(e);
}
return isSilverlightInstalled;
}
function isFlashInstalled() {
try {
return Boolean(new ActiveXObject('ShockwaveFlash.ShockwaveFlash'));
} catch (exception) {
return ('undefined' != typeof navigator.mimeTypes['application/x-shockwave-flash']);
}
}
function addErrorMessage() {
$($("#mediaPlayer.marginBlock").find('h3')).text('Media is not supported on this browser or device.');
$($("#mediaPlayer.marginBlock").find('video')).css('display', 'none');
$($("#mediaPlayer.marginBlock").find('p')).css('display', 'none');
$('.azuremediaplayer').css('display', 'none');
ga("send", "event", "Videos", "error",
getBrowserInformation().name + getBrowserInformation().version +
": is silverlight Installed " + isSilverlightInstalled() +
" and is Flash Installed " + isFlashInstalled());
}
function checkBrowser() {
if ((getBrowserInformation().name === 'MSIE' || getBrowserInformation().name === 'IE')) {
if (getBrowserInformation().version < 11) {
addErrorMessage();
}
} else if (getBrowserInformation().name === 'Firefox') {
addErrorMessage();
}
}
if ((getBrowserInformation().name === 'MSIE' || getBrowserInformation().name === 'IE')) {
if (getBrowserInformation().version < 9) { addErrorMessage() }
}
for (var key in amp.players) {
if (amp.players.hasOwnProperty(key)) {
if (isSilverlightInstalled()) {
if (!amp.players[key].isReady_) {
checkBrowser();
}
} else if (!isFlashInstalled()) {
checkBrowser();
}
}
}}
This function is called after 5 second of page load in document.ready function, giving it enough time to load and make the isReady_boolean true.
SetTimeout(function () { checkForAzureErrors(); }, 5000);
Still I am waiting for some angel to resolve this issue.
Updates: Partially Fixed
Need to add the xap refrences like older version, it will play silverlight but there is a catch, it will only work if you have one video per page.
<script>
amp.options.flashSS.swf = "http://amp.azure.net/libs/amp/1.3.0/techs/StrobeMediaPlayback.2.0.swf"
amp.options.flashSS.plugin = "http://amp.azure.net/libs/amp/1.3.0/techs/MSAdaptiveStreamingPlugin-osmf2.0.swf"
amp.options.silverlightSS.xap = "http://amp.azure.net/libs/amp/1.3.0/techs/SmoothStreamingPlayer.xap"
</script>
Fixed
As per comments from Amit Rajput
#Parshii Currently, as per the documentation, Azure Media Player doesn't support multi-instance playback. While it may work on some techs, it is not a tested scenario at this time. Please feel free to add it to the UserVoice forum (http://aka.ms/ampuservoice).
As per my testing it is working in html5 and flash but not Silverlight, for silverlight support we can try using iframes as per comments from rnrneverdies
Single Instance Media Player is working in all techs.
#Parshii Currently, as per the documentation, Azure Media Player doesn't support multi-instance playback. While it may work on some techs, it is not a tested scenario at this time. Please feel free to add it to the UserVoice forum (http://aka.ms/ampuservoice).
This may be not a complete answer, but could help you.
I made the following plugin for the Azure Media Player 1.3.0 which logs all the activity performed by the user and also the errors.
Set up it as:
var mylogFunction = function(data) { console.log(data); };
var options = {
techOrder: ["azureHtml5JS", "flashSS", "silverlightSS", "html5"],
nativeControlsForTouch: false,
loop: false,
logo: { enabled: false },
heuristicProfile: "Quick Start", //"High Quality", // could be "Quick Start"
customPlayerSettings: {
customHeuristicSettings: {
preRollInSec: 4,
windowSizeHeuristics: true
}
},
plugins: {
DebugLog: {
logFunction: mylogFunction
}
}
};
var amPlayer = amp("yourvideotagid", options);
Plugin Code:
var amp;
(function (amp) {
amp.plugin('DebugLog', DebugLog);
function DebugLog(options) {
var player = this;
var log = function (data) { console.log("Azure Media Player Log", data); }
if (options) {
if (options['logFunction']) {
log = options['logFunction'];
}
}
init();
function init() {
player.ready(handleReady);
player.addEventListener(amp.eventName.error, handleError);
}
function handleReady() {
player.addEventListener(amp.eventName.loadedmetadata, handleLoadedMetaData);
var data = {
ampVersion: "1.3.0",
appName: options['appName'],
userAgent: navigator.userAgent,
options: {
autoplay: player.options().autoplay,
heuristicProfile: player.options().heuristicProfile,
techOrder: JSON.stringify(player.options().techOrder)
}
};
logData("InstanceCreated", 1, data);
}
function handleError() {
var err = player.error();
var data = {
sessionId: player.currentSrc(),
currentTime: player.currentTime(),
code: "0x" + err.code.toString(16),
message: err.message
};
logData("Error", 0, data);
}
function handleLoadedMetaData() {
player.addEventListener(amp.eventName.playbackbitratechanged, handlePlaybackBitrateChanged);
player.addEventListener(amp.eventName.playing, handlePlaying);
player.addEventListener(amp.eventName.seeking, handleSeeking);
player.addEventListener(amp.eventName.pause, handlePaused);
player.addEventListener(amp.eventName.waiting, handleWaiting);
player.addEventListener(amp.eventName.ended, handleEnded);
if (player.audioBufferData()) {
player.audioBufferData().addEventListener(amp.bufferDataEventName.downloadfailed, function () {
var data = {
sessionId: player.currentSrc(),
currentTime: player.currentTime(),
bufferLevel: player.audioBufferData().bufferLevel,
url: player.audioBufferData().downloadFailed.mediaDownload.url,
code: "0x" + player.audioBufferData().downloadFailed.code.toString(16),
message: player.audioBufferData().downloadFailed
};
logData("DownloadFailed", 0, data);
});
}
if (player.videoBufferData()) {
player.videoBufferData().addEventListener(amp.bufferDataEventName.downloadfailed, function () {
var data = {
sessionId: player.currentSrc(),
currentTime: player.currentTime(),
bufferLevel: player.videoBufferData().bufferLevel,
url: player.videoBufferData().downloadFailed.mediaDownload.url,
code: "0x" + player.videoBufferData().downloadFailed.code.toString(16),
message: player.videoBufferData().downloadFailed
};
logData("DownloadFailed", 0, data);
});
}
var data = {
sessionId: player.currentSrc(),
isLive: player.isLive(),
duration: player.duration(),
tech: player.currentTechName(),
protection: ((player.currentProtectionInfo() && player.currentProtectionInfo()[0]) ? player.currentProtectionInfo()[0].type : "clear")
};
logData("PresentationInfo", 1, data);
}
function handlePlaybackBitrateChanged(event) {
logData("BitrateChanged", 1, eventData(event));
}
function handleWaiting(event) {
logData("Waiting", 0, eventData(event));
}
function handlePlaying(event) {
logData("Playing", 1, eventData(event));
}
function handleSeeking(event) {
logData("Seeking", 1, eventData(event));
}
function handlePaused(event) {
logData("Paused", 1, eventData(event));
}
function handleEnded(event) {
logData("Ended", 1, eventData(event));
}
function logData(eventId, level, data) {
var eventLog = {
eventId: eventId,
level: level,
data: data
};
log(eventLog);
}
function eventData(event) {
return {
sessionId: player.currentSrc(),
currentTime: player.currentTime(),
isLive: player.isLive(),
event: event.type,
presentationTimeInSec: event.presentationTimeInSec,
message: event.message ? event.message : ""
};
}
}
})(amp || (amp = {}));
Google adds styles to the maps container that override my styles.
I know how to fix this. But the API (v3.8/9/exp) also loads the webfont "Roboto" which I don't really need/want.
Is there any setting/option/way around this?
Can I prevent the API from adding the extra CSS?
This is the code the google-maps-API adds to the <head> of my page:
<style type="text/css">
.gm-style .gm-style-cc span,
.gm-style .gm-style-cc a,
.gm-style .gm-style-mtc div {
font-size:10px
}
</style>
<link type="text/css"
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700">
<style type="text/css">
#media print {
.gm-style .gmnoprint,
.gmnoprint {
display:none
}
}
#media screen {
.gm-style .gmnoscreen,
.gmnoscreen {
display:none
}
}
</style>
<style type="text/css">
.gm-style {
font-family: Roboto,Arial,sans-serif;
font-size: 11px;
font-weight: 400;
text-decoration: none
}
</style>
You can replace the insertBefore method before the Google script invokes it:
http://jsfiddle.net/coma/7st6d9p2/
var head = document.getElementsByTagName('head')[0];
// Save the original method
var insertBefore = head.insertBefore;
// Replace it!
head.insertBefore = function (newElement, referenceElement) {
if (newElement.href && newElement.href.indexOf('//fonts.googleapis.com/css?family=Roboto') > -1) {
console.info('Prevented Roboto from loading!');
return;
}
insertBefore.call(head, newElement, referenceElement);
};
// Check it!
new google.maps.Map(document.getElementById('map'), {
center : new google.maps.LatLng(51.508742,-0.120850),
zoom : 16,
mapTypeId : google.maps.MapTypeId.ROADMAP,
streetViewControl: false,
zoomControl : false,
panControl : false,
mapTypeControl : false
});
UPDATE 10/2017
Google changed the approach of how they inject the styles on the page. Currently they inserting an empty style element and then changing the contents of this style element with Robot font. Here is a new solution:
// Preventing the Google Maps libary from downloading an extra font
(function() {
var isRobotoStyle = function (element) {
// roboto font download
if (element.href
&& element.href.indexOf('https://fonts.googleapis.com/css?family=Roboto') === 0) {
return true;
}
// roboto style elements
if (element.tagName.toLowerCase() === 'style'
&& element.styleSheet
&& element.styleSheet.cssText
&& element.styleSheet.cssText.replace('\r\n', '').indexOf('.gm-style') === 0) {
element.styleSheet.cssText = '';
return true;
}
// roboto style elements for other browsers
if (element.tagName.toLowerCase() === 'style'
&& element.innerHTML
&& element.innerHTML.replace('\r\n', '').indexOf('.gm-style') === 0) {
element.innerHTML = '';
return true;
}
// when google tries to add empty style
if (element.tagName.toLowerCase() === 'style'
&& !element.styleSheet && !element.innerHTML) {
return true;
}
return false;
}
// we override these methods only for one particular head element
// default methods for other elements are not affected
var head = $('head')[0];
var insertBefore = head.insertBefore;
head.insertBefore = function (newElement, referenceElement) {
if (!isRobotoStyle(newElement)) {
insertBefore.call(head, newElement, referenceElement);
}
};
var appendChild = head.appendChild;
head.appendChild = function (textNode) {
if (!isRobotoStyle($(textNode)[0])) {
appendChild.call(head, textNode);
}
};
})();
ORIGINAL ANSWER
Thanks to coma for the solution! I also decided to intercept styles which override the font-family, font-size and font-weight. The complete solution for modern browsers and IE8+:
// Preventing the Google Maps libary from downloading an extra font
var head = $('head')[0];
var insertBefore = head.insertBefore;
head.insertBefore = function (newElement, referenceElement) {
// intercept font download
if (newElement.href
&& newElement.href.indexOf('https://fonts.googleapis.com/css?family=Roboto') === 0) {
return;
}
// intercept style elements for IEs
if (newElement.tagName.toLowerCase() === 'style'
&& newElement.styleSheet
&& newElement.styleSheet.cssText
&& newElement.styleSheet.cssText.replace('\r\n', '').indexOf('.gm-style') === 0) {
return;
}
// intercept style elements for other browsers
if (newElement.tagName.toLowerCase() === 'style'
&& newElement.innerHTML
&& newElement.innerHTML.replace('\r\n', '').indexOf('.gm-style') === 0) {
return;
}
insertBefore.call(head, newElement, referenceElement);
};
I found above solution to prevent websites with Google Maps from loading Roboto.
If you - like I do - use Wordpress, there might be other plugins referring to Google Fonts.
However, I struggled on some of my websites with the above code, since parts of it (1) affected also other styles to load, (2) "killed" styles, which intentionally not only contained gm-style, but other styles as well and (3) not affected other Google Fonts to load, where one or another plugin added links to fonts.googleapis.com by DOM-manipulation as well.
The below worked for me. It simply prevents other scripts from adding any tag having https://fonts.googleapis.com in it's href-attribute.
(function($) {
var isGoogleFont = function (element) {
// google font download
if (element.href
&& element.href.indexOf('https://fonts.googleapis.com') === 0) {
return true;
}
return false;
}
// we override these methods only for one particular head element
// default methods for other elements are not affected
var head = $('head')[0];
var insertBefore = head.insertBefore;
head.insertBefore = function (newElement, referenceElement) {
if (!isGoogleFont(newElement)) {
insertBefore.call(head, newElement, referenceElement);
}
};
var appendChild = head.appendChild;
head.appendChild = function (textNode) {
if (!isGoogleFont($(textNode)[0])) {
appendChild.call(head, textNode);
}
};
})(jQuery);
Unfortunately, I'm a newb and I couldn't get the other suggestions to work. So I removed all the Google fonts from the DOM. I hope it helps.
const googleFont = document.querySelector('link[rel="stylesheet"][href*="fonts.googleapis.com"]');
if (googleFont) {
googleFont.remove();
}
For TypeScript a solution would be:
const head = document.head;
const insertBefore = head.insertBefore;
head.insertBefore = <T extends Node>(
newElement: T,
referenceElement: Node
): T => {
if (
newElement instanceof Element &&
newElement?.hasAttribute('href') &&
newElement?.getAttribute('href')?.includes('fonts.googleapis')
) {
return newElement;
}
insertBefore.call(head, newElement, referenceElement);
return newElement;
};
I have a standard template in an Html file like:
<template name="cards">
{{#each all_cards}}
{{> card_item}}
{{/each}}
</template>
<template name="card_item">
<div class="card" style="left:{{position.x}}px; top:{{position.y}}px">
{{title}}
</div>
</template>
I want to have the cards (css selector .card) become draggable with JQuery.
Now since Meteor automagically updates the DOM using the template, when and how do I know where to call .draggable() on what??
EDIT: This is so far my solution which makes pending movements on other client visible with a wobble animation (using CSS3):
Template.card_item.events = {
'mouseover .card': function (e) {
var $target = $(e.target);
var $cardContainer = $target.hasClass('card') ? $target : $target.parents('.card');
$cardContainer.draggable({containment: "parent", distance: 3});
},
'dragstart .card': function (e) {
Session.set("dragging_id", e.target.id);
$(e.target).addClass("drag");
pos = $(e.target).position();
Events.insert({type: "dragstart", id:e.target.id, left: pos.left, top: pos.top});
},
'dragstop .card': function (e) {
pos = $(e.target).position();
Events.insert({type: "dragstop", id:e.target.id, left: pos.left, top: pos.top});
Cards.update(e.target.id, {$set: {left:pos.left, top:pos.top}});
Session.set("dragging_id", null);
}
}
Events.find().observe({
added: function(event) {
if (event.type == "dragstart" && !Session.equals("dragging_id", event.id)) {
$("#"+event.id).draggable({disabled: true});
$("#"+event.id).addClass("wobble");
}
if (event.type == "dragstop" && !Session.equals("dragging_id", event.id)) {
$("#"+event.id).animate({left: event.left, top: event.top}, 250);
Events.remove({id:this.id});
$("#"+event.id).draggable({disabled: false});
}
}
});
EDIT: This approach doesn't seem to work in the latest versions of Meteor, e.g. v0.5.0. See my comment below.
Looks like we're working on similar things! I've got a working proof of concept for a simple Magic: The Gathering app. Here's how I have dragging implemented at the moment:
In a <head> section in one of your html files, include the jQuery UI script:
<script src="jquery-ui-1.8.20.custom.min.js"></script>
Then, in a js file, make sure elements become draggable on mouseover (note: this is sub-optimal on touchscreens since it requires two touches to drag... I'm looking for a better touchscreen solution):
Template.card_item.events['mouseover .card, touchstart .card'] = function (e) {
var $target = $(e.target);
if (!$target.data('isDraggable')) {
$target.data('isDraggable', true).draggable();
}
};
And finally, handle the drag and dragstop events:
var prevDraggedTime = 0
Template.card_item.events['drag .card'] = function (e) {
// get the cardId from e
var now = new Date().getTime();
var position;
if (now - prevDraggedTime > 250) {
position = $(e.target).position();
Cards.update(cardId, {$set: {x: position.top, y: position.left}});
prevDraggedTime = now;
}
}
Template.card_item.events['dragstop .card'] = function (e) {
// get the cardId from e
var position = $(e.target).position();
Cards.update(cardId, {$set: {x: position.top, y: position.left}});
}