Rails asset pipeline & coffeescript files, how to bind actions in various files to ajax calls? - ruby-on-rails-3

Using rails 3 asset pipeline, I've structured the javascript (by using coffeescript) to files regarding the model. For example, all comment writing related javascript is stored to /app/assets/javascripts/comments.js.coffee, and user overlay related (fetching a:href's and triggering ajax on them) are stored in /app/assets/javascripts/users.js.coffee.
However, now I'm using more and more AJAX calls, where HTML content is pulled dynamically to the site. The problem is that I need to execute the javascript in various files, but as coffeescript is scoped inside a function, I can not access them.
Let's say that I've got a general.js.coffee file with following code
$(document).ready ->
# Parse all images with action
$("img.clickableaction").click ->
# Fetch some content
$.ajax
url: "something.php"
dataType: "html"
complete: (xhr, status) ->
# We got the content, set it up to a container
$("#somecontainer").html(receiveddata)
# The tricky part:
# run code in comments.js.coffee for #somecontainer (or for the whole dom)
# run code in users.js.coffee for #somecontainer (or for the whole dom)
And comments.js.coffee contains for example the following:
$(document).ready ->
commentDiv = $('div#commentsContainer')
commentsFetch = $('a.commentsFetch')
# Set commentid for comments fetch
commentsFetch.bind 'ajax:beforeSend', (event, xhr, settings) ->
# do stuff
The comments.js.coffee code works for the initial page view, e.g. the HTML code that was received when user loaded the page. But now, I need to parse the comments.js.coffee code for the content returned from the ajax call.
I can not make a function inside comments.js because it is scoped away, and can not be accessed from the general.js. This is what coffeescript produces:
(function() {
$(document).ready(function() {
var commentDiv, commentsFetch;
commentDiv = $('div#commentsContainer');
commentsFetch = $('a.commentsFetch');
}
})
I could make a global function for each separate file, e.g. window.comments && window.users, but then I'd need to call window.comments from the many places where I need to have ajax oncomplete call. On the long term, that will produce duplicate and hard to maintain -code.
How could something like this be made:
// comments.js
window.ajaxExecuteBlocks.push(function() { // comments related stuff });
// user.js
window.ajaxExecuteBlocks.push(function() { // user related stuff });
// general.js ajax on complete:
window.runExecuteBlocks()
Then, runExecuteBlocks would somehow run through all the functions that have been initialized in various controller-specific javascript files.
Anyone implemented similar system?

Related

Vue.js including non-npm JavaScript library

I'm a total beginner with Vue.js and struggling to find the answer to what I feel is a fairly basic need.
I have a JavaScript library that cannot be installed locally and must be imported via script tag in the index.html file in the old-fashioned way:
<script src="https://foo.bar/scriptyscripts.js"></script>
This library has a bunch of methods in it that I need to use in various spots throughout my app, so it's not going to be a problem to load it globally. The issue I'm facing is that it's loading fine, but the methods are not being recognised in components.
I can use the methods and whatnot if I put them all in a script tag in the index.html however doing that rather defeats the whole point of having components.
Can anyone help me with the step that I'm missing to register all of the methods in this loaded js file so my components don't get mad?
Specifically, the script contains require.js and a collection of other things including JQuery.
Including the library makes the method 'require' available, which is used to load other modules on demand - the example being "js/qlik" in the below snippet. "js/qlik" loads JQuery and a stack of stuff associated with "qlik".
//async login method here. not relevant to this problem
login().then(() => {
require.config({
baseUrl:
(config.isSecure ? "https://" : "http://") +
config.host +
(config.port ? ":" + config.port : "") +
config.prefix +
"resources",
webIntegrationId: config.webIntegrationId,
});
//Load js/qlik after authentication is successful
require(["js/qlik"], function (qlik) {
qlik.on("error", function (error) {
$("#popupText").append(error.message + "<br>");
$("#popup").fadeIn(1000);
});
$("#closePopup").click(function () {
$("#popup").hide();
});
var app = qlik.openApp("caa866be-c8e1-44c8-b67b-dac9d24421fa", config);
});
});
The problem I have is that if I load this library in the index.html file and then try to execute the methods in the snippet above in any component, it does not know that the methods are available.
I see:
'Module not found: Error: Can't resolve 'js/qlik'
66:11 error '$' is not defined
which indicates that the components are unaware of the methods because they're not registered like they would be if I were importing a packaged afterinstalling it locally via NPM
i.e. Your original js code: function abc(){// sth...}
What you need: window.abc = ()=>{// sth...}
Even if you want it in Vue dom.
You should add vue.prototype.abc = ()=>{//sth...}

JSReport External javascript with Json data required

We have hosted jsreport node application on EBS. We created template and using css and javascripts from a static website(hosted internally). In the external javascript file we are using variables similar to what jsreport requires i.e. {{variablename}} which does not work. When we add the javascript inline in the template it works.
We know there should be some other way around to specify this but could not find it.
This won't work. jsreport templating engines only compile and process the html output, not the referenced scripts.
However you can try this approach:
Put a placeholder in a template content where you want to put external script. Lets say we want to put inline jquery
<script>
$$$myScript
</script>
<script>
$(() => {
alert('yes I have jquery inlined')
})
</script>
Create jsreport custom server script which downloads your external script, in this case jquery, and replace the placeholder with its content
var getReq = require('request').get
function beforeRender(req, res, done) {
getReq('https://code.jquery.com/jquery-3.1.0.min.js', (err, res, body) => {
req.template.content = req.template.content.replace('$$$myScript', body.toString())
done()
})
}
The script will run before the templating engines are executed therefore you can use templating engines tags inside it now.
playground live demo here

Internationalization with Handlebars

I'm trying to internationalize my application that uses Express and Handlebars. Is it possible to get Handlebars partials (fragments) to load and render the localization resource file?
Noting that I've already read this question: express3-handlebars and 18next-node - internationalisation based on page?.
Here is my directory structure:
views/
index.html
login.html
fragments/
frag1.html
frag2.html
frag3.html
locales/
index.json
login.json
fragments/
frag1.json
frag2.json
frag3.json
If necessary, I can separate the JSON files in the locales/ directory to be something like this:
locales/
en-CA/
index.json
...other files
fr-CA/
index.json
...other files
Here is the relevant code in my server.js file:
// ...
hbs = exphbs.create({
extname: '.html',
layoutsDir: [
__dirname + '/views'
],
partialsDir: [
__dirname + '/views/fragments'
],
helpers: {
'json': function(context) {
return JSON.stringify(context);
},
't': function(k) {
// ?
}
}
});
app.engine('.html', hbs.engine);
app.set('view engine', 'html');
The t helper is what I need help with. In my templates/template fragments, I have these:
<h1>{{ t 'pageTitle' }}</h1>
<p>{{ t 'foo' }}</p>
<p>{{ t 'moreThings' }}</p>
And my JSON file could look like this:
{
"pageTitle": "Hello world",
"foo": "Paragraph contents here",
"moreThings": "There are %d things"
}
Also how do I deal with the printf parameters?
Doing internationalization in your application means doing two things:
1) Determine which locale should be used
Depending on how you determine the used locale it can be difficult to do this inside a helper. Helpers do not have access to the request object for instance. To be honest i cannot think of a good way to do this inside a helper.
Personally i use the i18n-abide middle-ware to do internationalization. They have several options to determine the locale for a given request. Once locale is determined it is added as a property to the request object. So you only need to determine the locale once for each request. An other advantage is that you have also access to the locale outside the handlebars helper.
2) Access the resource files
To access the resource files from within a helper means that you should read and parse the resource files outside the helper. Parsing resource files every time you need to translate a string really hurts performance.
Here you also should use middle-ware. You can do something like the pseudo code below.
function setup() {
// Load resource files from disk and parse them.
var resources = { /* parsed resources*/ }
return function(req, res, next) {
var locale = determineLocalFunction(req);
req.getText = function(label) {
return resources[local][label];
}
}
}
Now you can use the req.getText function every where in your code. Personally i never use language labels inside a partial. Instead i pass all the language strings needed in a partial using a data object. The reason behind this is that i think partials should be as re-usable as possible. Using hardcoded language labels inside them makes them less re-useable.
When you do want to use the getText function in your partials you can pass to getText function to your partial.
Something like this:
var objectPassedToPartial = {
getText: req.getText
}
Use it like:
{{getText 'label'}}
Read more about Mozilla's i18n-abide solution, i really love it.

MVC 4: How to create visual Indicator while FileResult Action generates PDF to be sent back to browser?

I have FileResult Controller Action that can take a couple minutes to run under some circumstances. I would like to provide our users a visual indication that the Action is running, such as a message, a spinner, or a progress bar. The trouble is, I'm having a tough time figuring out how to "detect" on the front end that the Action has completed.
I have been toying with Ajax calls to the FileResult Action, but that doesn't work because Ajax can't return a file to the browser. I also looked at Asynchronous Actions and Tasks, but it looks like FileResult does not support the "await" keyword, which I think is necessary.
At this point, I don't know what to try next, and am very open to thoughts/ideas.
Edit:
I got this working as bobek suggested. Basically, I saved the PDF file to disk, and returned the file path as an ActionResult Content(fullFilePath). Then, on my page, I used something like this to display a little spinner gif while the Action executed, and then a link to the file after it completed.
var img = $("img#reportLoadingIcon");
var link = $('a#exportedFileLink');
link.hide();
img.show();
$.ajax({
url: url,
success: function (result) {
link.attr({target: '_blank', href: result});
link.show();
img.hide();
}
});
You could make it a regular POST action called via AJAX, and instead of returning a File you can return path to file and redirect the user to that path.

ExtJS4 - How to make an initial entry to a site with param data?

I have an ExtJS4 site www.mysite.com where I serve index.html when a user enter the site. I want the user to be able to access the site with some param data redirected from another site. For example, www.mysite.com?q=10
How do I capture q=10 which I will use to retrieve some data from the database?
How do I send index.html so that browser retrieves javascript and css files. Once all the javascript and css files are loaded, I need to render a page displaying the result from the database?
Thanks
To get the url parameters I've done this :
var getParams = document.URL.split("?");
var params = Ext.urlDecode(getParams[getParams.length - 1]);
console.log(params.q) // you should see 10 being printed
If index.html is gonna come with some param in the url you can use the launch method to do an ajax request and bassed on that response render something
Ext.application({
name : 'MyAppWithDynamicFirstPage',
launch : function() {
var getParams = document.URL.split("?");
var params = Ext.urlDecode(getParams[getParams.length - 1]);
var q = params.q;
Ext.Ajax.request({
url: 'someServlet/getViewToRender',
params: {
'q': q
},
success: function(response, opts) {
//bassed on this you would do something else like render some specific panel on your viewport
},
failure: function(response, opts) {
console.log('server-side failure with status code ' + response.status);
}
});
}
});
I hope this was of some help.
Best regards.
Depends of your web server, programming language and architecture
Usually first ExtJs is loading with all js/css. After it loaded, data loads asynchronously from the server. But if you exactly know what are you doing, you can render your data into a global variable inside a script tag and then use it in the code.