I'm trying to learn Handlebars.js and thought of a way to get use of it in a site im making. It's a one page site which will have two containers with three divs in each which will contain embedded Soundcloud players through their API.
When including the divs which contain the API requests in the script tag handled by Handlebars, the site behave very unreliable and shows only some of the six players. It's not really consistent but can show different players all the time. The problem seems to be in the Soundcloud javascript SDK but I don't feel to familiar to dig around in there too much.
Therefore I thought of some way to exclude the player divs (see code) so that they're loading instantly and not being Handled as javascript, but still show up beneath the artist - title in the placeholder div (which is set to contain the result of the Handlebar script).
The problem is that I can't come up with a nice way of doing this, are there any easy function (with Handlebars helpers maybe) that will help me do what I want?
<div id="placeholder"></div>
<script id="player-template" type="text/x-handlebars-template">
<div id="container1">
Artist1 - {{title1}}
<div id="player1"></div>
Artist2 - {{title2}}
<div id="player2"></div>
Artist3 - {{title3}}
<div id="player3"></div>
</div>
<div id="container2">
Artist4 - {{title4}}
<div id="player4"></div>
Artist5 - {{title5}}
<div id="player5"></div>
Artist6 - {{title6}}
<div id="player6"></div>
</div>
</script>
<script src="js/handlebars_title_script.js"></script>
One solution is of course to make one Handlebar template for each Artist - Title div and set the placeholder of each template to a div containing only Artist1 - {{title1}} but that really destroys the point of using Handlebars to minimize my HTML coding.
Anyone got any tip for me how to solve this?
Edit 1:
I found another solution by changing in my javascript (which I didn't post at first so obviously you couldn't help me with that).
$(document).ready(function() {
var hey = "heya";
SC.get("/users/artist/tracks", {limit: 1}, function(tracks){
var title_data1 = tracks[0].title;
hey = tracks[0].title;
alert(title_data1);
alert(hey)
});
//Data that will replace the handlebars expressions in our template
var playerData = {
title1 : hey,
};
document.getElementById( 'player-placeholder' ).innerHTML = playerTemplate( playerData );
});
Sorry for bad intendetion. The only problem with this code is that title1 (in the variable playerData which is the Handlebars context) gets the first value of the variable hey ("heya"). When it's alerted it pops up the real title, how can I make title1 use this value instead without nesting the variable in more javascript (since that's what causes the before mentioned error with players showing up weird)?
Note: throughout the comments this answer has changed drastically. Please view the earlier revisions if you would like to see the evolution of this answer.
After getting a hold of your JsFiddle example I was able to get it working in a way I think you wanted.
Working Demo
HTML:
<body>
<div id="wrapper">
<div id="player-placeholder"><!-- rendered template goes here --></div>
<!-- handlebars template: -->
<script id="player-template" type="text/x-handlebars-template">
{{#each tracks}}
<div class="track">
<header class="header">
<span class="artist">{{user.username}}</span> - <span class="title">{{title}}</span>
</header>
<section class="player" data-uri="{{permalink_url}}">
</section>
</div>
{{/each}}
</script>
</div>
</body>
JavaScript:
$(document).ready(function() {
/*
get your template string See:
http://api.jquery.com/id-selector/
http://api.jquery.com/html/
*/
var source = $('#player-template').html();
// compile the template into a handlebars function
var template = Handlebars.compile(source);
// initialize sound cloud api
SC.initialize({
client_id: '90fb9e15c1e26f39b63f57015ab8da0d'
});
/*
This function will be called once the HTTP transaction
started by SC.get(...) completes. Note, there's nothing
wrong with doing this as an anonymous function, I'm
simply assigning it to a variable to show that this
is a distinct function that's called later
*/
var callback = function(tracksResponse){
/*
once a response has been received, we'll use the response
to generate a new context to pass to the template function.
Note, you can use the template function in here because its
within a closure. See:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures
*/
var context = { tracks: tracksResponse };
var html = template(context);
/*
assign the rendered html to your placeholder on the page
see: http://api.jquery.com/html/
*/
$('#player-placeholder').html(html);
/*
Now that the html is rendered and on the page, its time to
setup the sound cloud players. Note the css classes I assigned
to the track/player. This line selects all of the player's and
runs the function over each. See:
http://api.jquery.com/class-selector/
http://api.jquery.com/each/
*/
$('.track .player').each(function(index, e){
var $this = $(this); // jQuery reference to the current object in 'each loop'
/*
I assigned the permalink_url of each track to an attribute called 'data-uri'
This line gets the value of that attribute. See:
http://api.jquery.com/data/#data2
*/
var permalink = $this.data('uri');
var urlParameters = '/&maxheight=100&maxwidth=300&format=json&sharing=false';
/*
finally we call the sound cloud oEmbed function feeding it the url
stored in the element, as well as the actual element.
(see the second argument of the each function: http://api.jquery.com/each/)
*/
SC.oEmbed(permalink + urlParameters, e);
});
};
// get tracks for your artist
// Note the "limit" in the object controls the number of items returned
// by sound cloud
SC.get("/users/theshins/tracks", {limit: 5}, callback);
});
What went wrong?
JavaScript is a single-threaded, asynchronous, event-driven language. That giant mouth-full means JavaScript doesn't really have a notion of threading (I'm intentionally ignoring WebWorkers). To work around that limitation, almost all IO in JavaScript is non-blocking (asynchronous).
Whenever an asynchronous IO transaction begins it immediately returns to the caller and code execution continues. Almost all IO transactions take a 'callback' or have an event that will be called when the IO transaction completes. That means the basic pattern for all IO operations follows something like this:
Create a callback function
Call IO operation, passing it the arguments it requires to complete, plus the callback
Execution returns immediately
Sometime in the future, the callback function is called
In your original example $(document).ready(function() { ... }) queues an anonymous function to fire when the document.onReady event is raised. Your original example, however, had two callbacks assigned. This isn't a problem, an in fact .ready(...) is designed to accept and queue many callbacks. However, where you went wrong is you had two separate blocks of code that called SC.get(...).
Technically if done right this wouldn't be a problem, but your first on ready callback's purpose was tasked to setup the page's HTML while your second callback tried to initialize the player controls based on html on the page. Remember these events and IO operations are asynchronous, they'll fire in whatever order. Essentially this became a timing issue, you were attempting to initialize controls on the page, and generate HTML to display on the page at the same time.
How it was fixed
To fix the timing issue you need to synchronize when you get your info, when you build your template HTML, and when you initialize your controls. There's a lot of ways to do this and many frameworks support the idea of promises to help gain control over the order that asynchronous events are fired, and their callbacks called.
I took the simple route and combined all of your SC.get calls in to one, then within it's callback I render the handlebars template and initialize the SoundCloud players.
Related
I can't get UserFlow to work for our AngularJS app.
The product runs on old AngularJS (1.8) and we love the concept of UserFlow , but the typical injection and init model runs in the core JS scope which AngularJS does not have access to... so, even after following the onboarding instructions, every user that registers is appearing to UserFlow as the same {{userId}}
We believe this is happening (UserFlow is not able to receive the user ID in userflow.identify as described here) because the user ID is not known outside of the AngularJS digest. i.e. - the method was called in a place where angularJS is not taking effect, so the handlebars never get rewritten.
Got it fixed. An overview of how we fixed it is below:
Simply split UserFlow's initialization into two distinct steps:
userflow.init() - this can be directly in your index.html or otherwise injected into the <body>
userflow.identify() - this has to be done within your AngularJS controller
.
DETAILED STEPS---------------
1. init() normally, but use a build variable and don't identify yet
In index.html, at the bottom of the <body> tag, add the following script:
<!-- UserFlow -->
<script ng-if="customization.features.userflow">
!function(){var e="undefined"==typeof window?{}:window,t=e.userflow;if(!t){var r="https://js.userflow.com/";t=e.userflow={_stubbed:!0};var n=e.USERFLOWJS_QUEUE=e.USERFLOWJS_QUEUE||[],o=function(e){t[e]=function(){var t=Array.prototype.slice.call(arguments);i(),n.push([e,null,t])}},s=function(e){t[e]=function(){var t,r=Array.prototype.slice.call(arguments);i();var o=new Promise((function(e,r){t={resolve:e,reject:r}}));return n.push([e,t,r]),o}},a=function(e,r){t[e]=function(){return r}},u=!1,i=function(){if(!u){u=!0;var t=document.createElement("script");t.async=!0;var n=e.USERFLOWJS_ENV_VARS||{};"es2020"===(n.USERFLOWJS_BROWSER_TARGET||function(e){for(var t=[[/Edg\//,/Edg\/(\d+)/,80],[/OPR\//,/OPR\/(\d+)/,67],[/Chrome\//,/Chrome\/(\d+)/,80],[/Safari\//,/Version\/(\d+)/,14],[/Firefox\//,/Firefox\/(\d+)/,74]],r=0;r<t.length;r++){var n=t[r],o=n[0],s=n[1],a=n[2];if(e.match(o)){var u=e.match(new RegExp(s));if(u&&parseInt(u[1],10)>=a)return"es2020";break}}return"legacy"}(navigator.userAgent))?(t.type="module",t.src=n.USERFLOWJS_ES2020_URL||r+"es2020/userflow.js"):t.src=n.USERFLOWJS_LEGACY_URL||r+"legacy/userflow.js",t.onerror=function(){u=!1,console.error("Could not load Userflow.js")},document.head.appendChild(t)}};o("_setTargetEnv"),o("closeResourceCenter"),o("init"),o("off"),o("on"),o("prepareAudio"),o("registerCustomInput"),o("remount"),o("reset"),o("setCustomInputSelector"),o("setCustomNavigate"),o("setCustomScrollIntoView"),o("setInferenceAttributeFilter"),o("setInferenceAttributeNames"),o("setInferenceClassNameFilter"),o("setResourceCenterLauncherHidden"),o("setScrollPadding"),o("setShadowDomEnabled"),o("setPageTrackingDisabled"),o("setUrlFilter"),o("openResourceCenter"),o("toggleResourceCenter"),s("endAll"),s("endAllFlows"),s("endChecklist"),s("group"),s("identify"),s("identifyAnonymous"),s("start"),s("startFlow"),s("startWalk"),s("track"),s("updateGroup"),s("updateUser"),a("getResourceCenterState",null),a("isIdentified",!1)}}();
userflow.init('##grunt_userflow')
</script>
Since we use Grunt as a build tool (which I don't recommend, but you can replicate the same pattern with different technologies), we put the environment-specific token, ##grunt_userflow, into our build script which replaces the individual token to match the respective environment.
You'll notice here we're not calling userflow.identify() yet...
2. Execute the UserFlow identify() directly within the controller
When the user first logs in, now you need to execute the userflow.identify() function and pass in the right IDs. Personally, I like putting AngularJS-agnostic functions like this outside of the controller and then inherit them in:
const startUserFlow = function(userId, login) {
userflow.identify(userId, {
email: login
});
};
And, now calling that function from within AJS:
$scope.processCredentials($scope.username, response.data.access_token).then(function (result) {
trackEvent('signIn', $rootScope.userProfile.id);
startUserFlow($rootScope.userProfile.id, $scope.username);
3. Finally, to reinitialize your Content, use ng-click=() on any HTML you'd like
That's right - since we're scoping it in and doing this the AngularJS way, use ng-click like any other function and bind it directly. Example below.
$scope.launchUserFlowChecklist = function () {
userflow.start('[insert content ID here]');
};
I hope this helps! Cheers.
I am importing a set of notes into my webpage, this is to read a JSON file locally in a loop and append the read data into the main div. No problem till now. But then I'm producing a ckeditor instance beside each note for the client to become able to easily add comments to his note of interest. The comments are initially generated as several indexed empty div's in another HTML file, loaded into the ckeditor instances. However, all these happen in a really large for loop (I have almost 6000 notes to be loaded in a segmented manner using if conditions), and so now I'm engaged with the classic closure-loop problem. Have read several previous questions and answers foo this and other websites and tested a number of them to get rid of the closure-loop problem, but no success so far.
The related segment of my java script has the structure:
var q;
$.when(
$.ajax( ... loads the json file that contains the notes and set q=$.parseJSON(data) on success)
).then(function() {
for(var i in q) {
if(i is in a specific range){
... several lines of code for properly importing the notes ...
... and generating a place for the comments to appear as:
... +'<div id="CKEditor'+i+'" contenteditable="true" placeholder="Put your comment here!"></div>'
... which is appended to the main div of the webpage
... Now the main problematic part begins:
$('#temporary').empty(); // a hidden div defined somewhere in the page
var func = (function() {
var ilocal=i, tmp;
return function() {
tmp=document.getElementById('temporary').innerHTML;
alert(tmp);
CKEDITOR.instances['CKEditor'+ilocal].setData(tmp);
}
})();
$.when(
$('#temporary').load("NewComments.htm #verse-"+i)
).then(func);
};
};
CKEDITOR.disableAutoInline = true;
CKEDITOR.inlineAll();
})
maybe the problem is not for the loop but for the nested $.when().then(), any suggestion to resolve the issue?
The problem is that there is only a single $('#temporary') div in your page, which will be re-used and overwritten by every iteration. In particular, in your callback
document.getElementById('temporary').innerHTML;
…
CKEDITOR.instances['CKEditor'+ilocal]
the ilocal (and tmp) variables are indeed local to the IIFE and that particular iteration, but document.getElementById is global. It will return the same element every time.
A quick fix is to create a new element for every request, and assign it to tmp during the iteration (like you assign i to ilocal) instead of when the func is called.
A much better practice however would be not to use $('#temporary').load("NewComments.htm #verse-"+i) multiple times, and instead load the NewComments.htm only once per Ajax and process the result as you need.
I need to do something seemingly quite simple.
In the same way that I can, from my scala.html file, create a link to another url, /entry, I need to do that from a javascript file.
i.e., from the scala.html:
<div class="footer">
<a href='#routes.Application.index()'>Home</a>
</div>
from my javascript event:
function() myEvent {
window.location="#routes.Application.entry()"; // DOESN'T WORK!
}
For routing from javascript, I've already had to setup my javascript routes for some ajax work I've already had to do.
My ajax work was calling a method 'findPersons()' so in my Application.java file, I had already:
public Result jsRoutes()
{
response().setContentType("text/javascript");
return ok(Routes.javascriptRouter( "appRoutes",
routes.javascript.Application.findPersons()));
}
Because I want to be able to redirect to my GET entry() method, I modified it to look like this:
public Result jsRoutes()
{
response().setContentType("text/javascript");
return ok(Routes.javascriptRouter( "appRoutes",
routes.javascript.Application.findPersons(),
routes.javascript.Application.entry()));
}
Additionally I have these in my routes file:
GET /entry controllers.Application.entry()
POST /findPersons controllers.Application.findPersons()
When I am invoking my findPersons method, it is really nice and simple.
It looks like this:
appRoutes.controllers.Application.findPersons().ajax({
data: {
personIdentifier : personIdentifier,
surname : surname,
givenNames : givenNames
},
success : processDBQuery
});
For my simple redirect, I would like to be able to maintain the same loose coupling between my html/javascript code and the urls, as I can the ajax call above.
My redirect needs to occur on an event. Therefore, the easiest and quickest solution would have been simple to write:
function() myEvent {
window.location="/entry";
}
However, then I would be hard-coding the URL (which I have managed to avoid for my ajax call above), no longer maintaining that loose coupling I would so much like to have.
However, I see no examples in the documentation, and from what I have in the generated javascript (for my routes) there is no chance.
Is there any way to achieve what I am after?
thanks for reading!
p.s., I should add; I guess I have also thought of the possibility of using the ajax call that is generated, I guess I can probably fetch the page I want... and there is probably a means of replacing the current document with the entire content of the fetched page. but that just sounds bad.... wrong...
or not?
I was rather hoping for a substitution, as is done in my html
i.e, my link as shown above is generated to look like this:
<div class="footer">
<a href='/'>Home</a>
</div>
In the same way, I hoped there was some means of substitution in the javascript, so that the event function above ends up in being massaged into looking like this:
function() myEvent {
window.location="/entry";
}
Jacques, from the above comments, helped me to realize a work-around.
From within my "assets located" javascript file, I can still refer to page/template located javascript.
Own-file/assets located javascript doesn't seem to be transformed how I expected.
However, Page/template located javascript is transformed exactly how I require.
I can refer to a template located javascript function from my assets located javascript.
This means, I have a little work-around of one extra little function inside the template which does the redirection for me.
i.e.,
myJavascript.js:
function personResultsListClickHandler(personId) {
var fpersonId = personId;
return function() {
window.alert("hello! " + fpersonId);
affectRedirect();
};
}
myTemplate.scala.html
#main("person lookup") {
<script type="text/javascript">
function affectRedirect(){
window.location="#routes.Application.entry()";
} // Need this here so it will be transformed.
// asset located javascript doesn't seem to get transformed like javascript here in template! :(...
</script>
Another possibility is the fact that the Javascript object retrieved by calling:
appRoutes.controllers.Application.entry()
contains a url member. This url member is exactly what I can use to assign to window.location. However, it looks a bit unofficial.. in terms of
1. the member not being documented
2. not sure if the url member will exist in the future
3. the generated javascript is constructing an object dealing with ajax... and i'm just grabbing the URL member from it... it just feels... like a hack.
But i've tested this, and it works. See code below:
function patientResultsListClickHandler(personId) {
var fpersonId = personId;
return function() {
window.location=appRoutes.controllers.Application.entry(personId).url;
// window.location="/entry/" + fpersonId; // the sort of hard-coding of URL that
}; // I wanted to avoid, but don't seem able to.
}
Does anyone else have a better solution?
I'm stuck with a problem since I upgraded to 0.8.0.
The Template rendered is not being fired anymore (except the first time).
I followed the recommendations as in:
https://github.com/avital/meteor-ui-new-rendered-callback/blob/master/new2/client/each.js
This didn't helped, and so I finally made this small piece of code (by modifying the new2 example).
The main difference is that the update is triggered by a Session variable change instead of a DB change.
This perfectly shows the problem, as rendered is fired only twice with this example:
client/each.js
Template.list.items = function () {
return (Session.get('items') || 'None')
};
var renderCount = 1;
var logRender = function () {
console.log("rendered #" + renderCount);
renderCount++;
};
Template.list.rendered = function () {
logRender();
};
Template.justName.rendered = function () {
logRender();
};
setInterval(function () {
Session.set('items', {name: Random.choice(["one", "two", "three"])});
}, 1000);
client/each.html
<body>
{{> list}}
</body>
<template name="list">
{{#with items}}
{{> justName}}
{{/with}}
</template>
<template name="justName">
{{name}}
</template>
How can I do to get the Template.justName.rendered callback properly fired when content update is triggered by a Session.set?
Thanks,
I do have an instant solution for you, but it probably requires a tiny bit of re-thinking your actual code. This is by the way the same problem as here:
Meteor 0.8.0 - Failed to operate DOM in rendered callback
But the question is posed in such a different context that it makes sense to answer it twice.
So why does it not trigger the rendered callback? Because it does not re-render.
Blaze treats the whole thing of "how to react on a changed dependencies" very differently, "better" one might say: it will identify the DOM node where your "one", "two" or "three" (in your case it's the template itself) was stored in and just replace the part that has changed, which is the text content "one", "two" or "three". The DOM node itself as well as the template stay completely intact. That also means, that everything you could have been doing with this DOM node won't have to be re-done in almost every practical scenario. I.e. if you animate it, change it's text color using jQuery, the color and animation will just stay on the screen, so you won't need the rendered callback to re-do that.
In your case, the problem is easily solved by just rearanging what you want to do on "rerender":
var whatever = function(){
// whatever you want to do on data-change, in your case calling "logRender" (which needs to be renamed with Blaze, anyway..)
logRender();
}
And then the only thing you have to do is firing it whenever your data change, either manually, like this:
setInterval(function () {
Session.set('items', {name: Random.choice(["one", "two", "three"])});
// calling the function when changing the data, knowing that it WON'T destroy the DOM node it affects
whatever();
}, 1000);
or reactively, like this:
Deps.autorun(function(){
Session.get("items"); // our dependency, just has to be there, but you can also use it
whatever(); // will be fired whenever dependency changes
});
The core idea is to eliminate the need to re-do something you did in the rendered callback, since the DOM and the identity of its objects (and all the beautiful jQuery effects) are still intact. So all that is left to re-do is something that only would depend on the particular reactive data-change, which is why there is the Deps.autorun().
In your particular example, your "logRender" function did not have any reactive dependencies, but if you add some and put it into the Deps.autorun(), it will be reliably re-run whenever the dependency changes.
As a conclusion, Meteor 0.7.x and below drove us to make the mistake of treating the "rendered" callback function as a general purpose autorun function, which is why we are running into trouble now and have to fix our apps.
As noted in the comments, this is a indeed a design change with Meteor.
Prior to Meteor 0.8, a template was a function that generated HTML. This function would be re-computed whenever any of its reactive dependencies changed, which resulted in a recreation of all the DOM nodes generated by the template (apart from any sub-templates or isolated nodes). Whenever this re-draw happened, the rendered callback was triggered.
This behavior creates quite a performance hit because it requires re-rendering of potentially a lot of HTML, including for identifiers and helpers depending on data that hadn't changed. Additionally, it made it difficult to use other libraries like jQuery to modify the DOM elements that were created, because Meteor basically had control of the entire process and the jQuery code would have to be carefully re-run each time.
Meteor 0.8 fixes this by only rendering the pieces of the DOM that have actually changed, down to the granularity of the identifiers in your template - it is much more fine-grained. As a result, the template's rendered callback is only triggered once when your template hits the page, and is never called again afterward. This solves a lot of performance issues and allows jQuery and other DOM manipulations to work seamlessly with Meteor, but also means that you won't get the automatic callback signalling when something has changed. You can, however, achieve this with helpers that use reactive variables for specific things that change.
For a more detailed listing of how Spacebars, the new Handlebars replacement, works, see https://github.com/meteor/meteor/blob/devel/packages/spacebars/README.md
See also the new documentation about the rendered callback: http://docs.meteor.com/#template_rendered
So, I was doing a lot of digging yesterday to try and figure out basically the exact same issues you are having. I am still digging, but I did come across this Devshop Talk about Integrating Other Clientside JS Libraries. In it Ted Blackman describes a package he made to trigger events when a Session variable changed. It sounds like what you need. This talk was given prior to 0.8.0 so I am not sure how the package would be effected, but it might be worth a shot.
Devshop Talk - https://www.youtube.com/watch?v=NdBPY98o6eM
Session Extras - https://atmospherejs.com/package/session-extras
Event Horizon - https://atmospherejs.com/package/event-horizon
I'm a beginner with JS.
I am working with some JavaScript on a site, and I just want to use only 1 file of JS for determine the actions off the pages. Something like this:
function registerEvents(){
NameOfHTMLDocument = //?? Get the name of the document that called registerEvents function.
switch(NameOfHTMLDocument)
{
case:"homepage":
hmp_btn = document.getElementById("hmp_btn");
hmp_btn.onclick=otherFunction;
break;
case:"otherPage":
elem = document.getElementById("elemID");
elem.onclick=fooFunction;
break;
//etc...
}
}
This function is called with a <body onload="registerEvents()"> that is "inherited" by all the pages.
The question is, How can I get the "NameOfHTMLDocument"?. Because I don't want that JS begin doing weird things when trying to get elements that don't exist.
I found that I can get the URL of the DOM and then play a little with it to get the string that i want, but i'm not sure if this is the better way of doing it.
It Would be nice if you have a better suggestion.
Firstly I would really suggest that you create separate script tags in html documents for functionality that is used only on that page and common functionality in separate file for several reasons:
No code pollution
Ease of change
Smaller download
Secondly, you can use switch on window.location.pathname DOM variable which is everything after domain
instead of homepage, etc..
i.e.
url = vader.samplesite.com/light/saber/
window.location.pathname = /light/saber/
(look at http://www.developertutorials.com/questions/question/q-242.php )
window.location.pathname
All you need to do is some parsing, but I'm sure you'll figure that out :) If not, leave a comment.
In your <body onload="registerEvents()"> pass the object this (the BODY in the DOM) through your event function such as : <body onload="registerEvents( THIS )">.
In your function itself, call the object you passed like object.ownerDocument.URL to get the URL including the HMTL document name or object.ownerDocument.title to get the page title.