I want JavaScript code to be separated from views.
I got the requirement to implement localization for a simple image button generated by JavaScript:
<img src="..." onclick="..." title="Close" />
What's the best technique to localize the title of it?
PS: I found a solution by Ayende. This is the right direction.
Edit:
I got Localization helper class which provides the Controller.Resource('foo') extension method.
I am thinking about to extend it (helper) so it could return all JavaScript resources (from "ClientSideResources" subfolder in App_LocalResources) for the specified controller by its name. Then - call it in BaseController, add it to ViewData and render it in Layout.
Would that be a good idea?
EDIT
Consider writing the necessary localized resources to a JavaScript object (hash) and then using it for lookup for your dynamically created objects. I think this is better than going back to the server for translations. This is similar to adding it via viewdata, but may be a little more flexible. FWIW, I could consider the localization resources to be part of the View, not part of the controller.
In the View:
<script type="text/javascript"
src='<%= Url.Content( "~/Resources/Load?translate=Close,Open" %>'></script>
which would output something like:
var local = {};
local.Close = "Close";
local.Open = "Open";
Without arguments it would output the entire translation hash. Using arguments gives you the ability to customize it per view.
You would then use it in your JavaScript files like:
$(function(){
$('#button').click( function() {
$("<img src=... title='" + local.Close + "' />")
.appendTo("#someDiv")
.click( function() { ... } );
});
});
Actually, I'm not too fussed about keeping my JavaScript code out of my views as long as the JavaScript code is localized in a container. Typically I'll set my master page up with 4 content area: title, header, main, and scripts. Title, header, and main go where you would expect and the scripts area goes at the bottom of the body.
I put all my JavaScript includes, including any for viewusercontrols, into the scripts container. View-specific JavaScript code comes after the includes. I refactor shared code back to scripts as needed. I've thought about using a controller method to collate script includes, that is, include multiple scripts using a single request, but haven't gotten around to that, yet.
This has the advantage of keeping the JavaScript code separate for readability, but also allows me to easily inject model or view data into the JavaScript code as needed.
Actually ASP.NET Ajax has a built-in localization mechanism: Understanding ASP.NET AJAX Localization
If you insist on keeping it separate, you could do something like:
//keep all of your localised vars somewhere
var title = '{title_from_server}';
document.getElementById('someImage').title = title;
Remember, if you use JavaScript code to initialize any text of elements, your site will degrade horribly where JavaScript isn't available.
Related
I've hooked up a lazy loader in Angular. It pulls in full templates and extracts key information from that full template in order to populate a partial. This full page template has script tags which load in and then register with the existing app. All of this works fine. My problem is that I'd like to remove the only use of jQuery in this approach.
The root issue is that the JS inside of something.js doesn't execute when using $element.html(), but it does execute when using $.html(), despite the script tag being placed in the DOM in both approaches.
Working code, including lazy loader and post-bootstrap registration of lazy-loaded JS:
$http.get("/path/to/file.html").success(function(response) {
// response is a full HTML page including <doctype>
var partial = getOnlyWhatWeNeed(response);
// partial is now something like: '<script type="text/javascript" src="/path/to/something.js"></script><div ng-controller="somethingCtrl">{{something}}</div>'
// i'd like the following to not rely on full jQuery.
$("#stage").html(partial);
$("#stage").html($compile(partial)($scope)); // it is necessary to do it once before compile so that the <script> tags get dropped in and executed prior to compilation.
});
I've tried what seems like the logical translation:
$element.html($compile(partial)($scope));
and the DOM is created properly, but the JS inside of the loaded <script> tag doesn't actually execute. My research suggested this was an $sce issue, so I tried:
$element.html($compile($sce.trustAsHtml(partial)($scope));
but i get the same result. the DOM is fine, but the JS doesn't actually execute and so I get undefined controller issues.
I've tried playing with $sce.JS and $sce.RESOURCE_URL but the docs didnt elaborate much so I'm not sure I know whether or not what I'm trying is even right.
I've also tried $element[0].innerHTML but I get the same result as $element.html().
Preemptive disclaimer: I can trust the incoming HTML/JS. I know it's inadvisable. This isn't my baby and it is much more complicated than I explained so please try to stay on topic so other people in this position may not have as hard of a time as I am :)
The $http.get happens in a provider, and the $element.html happens in a directive. I consolidated them to remove noise from the problem.
Jquery will find any script tags and evaluate them (either a direct eval or appending them to the head for linked scripts) when calling html(), see this answer. I'm assuming angular's jquery lite doesn't do this. You would need to effectively replicate what jquery is doing and look for script tags in the html you are appending.
Something like this (although I haven't tested it):
$http.get("/path/to/file.html").success(function(response) {
// response is a full HTML page including <doctype>
var partial = getOnlyWhatWeNeed(response);
// partial is now something like: '<script type="text/javascript" src="/path/to/something.js"></script><div ng-controller="somethingCtrl">{{something}}</div>'
var d = document.createElement('div');
d.innerHTML = partial;
var scripts = d.getElementsByTagName('script');
for (var i = 0; i < scripts.length; i++) {
document.head.appendChild(scripts[0]);
}
$("#stage").html($compile(partial)($scope)); // it is necessary to do it once before compile so that the <script> tags get dropped in and executed prior to compilation.
});
This is far from an ideal solution as it gives you no guarantee of when things are loaded and doesn't really handle dependencies across scripts. If you can control the templates it would be simpler to remove the scripts from them and load them independently.
I'm trying to make a modern site that loads data using ajax, renders it in a template and uses jQuery and pushstate to display it, plus server-side rendering so that initial page loads are fast and spiders can crawl as intended. Of course, I read the article about linkedin using dust.js to achieve this.
Now, dust.js claims the following advantages:
Composable: Designers should be able to break presentation markup into manageable components and combine these components at runtime. It should not be necessary to statically link templates or manually assemble 'layouts' inside application code.
Format agnostic: While HTML generation and DOM manipulation are useful in specific instances, a general-purpose template system should not be tied to a particular output format.
So it sounds great, but how do I actually achieve what I want? I've made templates that render a complete page just fine on the server, but they use blocks and inline partials - which means the internal bits can't be rendered at all without the wrapper present (it just returns an error that says it can't find the wrapper template). I don't see how composing inside application code (as opposed to selling point #1 above) can be avoided on the client.
I don't understand what #2 above even means. I guess it means that you get the output as a string and can do whatever you want with it?
The documentation is about as clear as mud.
So what do I do? Is there a better option than dust.js these days? Do I write templates so that they must be composed in application code? If so, by what mechanism do I compose them in application code?
Ok, since there has been trouble understanding my question (which is itself understandable), I just threw together this (untested) example showing the problem:
Wrapper template:
<html>
<head><title>{+title/}</title>
{+styles/}
</head>
<body>
header header header
<div id="pagecontent">{+content/}</div>
footer footer footer
<script src="jquery"></script>
<script src="dust"></script>
<script src="see 'Client side script' below"></script>
</body>
</html>
'time' template:
{>wrap/}
{<title}Time in millis{/title}
{<styles}
<style>
body {
background-color: {bgcolor};
}
</style>
{/styles}
{<content}
The time: {time}<br />
Switch format
{/content}
Server side code:
app.get('/millis',function(req,res) {
res.render('time',{millis:new Date().getTime(),link:'/time',bgcolor:'lightgreen'});
}
app.get('/time',function(req,res) {
res.render('time',{millis:new Date().toString(),link:'/millis',bgcolor:'lightpink'});
}
So, the server will render the page fine, but what about the client? Read on.
Client side script:
//load the 'time' template into dust somewhere up here
$(function(){
$('a').click(function(e){
var newcontent;
switch($(this).attr('href')) {
case '/time':
//FAILS: can't find the wrapper. We need logic to get the title and styles from the template and fill it in in-place in the DOM
newcontent = dust.render('time',{millis:new Date().toString(),link:'/millis',bgcolor:'lightpink'});
case '/millis':
//FAILS: can't find the wrapper. We need logic to get the title and styles from the template and fill it in in-place in the DOM
newcontent = dust.render('time',{millis:new Date().getTime(),link:'/time',bgcolor:'lightgreen'});
default: return;
}
e.preventDefault();
$('#pagecontent').fadeOut(1000,function(){
//use pushstate and stuff here
$(this).html(newcontent);
$(this.fadeIn(1000);
});
});
});
I was wondering the same thing and came across this. You may have seen this too, but I'm leaving this here in case it helps others.
http://spalatnik.com/blog/?p=54
I haven't implemented this so my deductions below are based off the above article and some (hopefully) educated assumptions. I'd certainly like to continue the discussion if the following is incorrect, as I'm learning as well.
I suspect that you'd have two types of wrapper templates. One is the wrapper template that you provided above (for server side rendering). A second wrapper template would be quite different (as you see below). I'm copying verbatim from the blog above in the following example. I presume that all your compiled DustJS templates are in the file dust-full-0.3.0-min.js below.
<html>
<head>
<script src="dust-full-0.3.0.min.js"></script>
<script type="text/javascript">
//example showing client-side compiling and rendering
var compiled = dust.compile("Hello {name}!", "index");
dust.loadSource(compiled);
dust.render("index", {name: "David"}, function(err, out) {
if(err != null)
alert("Error loading page");
//assume we have jquery
$("#pageContainer").html(out);
});
</script>
</head>
<body>
<div id="pageContainer"></div>
</body>
</html>
I suspect that in your Express server, you'd check the User Agent and decide on which template to render out. If it's a bot user-agent, use server-side generation in your example. Otherwise, use the client side template above.
I need to define javascript variables containing very long html code.
Here is a short example:
var text = "Select one item:<br>";
text += "<ul class='thumbnails'><li class='span3'><a href='#' class='thumbnail'><img src='http://placehold.it/260x180' alt=''></a></li></ul>";
Since the html is going to be much much longer, I would rather work in pure html rather than append text to a javascript string.
I thought of creating a separate html file, but I guess that would require an Ajax call to fetch its content.
What is the best way to deal with this?
As N.Zakas said on "maintainable javascript" book, you should «keep html out of javascript» to promote high mantainability of the code through loose coupling of UI layers.
Beside the ajax solution you could also place the markup as a comment in the html file and read it via javascript (as a regular DOM node) or you could use some kind of microtemplating system (e.g. handlebars) and place your markup in a script block (the idea is to put markup where is expected to be found and not into javascript logic)
One possible solution is to use templates. There are a few JavaScript libraries that provide templating, underscore.js is one: http://underscorejs.org/#template, or more details on how to use it for templating http://www.headspring.com/blog/developer-deep-dive/an-underscore-templates-primer/
Plus underscore is great for a number of other things.
You could break up the text into actual HTML objects.
var thumbnailsUL = document.createElement('ul');
for (index in {your-thumbnails-list}) {
var thumbnail = document.createElement('li');
thumbnail.innerHTML = {whatever you need, more objects or html as text};
thumbnailsUL.appendChild(thumbnail);
}
Ideally though, there is no reason to build this IN JavaScript - can you not emit it from the server?
Instead of constructing HTML in Javascript as a string, I would rather suggest you to emit those html elements in the page itself and hide while loading. Then in Javascript, you could select that container and display it.
As a JS developer, I always keep my design layer separate from my business layer. Meaning, HTML is always alone, CSS and JavaScript files are external and included.
Now, in the case of jQuery Templates, a declared template must apparently live within a script block of the page. How in the world are you supposed to keep all of your business separated? I don't want messy HTML. I want clean HTML that never needs to be touched because it's been designed that way...
Are there solid, proven methods for doing this?
You can call $.templ(yourTemplateString, data) if you want. That returns the built-up elements which you can then stick in your document with "append" or whatever.
I agree with you that doing templates as <script> tags is not a super cool idea for everyone.
There is a sacrifice, but what looks more maintainable and clean:
Original:
for(var i=0; i<client.name.length; i++) {
clRec += "<li><a href='clients/"+client.id[i]+"'>" + client.name[i] + "</a></li>";
}
Templates
<script id="clientTemplate" type="text/html">
<li>${name}</li>
</script>
Code from http://blog.reybango.com/2010/07/09/not-using-jquery-javascript-templates-youre-really-missing-out/
You can use a different $.tmpl() syntax and $.ajax() to use jQuery Templates definitions stored in external files.
I suppose that if you're looking to templatize your application, try out the jQuery Template. But I've come to realize that, like many frameworks, ends up complicating the code even further.
I want to give a static javascript block of code to a html template designer, which can be:
either inline or external or both
used once or more in the html template
and each block can determine its position in the template relative to the other javascript code blocks.
An example could be image banners served using javascript. I give code to template designer who places it in two places, once for a horizontal banner in the header and once for a vertical banner. The same code runs in both blocks but knowing their positions can determine if to serve a horizontal or a vertical image banner.
Make sense?
Another example: Say you have the same 2 javascript tags in a web page calling an external script on a server. Can the server and/or scripts determine which javascript tag it belongs to?
NOTE: Can we say this is a challenge? I know that I can avoid this puzzle very easily but I come across this on a regular basis.
JavaScript code can locate all <script> elements on the page and it can probably examine the attributes and the content to check from which element it came from. But that's probably not what you want.
What you want is a piece of JavaScript which replaces tags on the page with ad banners. The usual solution is to add a special element, say a IMG, for this and give that IMG an id or a class or maybe even a custom attribute (like adtype="vertical") and then use JavaScript to locate these elements and replace the content by changing the src attribute.
For example, using jQuery, you can should your images like so:
<img src="empty.gif" width="..." height="..." class="ad" adtype="..." />
Then you can locate each image with
$('img.ad')
[EDIT] Well, the server obviously knows which script belongs into which script tag because it inserts the script. So this is a no-brainer.
If the script wants to find out where it is in the DOM, add something which it can use to identify itself, say:
<script>var id= '329573485745';
Then you can walk all script tags and check which one contains the value of the variable id.
If you call an external script, then you can do the same but you must add the ID to the script tag as you emit the HTML:
<script id="329573485745" src="..." />
Then the external script can examine the DOM and lookup the element with this id. You will want to use an UUID for this, btw.
This way, a piece of JS can locate the script tag which added itself to the page.
Best thing would probably be to make an insert once function, and then have him insert only the function call where needed.
Like this:
timescalled=0
function buildad(){
var toinsert="" //Code to generate the desired piece of HTML
document.write(toinsert)
timescalled+=1 //So you can tell how many times the function have been called
}
Now a script block calling the function can simply be inserted wherever a banner is needed
<script type="text/javascript">buildad()</script>
Thanks for the tips everyone but I'll be answering my own question.
I figured out several ways of accomplishing the task and I give you the one which works nicely and is easy to understand.
The following chunk of code relies on outputting dummy divs and jQuery.
<script>
// Unique identifier for all dummy divs
var rnd1="_0xDEFEC8ED_";
// Unique identifier for this dummy div
var rnd2=Math.floor(Math.random()*999999);
// The dummy div
var d="<div class='"+rnd1+" "+rnd2+"'></div>";
// Script which :
// Calculates index of THIS dummy div
// Total dummy divs
// Outputs to dummy div for debugging
var f1="<script>$(document).ready(function(){";
var f2="var i=$('."+rnd1+"').index($('."+rnd2+"'))+1;";
var f3="var t=$('."+rnd1+"').length;";
var f4="$('."+rnd2+"').html(i+' / '+t);";
var f5="});<\/script>";
document.write(d+f1+f2+f3+f4+f5);
</script>
Why not not just place the function call on the page instead of the entire code block? This way you can pass in a parameter to tell it what type of advertisement is needed?
BuildAd('Tower');
BuildAd('Banner');
Javascript itself has no clue of it's position in a page. You have to target a control on the page to get it's location.
I don't think it is possible for JavaScript code to know where it was loaded from. It certainly doesn't run at the point it is found, since execution isn't directly tied to the loading process (code usually runs after the whole DOM is loaded). In fact, in the case of externals, it doesn't even make sense, since only one copy of the code will be loaded no matter how many times it is encountered.
It shouldn't be the same code for each banner - there will be a parameter passed to whatever is serving the image banner which will specify the intended size.
Can you give a specific example of what you need this for?
To edit for your recent example: The simple answer is no. I could help you approach the problem from a different direction if you post details of your problem
The term "static block of code" leaves a lot of room for interpretation.
Inline scripts (e.g., ones that rely on document.write and so must be parsed and executed during the HTML parsing phase) cannot tell where they are in the DOM at runtime. You have to tell them (as in one of the first answers you got).
I think you'll probably find that you need to change your approach.
A common way to keep code and markup separate (which is useful when providing tools to HTML designers who aren't coders) is to have them use a script tag like so:
<script defer async type='text/javascript' src='pagestuff.js'></script>
...which then triggers itself when the page is loaded (using window.onload if necessary, but there are several techniques for being triggered earlier than that, which you want because window.onload doesn't trigger until the images have all loaded).
That script then looks for markers in the markup and manipulates the page accordingly. For instance (this example uses Prototype, but you can do the same with raw JavaScript, jQuery, Closure, etc.):
document.observe("dom:loaded", initPage);
function initPage() {
var verticals = $$('div.vertical');
/* ...do something with the array of "vertical" divs in `verticals`,
such as: */
var index;
for (index = 0; index < verticals.length; ++index) {
vertical.update("I'm vertical #" + index);
}
}
The designers can then have blocks on the page that are filled in by code which they flag up in a way that's normal for them (classes or attributes, etc.). The code figures out what it should do based on the classes/attributes of the blocks it finds when it runs.