Pablo
Pablo

Reputation: 2386

How to create dynamic assets in Meteor

I thought this would be easy. I want to create simple files the user can download by clicking a link.

Write what you want into the servers assets/app folder and then generate a simple link

<a href="/new.txt" download="yourNewFile.txt">Download> me!</a>

Writing files into Meteor's server side asset folder is easy. And the download link above will always download a file with the name you specified.

You will get a yourNewFile.txt in the client's download folder. But, unfortunately its content will not be what you wrote on the server (new.txt).

Meteor has the strange behavior of downloading its startup html page as the content if the name of your content wasn't originally in the public folder. I think this is bug .... put the above anchor into a default Meteor project and click the link .. don't even create a public folder. You get a downloaded file with the name you asked for...

So, if you put stubs in the public folder (you know the names of the assets you are going to create) then you can create them dynamically.

I don't know the names before hand. Is there any way to get Meteor to 'update' its assets list with the new names I want to use?

I know there are packages that can do this. I'd like to just do it myself as above, really shouldn't be this hard.

Upvotes: 1

Views: 1353

Answers (2)

Pablo
Pablo

Reputation: 2386

Here is the raw Picker (meteorhacks:picker) route and method I used to get this running. I've kept it lean and its just what I got working and probably not the best way to do this ... the synchronous methods (like readFileSync) throw exceptions if things are not right, so they should be wrapped in try-catch blocks and the mkdirp is a npm package loaded through meteorhacks:npm package hence the Meteor.npmRequire. Thanks again to saimeunt for the directions.

Picker.route('/dynamic-asset/:filename', function(params, req, res, next) {
    console.log('/dynamic-asset route!');
    var fs = Npm.require('fs');
    var path = Npm.require('path');
    var theDir = path.resolve('./dynamic-asset');
    var filename = params.filename;
    var fileContent = fs.readFileSync(theDir + '/' + filename, {encoding:'utf8'});

    res.end(fileContent);

});

The Meteor method that creates the file is

writeFile: function(fname, content) {
        console.log('writeFile', fname);

        var fs = Npm.require('fs');
        var path = Npm.require('path'); 
        var mkdirp = Meteor.npmRequire('mkdirp');

        // verify/make  directory
        var theDir = path.resolve('./dynamic-asset');
        mkdirp(theDir);

        fs.writeFileSync(theDir + '/' + fname, content);

        return 'aok';
    }

and the hyper link I generate on the client if the file gets created looks like this:

<a href="/dynamic-asset/PBAL.b00" download="">Download lane file now</a>

I incorrectly stated in my original question at the top that you could use stubs and write files into the assets folder. Its not so .. you will only get back the stub ... sorry.

Upvotes: 2

saimeunt
saimeunt

Reputation: 22696

The public/ folder intended use is specifically for static assets. Its content is served by the node http server.

If you want to dynamically generate assets on the server, you can rely on iron:router server side routes.

Here is a simple example :

lib/router.js

Router.route("/dynamic-asset/:filename",function(){
  var filename = this.params.filename;
  this.response.setHeader("Content-Disposition",
    "attachment; filename=" + filename);
  this.response.end("Hello World !");
},{
  name: "dynamic-asset",
  where: "server"
});

In server-side route controllers, you get access to this.response which is a standard node HTTP response instance to respond to the client with the correct server generated content. You can query your Mongo collections using the eventual parameters in the URL for example.

client/views/download/download.html

<template name="download">
  {{#linkTo route="dynamic-asset" target="_blank" download=""}}
    Download {{filename}}
  {{/linkTo}}
</template>

client/views/parent/parent.html

<template name="parent">
  {{> download filename="new.txt"}}
</template>

The linkTo block helper must be called in a context where the route parameters are accessible as template helpers. It will generate an anchor tag having an href set to Router.path(route, dataContext). It means that if our server-side route URL is /dynamic-asset/:filename, having a data context where filename is accessible and set to "new.txt" will generate this URL : /dynamic-asset/new.txt.

In this example we set the current data context of the download template to {filename: "new.txt"} thanks to the template invocation syntax.

Note that target="_blank" is necessary to avoid being redirected to the dynamic asset URL inside the current tab, and the download HTML attribute must be set to avoid considering the link as something the browser should open inside a new tab. The download attribute value is irrelevant as it's value will be overriden server-side.

Upvotes: 2

Related Questions