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
Download> me!
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.
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.
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:
Download lane file now
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.
Related
I recently pull a project from GitHub that included a compile-soy task in its build.xml. The task uses SoyToJsSrcCompiler.jar to compile a couple of soy files into javascript. The project included the target directory so I could see that the compiled files contain code like this:
jive.fbldr.soy.attachments = function(opt_data, opt_sb) {
var output = opt_sb || new soy.StringBuilder();
output.append('<div class="fbldr-attachments"><div class="fbldr-attach-head"><p>Use the following form to upload file attachments and, optionally, include a variable to reference the uploaded file in the form\'s HTML source.</p><p>Multiple files may be attached, but only one at a time. Click "Finished" when all files have been attached.</p></div><div class="fbldr-attach-field"><label>Link to HTML Variable (optional) : </label></div><div class="fbldr-attach-field"><select id="fbldr-attach-link"><option value="" selected="selected">Select HTML variable...</option>');
var optionList34 = opt_data.variables;
var optionListLen34 = optionList34.length;
When I run the same task without any code changes, the resulting compiled keeps replacing opt_sb with opt_ignored and stripped out all references to soy.StringBuilder. I ran "java -jar lib/SoyToJsSrcCompiler.jar --outputPathFormat target/soy2/fbldr.soy templates/fbldr.soy" by hand instead of using the build.xml. I get the same result.
jive.fbldr.soy.attachments = function(opt_data, opt_ignored) {
var output = '<div class="fbldr-attachments"><div class="fbldr-attach-head"><p>Use the following form to upload file attachments and, optionally, include a variable to reference the uploaded file in the form\'s HTML source.</p><p>Multiple files may be attached, but only one at a time. Click "Finished" when all files have been attached.</p></div><div class="fbldr-attach-field"><label>Link to HTML Variable (optional) : </label></div><div class="fbldr-attach-field"><select id="fbldr-attach-link"><option value="" selected="selected">Select HTML variable...</option>';
var optionList4 = opt_data.variables;
var optionListLen4 = optionList4.length;
From all the Closure Templates documentation I've read, it is expected the output will use StringBuilder. I cannot figure out why my call keeps generating output that ignores the StringBuilder. Would someone happen to know what could cause this?
StringBuilder was appropriate for Internet Explorer 7 and earlier browsers. For modern browsers simple string concatenation is more efficient and Closure Templates was changed to make that mode the default mode (as an added bonus, the code is smaller). It sounds like the documentation has not been updated to reflect this change.
If for compatibility reasons your require StringBuilder you can set this option on the command-line using --codeStyle stringbuilder
As we all know, Meteor's initial payload sent to the client comprises (in production) a concatenated javascript file containing the Meteor platform, packages, and all templates parsed into Meteor's reactive templating system. Server-side rendering, where the templates are rendered to HTML and sent to the client in the initial payload, is on its way but doesn't have an expected release date yet.
I'm looking for a way to "hack" or approximate server-side rendering given the available functionality in Meteor 0.8.x. Specifically, I want to:
enable the page to render its initial contents without first waiting for the several hundred KB Meteor platform javascript file to be downloaded and parsed.
alternatively, modify Meteor so it only sends the templates it needs to render the initial request in the javascript payload, and fetches the remaining templates once render is complete.
The use case is http://q42.com. I recognise Meteor isn't the best fit for static websites like this one but I want to try and see how far I can get anyway. Right now the Meteor platform JS file is over 600 KB in size (±200 KB gzipped) and I'd like to reduce this size if possible.
Note: I'm aware of and already using Arunoda's fast-render package, which is intended to send data with the initial payload. In this case I want to cut down on time-to-first-render by also getting the templates themselves down faster.
This is a little bit tricky. But there are some things you could do. This may not be the beeest way to do it but it could help you get started somehow.
Meteor is build with many packages as 'default', sometimes some are not needed. You can remove the standard-app-packages and add the packages (that you need and use manually) listed here: https://github.com/meteor/meteor/blob/devel/packages/standard-app-packages/package.js
To cut down the templates you would have to include the bare templates that you use and include the other template's separately and perhaps send down the Template information via a Collection, using a live observer handle to initiate the templates
You would have to 'render' the templates on the server side or store them manually in your collection using Spacebars.compile from the spacebars-compiler package which is a little tricky but you could have it done decently:
This should give you a rough idea, not sure how to get passed the 'eval' bit of it though:
HTML file in /private/template.html
<template name="test">
Hello {{name}}
</template>
JS file in /private/template.js
Template.test.name = function() { return "Bob" }
Server side code
var collection = new Meteor.Collection("templates");
var templateData = Assets.getText("template.html");
var templateJs = Assets.getText("template.js");
var compiled = Spacebars.compile(templateData).toString();
var jsData = templateJs;
collection.insert({templateName:"test", data: templateData, js: templateJs});
Client Side code
collection.find().observeChanges({
added: function(id, fields) {
var template = fields.data,
name = fields.name,
js = fields.js;
Template["name"] = UI.Component.extend({
kind: "name",
render: eval(template),
});
eval(js);
}
});
Then just subscribe to the collection asking for your template and it should exist. If you use iron-router I think (not sure) you could make the subscription wait before the template is rendered so you could have it work.
Again this is just a 'hacky' solution, one thing I personally don't like about it is the use of eval, but javascript needs to run somehow...
You could loop through files in a particular folder using fs = Npm.require('fs') to render each template too.
One alternative would be to inject a 'script' tag calling the compiled js template and template helpers to let the template exist.
When users upload an image, it is stored in the file system but outside of what is publicly reachable.
I am displaying a list of items, and each item has an image associated with it.
How can I display the image when the actual file is stored outside of the wwwroot folder? i.e. it isn't publicly available.
Since the action method is running on the server, it can access any file it has permission to. The file does not need to be within the wwwroot folder. You simply need to tell the action method which image to get.
For instance:
<img src="/mycontroller/myaction/123">
Your action would then look like:
public FileResult MyAction(int id)
{
var dir = "c:\myimages\";
var path = Path.Combine(dir, id + ".jpg");
return new FileStreamResult(new FileStream(path, FileMode.Open), "image/jpeg");
}
Please note that the id is an int which will prevent someone from injecting a path to access different files on the drive/share.
You could do this two ways.
Option 1, you could create a Virtual directory which points to this other Directory. This would then mean that you could access the images via another URL. e.g. Create a Virtual Directory called "OtherImages", then your URL would be;
http://www.mywebsite.com/otherimages/myimage.jpg
Option 2, you could create a simple HttpHandler which can load up the image from the absolute path, then output this in the response. Read up on HttpHandlers and dynamically generating images.
I have a data.json file that I would like to load and that I have placed in the lib/ folder. What should I do in order to load that JSON into a variable in the server?
Thanks
There are three ways you can go about this, it depends what you're most comfortable with & your use case.
The first is to store it as a JS Object
if your json data is { "name":"bob" } you could use myjson = {"name":"bob"} in a .js file in the /lib folder and just call myjson when you need it.
Using an http call
You need the Meteor http package, installed via meteor add http.
Server Side code
myobject = HTTP.get(Meteor.absoluteUrl("/myfile.json")).data;
Client Side Code
HTTP.get(Meteor.absoluteUrl("/myfile.json"), function(err,result) }
console.log(result.data);
});
Another way to do it is to fetch the json file ajax style (you would have to put it in your /public folder though and use Meteor.http to call it.
Read the file directly
Lastly you could read the file directly, you store your myfile.json in a private directory in your project's root:
var myjson = {};
myjson = JSON.parse(Assets.getText("myfile.json"));
If you want to access this on the client side you would have to interface it with a Meteor.methods and Meteor.call
So whichever way you want, the first is the easiest but I'm not too sure how you want to use it or whether you want to pick the file or something
As I am new to all this I suspect this is not the correct way to do this, however this has worked for me...
Three coffee script files, two in the server directory:
server.coffee:
Meteor.startup ->
insertSample = (jsondata) ->
Fiber(->
Documents.insert
name: "Sample doc"
data: jsondata
).run()
if Documents.find().count() is 0
insertJSONfile("tests/test.json", insertSample)
and insertJSONfile.coffee:
fs = __meteor_bootstrap__.require("fs")
insertJSONfile = (file, insert) ->
jsondata = undefined
fs.readFile file, (err, data) ->
throw err if err
jsondata = JSON.stringify(JSON.parse(data))
insert(jsondata)
and model.coffee in the root dir:
#Documents = new Meteor.Collection("documents")
On startup this should load and insert the JSON file (in my case I've stored this in the tests directory) into a field in the documents collection.
I would love to hear from others on how this should be done properly.
I assume you want the json content to be represented as an object and not as a simple string.
I use js-yaml (https://github.com/nodeca/js-yaml), assuming you install the npm package. You can also just copy it manually.
yaml = __meteor_bootstrap__.require('js-yaml')
fs = __meteor_bootstrap__.require('fs')
content = fs.readFileSync(file, 'utf8')
object = yaml.load(content)
and that's it! I personally persist my json into meteor collections.
We have a requirement where the url of a page needs to be localizable/translated. Our existing mechanism relies on the actual published url to retrieve the page via oData. To clarify with a simplified example: we have some logic in the front end that takes the request url (which doesn't have a file extension, appends a .html extension, e.g.:
/my-awesome-path/my-awesome-page
now becomes
/my-awesome-path/my-awesome-page.html
the logic then pulls the page from oData using the query
/odata.svc/Pages?$filter=url eq '/my-awesome-path/my-awesome-page.html'
There is much more logic that we have around this to parse this SEO-friendly url and get MVC controller function parameters and other whatnots, but that's not relevant here.
Our requirement is that we cannot localize the page to give it a translated url since this would mean the entire page can't be managed in the parent web publication.
To get the localized path leading up to the page filename we simply localize the SGs. The difficulty is with the page filename. On the page's metadata we have a linked "localizable metadata" component with a field for providing a localized page filename.
What we'd like to do is update the page's URL property during the publishing/deployment process to update the page's published url with the localized page filename from this linked metadata component (assume that we have access to the localized filename field's value at any stage between start of publishing to commitment of deployment).
I've tried doing this via a custom resolver, however, at this level it appears that the page.PublishedUrl property is already established by the CM and cannot be overridden. So updating the page.FileName property doesn't do anything useful.
I've also tried directly updating the URL column in the PAGE table in Broker DB to a different name and it appears that everything continues to work, including dynamic linking and unpublishing of the page. Obviously writing a storage extension or a deployer extension to directly update the DB via jdbc is unacceptable.
Here are the options I'm thinking of:
1) try a deployer extension and use the Tridion API to update the url property
2) try writing a custom renderer that executes the url replace logic without actually updating the url in the broker. I don't favour this since request-time processing is required each time.
My question is: what is the most appropriate way to update the page url property? Will writing a custom deployer using Tridion APIs to update the URL property lead me to a dead end just like the Resolver did?
Following the points in Nuno's comment above I decided against using a custom deployer and have solved the problem using 2 event subscriptions of the Event System. On page publish I first localize the page, grab the localized filename from the localized linked metadata component and save the page. Then in a subsequent event I simply unlocalize the page. Here is my working code:
[TcmExtension("Publish or Unpublish Events")]
public class PublishOrUnpublishEvents : TcmExtension
{
public PublishOrUnpublishEvents()
{
EventSystem.Subscribe<Page, PublishEventArgs>(SetLocalizedPageFileName, EventPhases.Initiated);
EventSystem.Subscribe<Page, SetPublishStateEventArgs>(UnlocalizePageOncePublished, EventPhases.Initiated);
}
public void SetLocalizedPageFileName(Page page, PublishEventArgs args, EventPhases phase)
{
string localFilename = GetLocalilizedFileNameFromPageMetadata(page);
if (!string.IsNullOrEmpty(localFilename))
{
page.Localize();
if (page.TryCheckOut())
{
page.FileName = localFilename;
page.Save(true);
}
}
}
public void UnlocalizePageOncePublished(Page page, SetPublishStateEventArgs args, EventPhases phase)
{
string localFilename = GetLocalilizedFileNameFromPageMetadata(page);
if (!string.IsNullOrEmpty(localFilename))
page.UnLocalize();
}
private string GetLocalilizedFileNameFromPageMetadata(Page page)
{
string localFilename = string.Empty;
if (page.Metadata != null)
{
ItemFields fields = new ItemFields(page.Metadata, page.MetadataSchema);
if (fields.Contains("LocalizableMeta"))
{
ComponentLinkField localMetaField = fields["LocalizableMeta"] as ComponentLinkField;
Component component = localMetaField.Value;
ItemFields compFields = new ItemFields(component.Content, component.Schema);
if (compFields.Contains("LocalizedPageFilename"))
{
SingleLineTextField fileNameTextField = compFields["LocalizedPageFilename"] as SingleLineTextField;
localFilename = fileNameTextField.Value;
}
}
}
return localFilename;
}
}
Perhaps another option:
Store the localised URL has an additional metadata field for the page, keeping the same physical URL for the published pages.
I see your requirement is to avoid localisation of child pages, I like the way it's possible in wordpress to enter globally how URLs work, for example:
/mysite/%postname%/
It would be cool to build something similar to this within SDL Tridion, where the content title could be extracted and used at the content URL.
Either way, if you'd have to write a system that takes the 'Friendly URL' and does a look up for the actual URL, which I think would be pretty simple.