I have custom polymer component that will load my translations for whole application. Here is the code:
<link rel="import" href="../polymer/polymer-expressions/polymer-expressions.html">
<link rel="import" href="../polymer/core-ajax/core-ajax.html">
<polymer-element name="nz-i18n" hidden>
<script type="text/javascript">
Polymer('nz-i18n', {
/**
* our messages container
*/
messages: {},
/**
* what loading is in progress
*/
loading: {},
created: function() {
var self = this;
// create new expression, to be used for translate method
PolymerExpressions.prototype.$$ = function(key, params) {
// IMPORTANT !!! the scope here is the element we call this function from
// set this element as parent of the translator
self.parent = this;
// get translation
return self.translateMessage(key, params);
};
// restore loaded messages from local storage
//this.restoreFromLocalStorage();
},
/**
* Load messages from local storage
*/
restoreFromLocalStorage: function() {
// check if we have translations already loaded
try {
if (translations = localStorage.getItem('nz-messages')) {
// convert JSON string representation to object
this.messages = JSON.parse(translations);
return true;
}
} catch (e) {
// nothing to do
// we will load translations on demand
}
},
/**
* Translate message by given key and additional parameters
*
* IMPORTANT !!!do not use translate as the method name
* there is such a property in the element
*
* #param key - key to be translated
* #param params - additional parameters
*/
translateMessage: function(key, params) {
// set default parameters if not defined
if (!params || params == 'undefined') {
var params = {};
}
if (!params.module) {
params.module = 'System';
}
var msg;
if (this.messages[params.module]) {
// module messages are already loaded
try {
// try to get translation
msg = this.messages[params.module].messages[key] || key;
// key with multiple forms has been provided
if (typeof(msg) == "object") {
if (params.n != '' && params.n != 'undefined') {
//get index if the translation in function of the rules
eval('idx = ' + this.messages[params.module].pluralRules.replace('n', params.n) + ';');
msg = msg[idx] || key;
} else {
msg = msg[0];
}
}
} catch (e) {
//no translation - return the key
msg = key;
}
} else {
// module messages are not loaded
// start loading
this.loadTranslations(params.module);
// this will be processed very customly
msg = '';
}
return msg;
},
/**
* Load messages for the module requested
*
* #param module - messages module
* #param force - if we have to force loading even if
* messages for the module are already loaded
*/
loadTranslations: function(module, force) {
// set defaults
if (!module) {
module = 'System';
}
// check if translations for this module are loaded
// or if loading is in progress
if (!this.loading[module] && (force || !this.messages[module])) {
// noooooooo - we will load them
this.loading[module] = true;
// create ajax request
ajax = document.createElement('core-ajax');
ajax.auto = true;
ajax.method = 'GET';
ajax.handleAs = 'json';
ajax.headers = {
"Accept": "application/json",
"Content-type": "application/json"
};
ajax.url = window.basePath + 'api/translations';
ajax.params = {"module": module};
// register event listeners for the response and post response processing
ajax.addEventListener('core-response', this.handleResponse);
ajax.parent = this;
// do not uncomment this - http://xhr.spec.whatwg.org/
//ajax.xhrArgs = {sync: true};
}
},
/**
* Process messages loading request
*/
handleResponse: function() {
// IMPORTANT !!!! core-ajax scope is here
if (this.response) {
for (module in this.response) {
// add current response to the translations
this.parent.messages[module] = this.response[module];
// remove loading flag for this module messages
delete this.parent.loading[module];
}
// set translations in local storage
localStorage.setItem('nz-messages', JSON.stringify(this.parent.messages));
}
}
});
</script>
</polymer-element>
I have also another element that will be used as a frameset and will host all my other application elements:
<link href="../../polymer/core-header-panel/core-header-panel.html" rel="import">
<link href="../../polymer/core-toolbar/core-toolbar.html" rel="import">
<polymer-element name="nz-frameset">
<template>
<link href="nz-frameset.css" rel="stylesheet">
<core-header-panel flex>
<!-- HEADER -->
<core-toolbar justify="between">
<img id="logo" src="../../images/logo.png" />
<div id="title">{{ $$('header_title') }}</div>
</core-toolbar>
<!-- CONTENT -->
<div class="content">{{ $$('num', {n: 4}) }}</div>
</core-header-panel>
<!-- FOOTER -->
<core-toolbar bottomJustify="around">
<footer class="bottom">
{{ $('footer') }}
</footer>
</core-toolbar>
</template>
<script type="text/javascript">
Polymer('nz-frameset', {
ready: function() {
},
});
</script
</polymer-element>
And here is my body(all imports needed are in the HEAD):
<body fullbleed vertical layout unresolved>
<!-- INITIALIZE TRANSLATOR -->
<nz-i18n></nz-i18n>
<!-- LOAD FRAMESET -->
<nz-frameset flex vertical layout></nz-frameset>
</body>
The problem is that when I open my APP for the first time and no translations are loaded yet, after I update my messages container the expressions does not re-bind and i can not see any text. On refresh(messages are in the local storage already), everything works like a charm.
Any help? Thanks!
One issue that I saw right from the get-go is that the expressions will only be evaluated once since there is no observable value in it, e.g. the observer doesn't see a variable reference and can detect changes.
This might be a hack, but I would pass a "changeable variable" and filter it in the expression, e.g.
<div id="title">{{ n_translate | $$('header_title') }}</div>
Now you need to fire an event whenever you load a new translation, in your handle handleResponse just add:
this.fire("translationChanged");
In every module that uses translations, you need to add a event observer:
this.addEventListener("translationChanged", function() {
this.n_translate = new Date(); // some changed value
});
There is no easy way to re-trigger polymer expressions, actually I don't know of any other than little hacks (for template lists, etc) that in the end cause more problems then help.
Related
I have 2 vue projects. In one I am creating a simple component:
<template>
<button #click="importIframe">Click me</button>
</template>
<script>
export default {
name: 'App',
methods: {
importIframe() {
console.log(parent.document)
},
}
</script>
In the other project of vue, import with an iframe the project 01 with a script:
<script src="http://localhost:8081/myiframe.js"></script>
Project 01 is running in localhost:8081 and 02 in 8080. I have a my iframe script which is the one that creates the iframe and imports it into the body.
This is myiframe.js script:
ready(function () {
initIframe()
})
/**
* Auto execute javascript function
* #param callbackFunction
*/
function ready(callbackFunction) {
if (document.readyState != 'loading') callbackFunction()
else document.addEventListener('DOMContentLoaded', callbackFunction)
}
/**
* Initialize iframe
* #return {Promise<void>}
*/
function initIframe() {
// Set url
const baseUrl = 'http://localhost:8081'
// Create div tag
const divContainer = document.createElement('div')
divContainer.setAttribute('id', 'container-iframe')
// Create iframe tag
const iframe = document.createElement('iframe')
iframe.setAttribute('src', baseUrl)
divContainer.appendChild(iframe)
document.body.appendChild(divContainer)
}
What I want to achieve is that by clicking on the button. I should create a new div inside # container-iframe. If I select the iframe container it returns null. And if I select the parent of the iframe with parent.document. It gives me a security error, how can I add an element to the parent of the iframe?
This is a very common issue when you work with cross-domains.
Good news is that we have postMessage to send events between the iframe and the parent.
In the child (iframe button):
<script>
export default {
name: 'App',
methods: {
importIframe() {
parent.postMessage("clicked", "*");
},
}
</script>
Then listen in paranet:
function listenToChild() {
var eventMethod = window.addEventListener
? "addEventListener"
: "attachEvent";
var eventer = window[eventMethod];
var messageEvent = eventMethod === "attachEvent"
? "onmessage"
: "message";
eventer(messageEvent, function (e) {
// if (e.origin !== 'http://the-trusted-iframe-origin.com') return;
if (e.data === "clicked" || e.message === "clicked")
alert('Message from iframe just came!');
// create a new div inside # container-iframe.
console.log(e);
});
}
Then add it to page load (ready function in your case)
More about the postMessage
https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
https://gist.github.com/cirocosta/9f730967347faf9efb0b
I have alfresco share/repo version 5.2.3
I'm trying to modifiy the function found in _AlfDndDocumentUploadMixin.js with the fix that has been posted on https://github.com/Alfresco/Aikau/blob/1.0.101_hotfixes/aikau/src/main/resources/alfresco/documentlibrary/_AlfDndDocumentUploadMixin.js
I tried extending the attachment-doclib.xml and get the jsonmodul, but it gives me a null pointer.
My code is the following
From attachment-doclib.get.js
var documentServices = model.jsonModel;
for (var i=0; i<documentServices.length; i++)
{
if (documentServices[i] === "alfresco/documentlibrary")
{
documentServices[i] = "js/aikau/1.0.101.10/alfresco/documentlibrary/my-documentlibrary/_AlfDndDocumentUploadMixin-extension";
}
else if (documentServices[i].name === "alfresco/documentlibrary")
{
documentServices[i].name = "js/aikau/1.0.101.10/alfresco/documentlibrary/my-documentlibrary/_AlfDndDocumentUploadMixin-extension";
}
}
from doclib-customizations.xml
<config evaluator="string-compare" condition="WebFramework" replace="false">
<web-framework>
<dojo-pages>
<packages>
<package name="documentlibrary" location="js/aikau/1.0.101.10/alfresco/documentlibrary" />
</packages>
</dojo-pages>
</web-framework>
</config>
From _alfDndDocumentUploadMixin-extended.js
onDndUploadDrop: function alfresco_documentlibrary__AlfDndDocumentUploadMixin__onDndUploadDrop(evt) {
try
{
// Only perform a file upload if the user has *actually* dropped some files!
this.alfLog("log", "Upload drop detected", evt);
if (evt.dataTransfer.files !== undefined && evt.dataTransfer.files !== null && evt.dataTransfer.files.length > 0)
{
this.removeDndHighlight();
var destination = this._currentNode ? this._currentNode.nodeRef : null;
var config = this.getUploadConfig();
var defaultConfig = {
destination: destination,
siteId: null,
containerId: null,
uploadDirectory: null,
updateNodeRef: null,
description: "",
overwrite: false,
thumbnails: "doclib",
username: null
};
var updatedConfig = lang.mixin(defaultConfig, config);
var walkFileSystem = lang.hitch(this, function alfresco_documentlibrary__AlfDndDocumentUploadMixin__onDndUploadDrop__walkFileSystem(directory, callback, error) {
callback.limit = this.dndMaxFileLimit;
callback.pending = callback.pending || 0;
callback.files = callback.files || [];
// get a dir reader and cleanup file path
var reader = directory.createReader(),
relativePath = directory.fullPath.replace(/^\//, "");
var repeatReader = function alfresco_documentlibrary__AlfDndDocumentUploadMixin__onDndUploadDrop__walkFileSystem__repeatReader() {
// about to start an async callback function
callback.pending++;
reader.readEntries(function alfresco_documentlibrary__AlfDndDocumentUploadMixin__onDndUploadDrop__walkFileSystem__repeatReader__readEntries(entries) {
// processing an async callback function
callback.pending--;
array.forEach(entries, function(entry) {
if (entry.isFile)
{
// about to start an async callback function
callback.pending++;
entry.file(function(File) {
// add the relativePath property to each file - this can be used to rebuild the contents of
// a nested tree folder structure if an appropriate API is available to do so
File.relativePath = relativePath;
callback.files.push(File);
if (callback.limit && callback.files.length > callback.limit)
{
throw new Error("Maximum dnd file limit reached: " + callback.limit);
}
// processing an async callback function
if (--callback.pending === 0)
{
// fall out here if last item processed is a file entry
callback(callback.files);
}
}, error);
}
else
{
walkFileSystem(entry, callback, error);
}
});
// the reader API is a little esoteric,from the MDN docs:
// "Continue calling readEntries() until an empty array is returned.
// You have to do this because the API might not return all entries in a single call."
if (entries.length !== 0)
{
repeatReader();
}
// fall out here if last item processed is a dir entry e.g. empty dir
if (callback.pending === 0)
{
callback(callback.files);
}
}, error);
};
repeatReader();
});
var addSelectedFiles = lang.hitch(this, function alfresco_documentlibrary__AlfDndDocumentUploadMixin__onDndUploadDrop__addSelectedFiles(files) {
if (this.dndMaxFileLimit && files.length > this.dndMaxFileLimit)
{
throw new Error("Maximum dnd file limit reached: " + this.dndMaxFileLimit);
}
// Check to see whether or not the generated upload configuration indicates
// that an existing node will be created or not. If node is being updated then
// we need to generate an intermediary step to capture version and comments...
if (updatedConfig.overwrite === false)
{
// Set up a response topic for receiving notifications that the upload has completed...
var responseTopic = this.generateUuid();
this._uploadSubHandle = this.alfSubscribe(responseTopic, lang.hitch(this, this.onFileUploadComplete), true);
this.alfPublish(topics.UPLOAD_REQUEST, {
alfResponseTopic: responseTopic,
files: files,
targetData: updatedConfig
}, true);
}
else
{
// TODO: Check that only one file has been dropped and issue error...
this.publishUpdateRequest(updatedConfig, files);
}
});
var items = evt.dataTransfer.items || [], firstEntry;
// webkitGetAsEntry is a marker for determining FileSystem API support.
// SHA-2164 - Firefox claims support, but different impl. rather than code around differences, fallback.
if (items[0] && items[0].webkitGetAsEntry && !has("ff") && (firstEntry = items[0].webkitGetAsEntry()))
{
walkFileSystem(firstEntry.filesystem.root, function(files) {
addSelectedFiles(files);
}, function() {
// fallback to standard way if error happens
addSelectedFiles(evt.dataTransfer.files);
}
);
}
else
{
// fallback to standard way if no support for filesystem API
addSelectedFiles(evt.dataTransfer.files);
}
}
else
{
this.alfLog("error", "A drop event was detected, but no files were present for upload: ", evt.dataTransfer);
}
}
catch(exception)
{
this.alfLog("error", "The following error occurred when files were dropped onto the Document List: ", exception);
}
// Remove the drag highlight...
this.removeDndHighlight();
// Destroy the overlay node (required for views that will re-render all the contents)...
domConstruct.destroy(this.dragAndDropOverlayNode);
this.dragAndDropOverlayNode = null;
evt.stopPropagation();
evt.preventDefault();
},
/**
* This function publishes an update version request. It will request that a new dialog
* be displayed containing the form controls defined in
* [widgetsForUpdate]{#link module:alfresco/documentlibrary/_AlfDndDocumentUploadMixin#widgetsForUpdate}.
*
* #instance
* #param {object} uploadConfig
*
* #fires ALF_CREATE_FORM_DIALOG_REQUEST
*/
publishUpdateRequest: function alfresco_documentlibrary__AlfDndDocumentUploadMixin__publishUpdateRequest(uploadConfig, files) {
// TODO: Work out the next minor and major increment versions...
// TODO: Localization required...
// Set up a response topic for receiving notifications that the upload has completed...
var responseTopic = this.generateUuid();
this._uploadSubHandle = this.alfSubscribe(responseTopic, lang.hitch(this, this.onFileUploadComplete), true);
// To avoid the issue with processing payloads containing files with native
// code in them, it is necessary to temporarily store the files in the data model...
var filesRef = this.generateUuid();
this.alfSetData(filesRef, files);
this.alfPublish("ALF_CREATE_FORM_DIALOG_REQUEST", {
dialogTitle: "Update",
dialogConfirmationButtonTitle: "Continue Update",
dialogCancellationButtonTitle: "Cancel",
formSubmissionTopic: topics.UPLOAD_REQUEST,
formSubmissionPayloadMixin: {
alfResponseTopic: responseTopic,
filesRefs: filesRef,
targetData: uploadConfig
},
fixedWidth: true,
widgets: lang.clone(this.widgetsForUpdate)
}, true);
},
Expect: All files that are dragged and drop to be uploaded.
Actual: Only one file is uploaded
Following code, is a very simple Firebase - VueJS app, (codeSandBox demo)
app.vue
<template>
<div class="container">
<!-- Adding Quote -->
<add-quote/>
<!-- Display Quotes -->
<quote-list/>
</div>
</template>
<script>
import addQuote from "./components/AddQuote.vue";
import quoteList from "./components/QuoteList.vue";
export default {
components: {
addQuote,
quoteList
},
methods: {
get_allQuotes: function() {
// var vm = this;
var localArr = [];
quotesRef
.once("value", function(snapshot) {
snapshot.forEach(function(snap) {
localArr.push({
key: snap.key,
category: snap.val().category,
quoteTxt: snap.val().quoteTxt
});
});
})
.then(data => {
this.$store.commit("set_allQuotes", localArr);
});
}
},
mounted() {
this.get_allQuotes();
console.log("App: mounted fired");
}
};
</script>
store.js(vuex store)
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
quotesList: []
},
getters: {
get_quotesList(state) {
return state.quotesList;
}
},
mutations: {
set_allQuotes(state, value) {
state.quotesList = value;
}
}
});
AddQuote.vue
<template>
<div class="row quote-edit-wrapper">
<div class="col-xs-6">
<textarea v-model.lazy="newQuoteTxt"
rows="4"
cols="50"></textarea>
<button #click="addQuote">Add Quote</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
newQuoteTxt: '',
}
},
computed: {
allQuotes() {
return this.$store.getters.get_quotesList;
},
newQuoteIdx() {
var localArr = [...this.allQuotes]
if(localArr.length > 0) {
var highestKEY, currKEY
localArr.forEach((element, idx) => {
currKEY = parseInt(element.key)
if(idx == 0) {
highestKEY = currKEY
} else {
if(highestKEY < currKEY) {
highestKEY = currKEY
}
}
})
return highestKEY + 1
} else {
return 1
}
}
},
methods: {
// ADD new Quote in DB
addQuote: function() {
var vm = this
var localArr = [...this.allQuotes]
//1. First attach 'value' event listener,
// Snapshot will contain data from that ref
// when any child node is added/updated/delete
quotesRef.on('value', function (snapshot) {
snapshot.forEach(function(snap) {
var itemExists = localArr.some(function (item, idx) {
return item.key == snap.key
})
// If newly added item doesn't yet exists then add to local array
if (!(itemExists)) {
localArr.push({
key: snap.key,
category: snap.val().category,
quoteTxt: snap.val().quoteTxt })
vm.$store.commit('set_allQuotes', localArr)
}
})
})
//2. Second set/create a new quotes in Firebase,
// When this quote gets added in Firebase,
// value event (attached earlier) gets fired
// with
var newQuoteRef = quotesRef.child(this.newQuoteIdx)
newQuoteRef.set({
category: 'motivation',
quoteTxt: this.newQuoteTxt
})
}
}
}
</script>
quoteList.vue
<template>
<div class="row">
<div class="col-xs-12 quotes-list-wrapper">
<template v-for="(quote,idx) in allQuotes">
<!-- Quote block -->
<div class="quote-block-item">
<p class="quote-txt"> {{quote.quoteTxt}} </p>
</div>
</template>
</div>
</div>
</template>
<script>
export default {
computed: {
allQuotes() {
return this.$store.getters.get_quotesList;
}
}
}
</script>
Note: The main code of concern is of addQuote.vue
User enter newQuoteTxt that gets added to Firebase (addQuote()) as a quote item under quotesRef. As soon as quote is added (on firebase), Firebase client side SDK's value event fires, and adds the new quote (via callback) to localArray (allQuotes). VueJS then updates the DOM with newly added Quote.
The addQuote() method works in the following manner:
First, attach a callback/listener to 'value' event on quotesRef
quotesRef.on('value', function (snapshot) {
....
})
Next, A firebase ref (child of quotesRef) is created with a ID this.newQuoteIdx
var newQuoteRef = quotesRef.child(this.newQuoteIdx)
Then set() is called (on this newly created Ref) adding newquote to firebase RealTime DB.
value event gets triggered (attached from step 1) and listener /callback is called.
The callback looks for this new quote's key in existing list of items by matching keys of localArr and snap.key, if not found, adds the newly quote to localArr. localArr commits to a vuex store.
`vm.$store.commit('set_allQuotes', localArr)`
VueX then updates all subscriber component of this array. VueJS then adds the new quote to the existing list of quotes (updates the DOM)
While debugging the addQuote method, the problem I notice, the execution/flow of script (via F8 in chrome debugger) first steps into the listener/callback attached to value event before the code newQuoteRef.set({ ... }) that adds new quote (on firebase), which in turn will cause 'value' event to trigger.
I am not sure why this occurs. Can anybuddy explain why the listener/callback is called before the quotes is created.
Are child nodes (of QuotesRef) are cached at clientside such that 'value' fires even before new quote is added.
Thanks
If I correctly understand your question (Your code is not extremely easy to follow! :-)) it is the normal behaviour. As explained in the documentation:
The value event will trigger once with the initial data stored at
this location, and then trigger again each time the data
changes.
Your sandbox demo does not actually shows how the app works, but normally you should not set-up the listener in the method that saves a new node to the database. These two things should be decoupled.
One common approach is to set the listener in the created hook of a component (see https://v2.vuejs.org/v2/guide/instance.html#Instance-Lifecycle-Hooks and https://v2.vuejs.org/v2/api/#created) and then in your addQuote method you just write to the database. As soon as you write, the listener will be fired.
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
Is it possible to register static variables for handlebars templates?
I'm using express-handlebars for my backend, where this is possible. For some specific renderings I use HandlebarsJS on the frontend where I can't find a way to accomplish this.
What I want to do is something like
handlebars.static = {foo: 'bar'}
Instead of adding it to every render
template({....., static: {foo:'bar'}});
It would be fine, if I could precompile it into each template.
Define a helper that can access your static values. Here's an example:
var templates = Handlebars.templates = Handlebars.templates || {};
/**
* Reads property from object. Supports reading nested properties with dot or bracket notation.
*/
var readProperty = function(object, property) {
var value = object;
property = property.replace(/\[('|")?|('|")?\]/g, '.');
if (property.substring(property.length - 1) === '.') {
property = property.slice(0, property.length - 1);
}
property.split('.').forEach(function(name) {
value = value[name];
});
return value;
};
/**
* The object that holds the values available to all the templates. This must be accessible by the helper definition.
*/
var values = {
key: 'a value',
lists: {
numbers: [0,1,2,3,5],
},
};
Handlebars.registerHelper('values', function(property) {
return readProperty(values, property);
});
templates['template-a'] = Handlebars.compile($('#template-a').html());
templates['template-b'] = Handlebars.compile($('#template-b').html());
$('body').append(templates['template-a']());
$('body').append(templates['template-b']());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars-v4.0.5.js"></script>
<script id="template-a" type="text/x-handlebars-template">
<p>here's the value: <strong>{{values 'key'}}<strong></p>
</script>
<script id="template-b" type="text/x-handlebars-template">
<p>again: <strong>{{values 'key'}}</strong><br>
and another one from an array inside an object: <strong>{{values 'lists.numbers[3]'}}</strong><br>
using bracket notation: <strong>{{values 'lists["numbers"]'}}</strong></p>
</script>