Handlebars static variables - handlebars.js

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>

Related

Vue.js cannot change data value within a method

Can anyone help me why I cannot be able to change the data value within a method?
<head>
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<script src="https://www.gstatic.com/firebasejs/4.12.1/firebase.js"></script>
<script src="https://www.gstatic.com/firebasejs/4.11.0/firebase-firestore.js"></script>
</head>
<body>
<script>
var app = new Vue({
data() {
return {
name: ""
}
},
methods: {
firebase: function() {
var docRef = db.doc("test/testdoc");
docRef.get().then(function(doc) {
this.name = doc.data().name; //this one not working
console.log(doc.data().name); //just to test wether I can get data from Firestore
})
}
})
</script>
</body>
"console.log" is working and I can get the value from Firestore, but why the value of "name" doesn't change?
this will not be referring to the Vue instance anymore in function(){}, because this kind of function declaration bind its own this.
So you can save this into another variable 1st, and use it in function(){}.
methods: {
firebase: function() {
var docRef = db.doc("test/testdoc");
var _this = this; // save it in a variable for usage inside function
docRef.get().then(function(doc) {
_this.name = doc.data().name; //this one not working
console.log(doc.data().name); //just to test wether I can get data from Firestore
})
}
}
Alternatively, you could use arrow function like ()=>{}. Arrow function doesn't bind its own this. But it's not supported in some browsers yet.

Dynamically toggle resource column visibility

I have a FullCalendar scheduler on a webapp which has 2 way databinding for resources and events, all working great. I want to be able to present the user with a dropdown that enables them to toggle the visibility of a column, ideally completely client side.
I have tried a combination of addResource / removeResource however my issue here is that a rerender of the calendar (e.g. when a new event is added) then displays the previously removed resource. I can work around this however would prefer a really simple approach using JS / CSS. I currently cannot find a way to set a resource to not be visible, or to have zero width - is this possible?
There is an easy way to do this:
Store resources in an array variable resourceData.
Create another array called visibleResourceIds to store the ids of any resources you want to show.
In the resources callback function, filter resourceData to only contain the resources where the resource id exists in visibleResourceIds. Return the filtered array and fullcalendar will only add the desired resources for you.
To remove a resource from view, simply remove the resource id from visibleResourceIds and refetchResources. To add the resource back in, add the id to visibleResourceIds and refetchResources. DONE.
JSFiddle
var resourceData = [
{id: "1", title: "R1"},
{id: "2", title: "R2"},
{id: "3", title: "R3"}
];
var visibleResourceIds = ["1", "2", "3"];
// Your button/dropdown will trigger this function. Feed it resourceId.
function toggleResource(resourceId) {
var index = visibleResourceIds.indexOf(resourceId);
if (index !== -1) {
visibleResourceIds.splice(index, 1);
} else {
visibleResourceIds.push(resourceId);
}
$('#calendar').fullCalendar('refetchResources');
}
$('#calendar').fullCalendar({
defaultView: 'agendaDay',
resources: function(callback) {
// Filter resources by whether their id is in visibleResourceIds.
var filteredResources = [];
filteredResources = resourceData.filter(function(x) {
return visibleResourceIds.indexOf(x.id) !== -1;
});
callback(filteredResources);
}
});
I had the same challenge. Instead of a dropdown, I use checkboxes, but the workings will be the same.
My resources are stored in a variable, when I uncheck a box, the resource is removed and the resource's object is added to another array with the resourceId as key, and the index added to the object to restore the object in the same column as it originally was. When re-checking the box, the object is added to the resources array and the resources refetched.
/* retrieve the resources from the server */
var planningResources;
var removedResource = [];
$.ajax({
url: '/planning/resources/',
method: 'get',
success: function (response) {
planningResources = response;
showCalendar();
}
, error: function () {
if (typeof console == "object") {
console.log(xhr.status + "," + xhr.responseText + "," + textStatus + "," + error);
}
}
});
/* create the calendar */
showCalendar = function () {
$('#calendar').fullCalendar({
...
});
}
/* checkbox on click */
$('.resource').click(function() {
var resourceId = $(this).val();
var hideResource = !$(this)[0].checked;
$('.status:checkbox:checked').each(function () {
});
if(hideResource) {
$.each(planningResources, function(index, value){
if( value && value.id == resourceId ) {
value.ndx = index;
removedResource[resourceId] = value;
planningResources.splice(index,1);
return false;
}
});
$('#planningoverview').fullCalendar(
'removeResource',
resourceId
);
}
else {
planningResources.splice(removedResource[resourceId].ndx, 0, removedResource[resourceId]);
$('#planningoverview').fullCalendar('refetchResources');
}
});
showCalendar();
It probably doesn't get first price in a beauty contest, but it works for me ...
Cheers
You can use the resourceColumns option for this. In the column objects you can set the width property to a number of pixels or a percentage. If you pass a function here you can easily handle the width property someplace else. Your hide/show function can then set the width to 0 to hide the column. After that you can trigger reinitView to update the view: $('#calendar').fullCalendar("reinitView");

Meteor - Using reactive variables in anonymous functions

How can I use reactive template variables (from Template.data) in an anonymous function within the template rendered function? (I want to keep it reactive).
Template.templateName.rendered = function() {
function testFunction(){
//Log the field 'title' associated with the current template
console.log(this.data.title);
}
});
Not sure exactly what you are trying to do (like printing this.data.title whenever it changes?), but you should:
use a Reactive variable (add reactive-var package, then create a var myVar = new ReactiveVar()
If necessary, wrap your function with Tracker.autorun (or this.autorun in a template creation / rendered event).
So you could have like:
Parent template HTML:
<template name="parentTemplateName">
{{> templateName title=myReactiveVar}}
</template>
Parent template JS:
Template.parentTemplateName.helpers({
myReactiveVar: function () {
return new ReactiveVar("My Title!");
}
});
Template JS:
Template.templateName.onRendered(function() {
// Re-run whenever a ReactiveVar in the function block changes.
this.autorun(function () {
//Print the field 'title' associated with the current template
console.log(getValue(this.data.title));
});
});
function getValue(variable) {
return (variable instanceof ReactiveVar) ? variable.get() : variable;
}
What worked for me was simple using autorun() AND using Template.currentData() to grab the values from within autorun():
let title;
Template.templateName.rendered = function() {
this.autorun(function(){
title = Template.currentData().title;
});
function testFunction(){
console.log(title);
}
});
Template.templateName.onRendered(function(){
console.log(this.data.title);
});

Polymer: Evaluate expression after ajax response

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.

How to get prototype of a registered webcomponent without instantiating it

Consider the following code in a javascript library;
document.registerElement('my-component', { prototype: { foo: true }});
It seems registerElement returns a function which can be used as a constructor.
How can I get a reference to this function later ?
var tempDom = document.createElement('my-component')
console.log(tempDom.__proto__)
Seems working but it requires creating an instance first.
I think you just need to save the return from registerElement() in a variable, and then use that variable later. If you do not save the return then I believe it is lost.
// Save the return in a variable
var mycomp = document.registerElement('my-component');
// Use the var to create the element
document.body.appendChild(new mycomp());
// Then you can do things with the new tag
var mytag = document.getElementsByTagName("my-component")[0];
mytag.textContent = "I am a my-component element.";
prototype method will give you an expected result.
var mc = document.registerElement(
'my-component', { prototype: { foo: true }}
);
console.log(mc.prototype);
//⇒ my-component {foo: true}
Hope it helps.

Resources