Meteor Upload to AWS S3 - meteor

I have a Meteor app in which I insert a document (title, description, customer, ...) into a database. The app is using Autoform, Simple Schema and Collection2. I now want to add the possibility to upload a file to S3.
To keep things simple, I would present a filepicker as part of the 'Create Document' and once the file is uploaded, the URL field (from Autoform), should show the URL of the document on S3 (once uploaded) so that the URL is eventually stored in the document collection when the create button is clicked. I realise there might be better ways, but wanted to keep things simple for now.
I have tried to combine the tutorial here. The upload to S3 works, but I fail to get the URL for the uploaded file stored documents collection. The below screenshot shows the layout. Any idea's?
My current code can be found here.

There appear to be autoform add-on packages that specifically address S3 file upload (see the "Files" section here), but since you seem to be using this as learning opportunity, I'll try to explain how I'd do it using core meteor.
But first, before I forget, your uploader template is a child of #autoForm but has a form element of it's own. I think that will cause the generated HTML to have nested form elements (which is no-no). To fix that, I'd move the {{/autoForm}} before the {{> uploader}} and add an event handler for your submit button that submits the form using $('#documentForm').submit(). Note: I haven't tested that.
Now, on to your actual question. You are essentially asking how to modify something in a template (the value of the url field) from an event in a nested template (the uploader template). I'd do that as follows:
When an instance of the parent template is created, attach a ReactiveVar to it that will hold the URL of the uploaded file.
Template.adminDocumentNew.onCreated(function () {
this.url = new ReactiveVar();
});
Provide a helper to get the reactive var.
Template.adminDocumentNew.helpers({
url: function () {
return Template.instance().url;
}
});
Use the helper to set the value in the form.
{{> afQuickField name='url' value=url.get}}
Pass the reactive var to the uploader template as the data context:
{{> uploader urlVar=url}}
Use the reactive var to set the URL when the upload finishes (somewhere in upload-to-amazon-s3):
template.data.urlVar.set(url);
I've created a meteorpad that demonstrates the basic idea outside of the context of autoform and S3.

Related

Will a publication of all my images slow down my app when I will have more content?

I am currently using EasySearch for my search solution for my app and I have a question on pub/sub of the search results.
Basically, the way the search works is that users will put into a search and a number of posts will be returned (I have a collections called Posts where I implemented the EasySearch).
I assumed EasySearch probably will automatically publish or subscribe depending on the results of the search queries, so I don't think I should worry about sending too many data to the client? (Correct if I am wrong).
However, the problem that I am having at the moment is that each posts is associated with a image from a collections called Images where my pub/sub is simply publish all and subscribe all from client and server and not related to the search at all.
I am just wondering does that mean I won't able to scale since I will be publishing every images to client regardless of the searches?
Post collection
Posts = new Mongo.Collection('posts');
Posts.initEasySearch(['firstName', 'lastName', 'degreeStudy', 'tags'], {
'limit' : 20,
'use' : 'mongo-db'
});
Posts.allow({
update: function(userId, post) { return ownsDocument(userId, post); },
remove: function(userId, post) { return ownsDocument(userId, post); },
});
Image collection
Images = new FS.Collection("images", {
stores: [new FS.Store.GridFS("images")]
});
Template page for showing Search results
<template name="postPage">
<div class="container">
{{#ifEsHasNoResults index="posts"}}
<div class="jumbotron no-results"> <h1>No results found!</h1></div>
{{/ifEsHasNoResults}}
{{#ifEsIsSearching index="posts"}}
{{>loading}}
{{else}}
{{#esEach index="posts"}}
{{> postItem}}
{{/esEach}}
{{> esLoadMoreButton index="posts"}}
{{/ifEsIsSearching}}
</div>
</template>
In short, how can I make my pub and sub of the images collections related to the easySearch result?
I think you are confusing the cursor returned by your publication with the data itself. The cursor is like a way to make dynamic queries without having to load all your data (unless you do load all of them using for example an {{# each images}}). Read that for more info
This being said (no scaling issues ahead), I assume that inside your {{> postItem}} template, you have an image helper fetching the related image in your image collection.
This will work but keep this in mind: your user can open his console and get any image in your Images collection simply by doing an Images.find() related command. So it should be ok as long as you don't have private content. However if you do, you may want to use a method to fetch your image instead of publishing/exposing the whole collection.

Reinitialize library when new data is available

I'm currently developing a Meteor application where I use the video.js-Library.
I have the following template:
template(name='foo')
.video.embed-responsive.embed-responsive-16by9
with richMediaContent
video#video.video-js.vjs-default-skin.vjs-big-play-centered(controls='' preload='auto')
source(src='{{video.videoUrl}}' type='video/mp4')
p.vjs-no-js {{i18n 'videoTagNotSupported'}}
Initializing the video.js-Library after the template is rendered works fine.
Template.foo.onRendered ->
videojs document.getElementsByClassName('video-js')[0], {}
But the videojs-Library is not reinitialized if the same template is rendered with a different video (with a different richMediaContent).
I've already tried to move the video-Part in an own template and included it in the foo-Template so that the onRendered-Call should get called every time a new video is loaded. But this doesn't seem to work.
Do you have any idea how I can reinitialize the library if the video changes?
Thanks in advance!
New answer
Indeed, when your route changes but uses the same template, the said template does not get rendered again, therefore your js plugin call will not trigger a second time. What you can do instead is call your js plugin in an onAfterAction call, within your route definition:
Router.route('/video/:_id', {
name: 'video_page',
template: 'foo',
// ...
onAfterAction: function () {
videojs document.getElementsByClassName('video-js')[0], {}
}
});
Previous answer
I think you are looking for the almighty this.autorun. At the end of your onRendered function, it should look like this (I type it in pure javascript)
this.autorun(function () {
var video = Session.get("video"); // reactive data
videojs document.getElementsByClassName('video-js')[0], {}
});
The idea is that the first line must include, within the autorun function, a way to get your reactive data. In that case, I use the Session which is reactive. Collections are also reactive, so another way would be something like Videos.findOne();. This will depend on how you get that video element.
What this does is that any time the reactive data changes, the callback for this.autorun will run again, and your video plugin will be reset.

Using an <embed> tag with Meteor.js

I have a group of pdfs on another domain, and their urls in my Meteor app. My intention is to click a button, have it set a session variable and new data context, and have the associated pdf show up as an embedded object.
What's happening, is that if I directly reference it like below, it shows up the first time, but it doesn't update when I change data contexts.
<embed src="{{selectedSheet.docPath}}" type="application/pdf">
I've also tried embedding it all within a {{#with}} block, but in this case nothing will show up. It's definitely the <embed> that's breaking it -- if I remove that, this block works as expected.
{{#with selectedSheet}}
<embed src="{{docPath}}" type="application/pdf">
{{/with}}
I'm using this helper to get my selectedSheet, and I've verified that it's not an issue with the Session.get portion of it.
Template.bldgLevel.helpers({
selectedSheet: function(){
return Documents.findOne({"_id": Session.get("currentSheet")});
}
});
Any help appreciated. I'm pretty new at this :)

How to implement a button with a backend action in OpenWRT LUCI without UCI side effects

I am trying to to implement a button in LuCI which, when clicked , runs a shell script in the backend. This is the model code for this:
field_var_36 = section_var_7:option(Button,"buttonkk36",translate("ButtonKK"))
field_var_36.inputstyle = "apply"
field_var_36.rmempty = true
function field_var_36.write(self, section)
luci.sys.call('echo "ABCDEFG123" >/dev/null')
end
Though this is working it has some unwanted side-effects. All the unsaved modifications in the page are getting saved and I get a "n unsaved changes" notifications at the top. My guess is the the button if of type "submit" and all the fields inside the html "form" are getting "sumbitted". I dont want this to happen. The button needs to be standalone. Can this be done?
One other option I tried was using a template with button implemented in html. But I dont know how to connect this to the backend script. Is there a javascript API function in LuCI which takes the script as argument?
Thanks in advance for any help.
I did that using a view template.
You must set up an "entry(...)" in an "index()" function in the "controller/" directory.
The entry associates a path (last part of the LuCI URL's) with either a template view, cbi or LUA function, which is just what you want.
Pay attention that when you change by hand a controller file on the target openwrt device, you must delete /tmp/luci-indexcache so that the LuCI dispatcher does not use the older version.
Then in the template you can use "luci.dispatcher.build_url()" to create the URL that will make the controller call the function, and you put this as the target URL for your button (through either or or onClick="document.location=..."> and so on)

Precompiled views: can I still read the contents at runtime?

I have my views setup to pre-compile, and therefore, at runtime if I were to try and read the view file (e.g. "~\Views\User\Report.cshtml") I'd get the following dummy-text, as opposed to the contents of my view:
This is a marker file generated by the precompilation tool, and should not be deleted!
Problem is, I'd like to re-use the cshtml view, and rerender it another way at runtime, but I cannot due to the above restriction.
The scenario:
An admin can see a list of users in a /User/Report route. It outputs some HTML that has a list of all users, and their information in an HTML table. These admins frequently want to download this html file (styles and all) to email it as an attachment to someone else. They could, of course, go to File->Save in their browser, but I wanted to simplify that action by adding a link to the page "Download this report as HTML" that would simply return the same page's content, as a forced-downloaded HTML file (2012-07-11_UserReport.html).
So, what I tried to do was re-render the view by running the Report.cshtml file's contents through ASP.NET's File() method, like this:
var html = System.IO.File.ReadAllText(Server.MapPath(#"~\Views\User\Report.cshtml"));
var bytes = System.Text.Encoding.UTF8.GetBytes(html);
return File(bytes,"text/html",string.Format("{0}_UserReport.html",DateTime.Now.ToString("yyyy-MM-dd")));
But, like I mentioned earlier, the file comes back as the dummy-text, not the view, since I'm pre-compiling the views.
I understand that to get around the pre-compilition, I could simply copy the Report.cshtml file, and rename it to Report.uncompiled (adding it to the csproj as of course) and read the contents of it, that's an ok solution, but not good enough.
What I would really like to know is: Is there a way I can get at that pre-compiled content? I looked in the Assembly's embedded resources, and they are not there. Any ideas/suggestions?
Updated with current solution
So after searching around some more, and trying to use WebClient/WebRequest to just make a request to the route's URL and send the response back down to the user to download while at the same time trying to pass the user's .ASPXAUTH cookie (that made WebClient/WebRequest time out for some reason? I even tried to create a new ticket, same result) I ended up going with what I didn't want to do: duplicate the view file, and rename it so it's not precompiled.
The view file (Report.uncompiled) had to be modified a bit as it was, and then I ran it through RazorEngine's Razor.Parse method and got what I needed, but it just felt hackey. Would still like a way to access the view file (Report.cshtml) even after it's compiled.
var templateHtml = Razor.Parse(System.IO.File.ReadAllText(Server.MapPath(#"~\Views\User\Report.uncompiled")),model);
var bytes = System.Text.Encoding.UTF8.GetBytes(templateHtml);
return File(bytes, "text/html", string.Format("{0}_UserReport.html", DateTime.Now.ToString("yyyy-MM-dd")));
Would the WebClient class work?
using System.Net;
using (WebClient client = new WebClient ())
{
client.DownloadFile("http://yourwebsite.com/test.html", #"C:\directory.html");
// If you just want access to the html, see below
string html = client.DownloadString("http://yourwebsite.com/test.html");
}
Just have this fire whenever your user clicks a button and then it will save the current content of the page wherever? You could probably also have a directory selector and feed whatever they select into that second parameter.
It essentially does the same thing as the browser save as, if that's what you want.

Resources