Is there a way to have Expression Language (EL) expressions included JavaScript files be evaluated by JSF?
I was hoping that Seam might have a way around this, but no luck so far. All I want is to be able to use localized messages in my JavaScript functions which are shared across pages.
Five ways:
Declare it as global variable in the parent JSF page.
<script type="text/javascript" src="script.js"></script>
<script type="text/javascript">
var messages = [];
<ui:repeat value="#{bean.messages}" var="message">
messages['#{message.key}'] = '#{message.value}';
</ui:repeat>
</script>
Or, if it's in JSON format already.
<script type="text/javascript" src="script.js"></script>
<script type="text/javascript">var messages = #{bean.messagesAsJson};</script>
Put the whole <script> in a XHTML file and use ui:include to include it.
<script type="text/javascript" src="script.js"></script>
<ui:include src="script-variables.xhtml" />
Pass *.js through the JspServlet (only if it's enough to evaluate only the ${} expressions). E.g. in web.xml (the <servlet-name> of JspServlet can be found in web.xml of the servletcontainer in question, it's usually jsp).
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.js</url-pattern>
</servlet-mapping>
Make use of "good old" JSP with a modified content type. Rename script.js to script.jsp and add the following line to top of JSP (only if it's enough to evaluate only the ${} expressions):
<%#page contentType="text/javascript" %>
Let JS obtain the data ajaxically during load. Here's a jQuery targeted example.
$.getJSON('json/messages', function(messages) {
$.each(messages, function(key, value) {
$.messages[key] = value;
});
});
Since I don't like techniques that won't let the browser cache the localized Strings, I used the following technique to localize JavaScript alerts, etc. It seems a good fit if the Strings that you need in your JavaScript code are different from the ones needed by the Web server:
<h:head>
<h:outputScript library="javascript" name="#{fw.JsFwStrings}" />
...
I then assign the resource string JsFwStrings to the filename of the JavaScript file defining the localized strings for the given language.
For example, the fw_en.properties file contains the entry
JsFwStrings=JsFwStrings_en.js
And the JsFwStrings_en.js file contains
var TosFramework = TosFramework || {};
TosFramework.Strings = {
UnSavedChangesWarning : 'You have unsaved changes.',
CancelConfirmQuestion : 'Are you sure you want to cancel?'
}
Related
I used several editors for javaScript and i write
var firstName = "John";
console.log(firstName);
problem is that i do not get console.log in Chrome (or other browser)
anyone knows what might happening?
Simply writing:
var firstName = "John"; console.log(firstName);
Isn't enough for javascript to run. Javascript is a scripting language meaning you need to provide some other infrastructure for it to work. In this case that infrastructure is HTML. You can use HTML and Javascript together like so:
<!DOCTYPE html>
<html>
<script>
var firstName = "John"; console.log(firstName);
</script>
</html>
The script tags indicate that you are writing Javascript. Save this file as a .html file and then right-click your file and click view in chrome. Then check the console for results
Check:
You have saved the file (something like index.html)
You have the correct file open in the browser
You have put the js inside a script tag
In my project we're using TYPO3. We're getting some data from the backend and they're assigned as follows in the html page itself.
var items = {};
items.item1 = {settings.item1};
items.item2 = {settings.item2};
items.item3 = {settings.item3};
and then the values are being assigned to the buttons. Those values will be sent back to the JS when an action has triggered. The {settings.item*} comes from the TYPO3 backend. What I wanted to know is how can I add the above code block in a separate JS file rather than adding it in the HTML page. When I tried adding it directly, it doesn't work as the {settings.item*} comes from TYPO3
Thanks
You have to pick up your settings from the HTML since this is what TYPO3 will render for you. but you could rather make use of the data-attributes of HTML, e.g.
You could also render the whole {settings} array as a JSON string into a data-attribute and pick that up with your JavaScript.
You can use javascript on html file (templates , partial , layouts )
You need to add javascript code between the
Syntax:
<![CDATA[ javascript code ]]> TYPO3 Code <![CDATA[ javascript code ]]>
<script type="text/javascript">
<![CDATA[
{
var items = {};
items.item1 = ]]>{settings.item1}<![CDATA[;
items.item2 = ]]>{settings.item2}<![CDATA[;
items.item3 = ]]>{settings.item3}<![CDATA[;
]]>
</script>
Thanks
I hope it helps !!
Can you define the context more precisely? Where are the settings defined, inside of TypoScript, Flexform, PHP (Extensionmanager)?
If you have settings defined in TypoScript you can use:
page.inlineSettings {
setting1 = Hello
setting2 = GoOnTop
}
To make them available in JavaScript as:
TYPO3.settings = {"TS":{"setting1":"Hello","setting2":"GoOnTop"}};
See: https://docs.typo3.org/typo3cms/TyposcriptReference/Setup/Page/Index.html#inlinesettings
Perhaps this will be removed in future versions as it's purpose is usage via ExtJS. Also it's not possible to use stdWrap and such.
Another, more flexible, way is using
page.jsInline {
10 = TEXT
10.stdWrap.dataWrap = var pageId = {TSFE:id};
}
This allows you to use full TypoScript like TEXT, dataWrap, etc.
See: https://docs.typo3.org/typo3cms/TyposcriptReference/Setup/Page/Index.html#jsinline
I wouldn't write JavaScript from PHP just for some configuration. I would store them in data-attributes of a DOM element and grab it via JavaScript. Maybe this is also an option for you.
I have several WebSockets endpoints such as,
wss://localhost:8181/ContextPath/Push
All of such endpoint URLs are hard-coded in separate, external JavaScript files (.js). These JavaScript files are included in respective XHTML files as and when required. The host name and the context path should be evaluated programmatically instead of hard-coding all over the place where they are required.
The host name (localhost:8181) can be obtained in JavaScript using document.location.host but there is no standard/canonical way in JavaScript to obtain a context path where the application runs.
I am doing something like the following.
A global JavaScript variable is declared on the master template as follows.
<f:view locale="#{bean.locale}" encoding="UTF-8" contentType="text/html">
<f:loadBundle basename="messages.ResourceBundle" var="messages"/>
<ui:param name="contextPath" value="#{request.contextPath}"/>
<ui:insert name="metaData"></ui:insert>
<h:head>
<script type="text/javascript">var contextPath = "#{contextPath}";</script>
</h:head>
<h:body id="body">
</h:body>
</f:view>
</html>
The JavaScript files in which the host name and the context path are hard-coded are included in respective template clients or any of sections of the template north, south, east and west as follows.
<html lang="#{bean.language}"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:form>
<h:outputScript library="default" name="js/websockets.js" target="head"/>
</h:form>
For the sake of viewpoint only, websockets.js looks like the following (you can simply ignore it).
if (window.WebSocket) {
// The global variable "contextPath" is unavailable here
// because it is declared afterwards in the generated HTML.
var ws = new WebSocket("wss://"+document.location.host + contextPath + "/Push");
ws.onmessage = function (event) {
// This handler is invoked, when a message is received through a WebSockets channel.
};
$(window).on('beforeunload', function () {
ws.close();
});
} else {}
Now, the global JavaScript variable contextPath declared in the master template is expected to be available in the included JavaScript file namely websockets.js. This is however untrue.
What happens is that the included JavaScript file namely websockets.js where the global variable contextPath is attempted to be accessed, is placed before the hard-coded <script> tag in the generated HTML <head> tag in the master template.
In other words, the global JavaScript variable contextPath is actually attempted to use in the included file websockets.js before being declared.
Anyway, how to get rid of hard-coding the context path in external JavaScript files?
The sole purpose of doing this is that unlike CSS files, EL isn't evaluated in external JavaScript files. Therefore, #{} thing will not work unless it is placed in an XHTML file.
What happens is that the included JavaScript file named websockets.js where the global variable contextPath is attempted to be accessed, is placed before the hard-coded <script> tag in the generated HTML <head> tag in the master template
This is unexpected. You declared the <h:outputScript> referring websockets.js file inside <h:body> with target="head". This is supposed to end up after all other script resources already declared in <h:head>. See also a.o. How to reference CSS / JS / image resource in Facelets template? After all, this appears to be caused by PrimeFaces bundled HeadRenderer which is intented to auto-include some CSS resources and take care of the <facet name="first|middle|last">.
This is worth an issue report to PF guys (if not already done). In the meanwhile, your best bet is to turn off it by explicitly registering the JSF implementation's own HeadRenderer back as below in faces-config.xml (provided that you're using Mojarra).
<render-kit>
<renderer>
<component-family>javax.faces.Output</component-family>
<renderer-type>javax.faces.Head</renderer-type>
<renderer-class>com.sun.faces.renderkit.html_basic.HeadRenderer</renderer-class>
</renderer>
</render-kit>
And explicitly include the PrimeFaces theme-specific theme.css as below in <h:head>:
<h:outputStylesheet library="primefaces-aristo" name="theme.css" />
Coming back to the real question,
Anyway, how to get rid of hard-coding the context path in external JavaScript files?
Either set it as base URI (note: relative path isn't supported in HTML4 / IE6-8).
<h:head>
<base href="#{request.contextPath}/" />
...
</h:head>
var baseURI = $("base").attr("href");
Or set it as data attribute of HTML root element.
<!DOCTYPE html>
<html lang="en" data-baseuri="#{request.contextPath}/" ...>
...
</html>
var baseURI = $("html").data("baseuri");
Unrelated to the concrete problem, as a word of advice, to transparently cover both http+ws and https+wss, consider using location.protocol instead of a hardcoded wss.
var ws = new WebSocket(location.protocol.replace("http", "ws") + "//" + location.host + baseURI + "Push");
Is the following an option for you?
Define a hidden html-tag in the master template, something like :
<span id="pageContextPath" data="#{contextPath}" style="display:none;"></span>
Change your JavaScript Code to something like :
jQuery(document).ready(function ($) {
if (window.WebSocket) {
contextPath = $("#pageContextPath").attr("data");
var ws = new WebSocket("wss://" + document.location.host + contextPath + "/Push");
//...
} else {}
});
I used here jQuery. You may rewrite it in plain JavaScript. But it should be done after "document ready" to ensure that the hidden tag has been rendered. otherwise js won't find that element.
I have some javascript code which requires few fields from server side. I would like to store this code in a separate .js file but, moustache will not be able to populate it with server side information. This is what I have:
<body>
<script type="text/javascript" src="/js/test.js"></script>
</body>
My .js file:
var testField = '{{someValue}}';
alert(testField); // is null
Thank you
You can try to use eval js function: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
var testTemplate = '{{someValue}}';
var testTemplateValue = Mustache.render(testTemplate, view);
var testField = eval(testTemplateValue);
alert(testField);
Eval function is not secure, so you must take care about input manually.
I want to include a script tag, while providing a parameter to it. This is what I came up with so far
Provide a parameter to script URL (cons: generate multiple JS files)
<script src="http://example.com/something.js?P=123" type="text/javascript"></script>
Hide parameter in script tag (cons: same as #1)
<script src="http://example.com/scripts/123/something.js" type="text/javascript"></script>
Google Analytics way (cons: ugly, complicated, global variables)
<script type="text/javascript" charset="utf-8">
var _something = _something || 123;
(function() {
var s = document.createElement('script');
s.type = 'text/javascript';
s.src = 'http://example.com/something.js';
var ss = document.getElementsByTagName('script')[0];
ss.parentNode.insertBefore(s, ss);
})();
</script>
The best thing is to define things (functions &c) in the external script but execute nothing. Then have an inline script that calls functions/methods defined in the external script.
<script src="http://example.com/something.js" type="text/javascript"></script>
<script type="text/javascript">
something(123);
</script>
If the way the script is executed, depends on how it's called, you can add params like your option 1.
Other ways are:
<script params='{"abc": 123}' src="script.js"></script><!-- params is a non standard, non official attr that the script will read -->
or
<script>var _abc = 123;</script>
<script src="script.js"></script>
or even
<script src="script.js#abc=123"></script>
I have to agree with #outis though: load the same thing for everybody, always, and execute it like you/the client want(s) afterwards.
I do this for a cross-sub-domain XHR handler that I have. I call it as:
<script type="text/javascript" src="xd.js#subdomain"></script>
and then in the script, parse it as such (using jQuery):
$('script').each(function(){
if((src = this.src).indexOf('xd.js') < 0){ return; }
xds = src.substr(src.indexOf('#') + 1).split(',');
// do stuff with xds
});
Your first example does not need to generate multiple files. It can be used by JavaScript alone, by detecting window.location.href and parsing it (you might find the likes of http://phpjs.org/functions/parse_url:485 and http://phpjs.org/functions/parse_str:484 helpful in doing this: var queryString = parse_str(parse_url(window.location.href).query); ).
However, if you use something like #P=123 instead of ?P=123, you won't cause another download of the file by your users, so I'd recommend that instead (in which case change "query" in the above code sample to "fragment").
Another possibility is using the HTML5-reserved data-* attributes, and detecting their values within your script:
<script src="http://example.com/something.js" data-myOwnAttribute="someValue" data-anotherCustomAttribute="anotherValue"></script>
The script would then detect along these lines:
(function () {
function getScriptParam (attr) {
var scripts = document.getElementsByTagName('script'),
currentScript = scripts[scripts.length-1];
return currentScript.getAttribute('data-' + attr); // in future, could just use the HTML5 standard dataset attribute instead: currentScript.dataset[attr]
}
var myOwnAttribute = getScriptParam('myOwnAttribute');
// ... do stuff here ...
}());
The real advantage of Google's ugly API is that it allows developers to drop in that code in the <head> of the document (considered proper form), while still acting asynchronously in a cross-browser way. I think they could indeed avoid the global had they combined their dynamic script-tag technique with either of the above approaches.