this is my first StackOverflow question, so I hope I ask it right!
I have a Ruby on Rails web application where in our javascript files, I have the following CoffeeScript code, which detects when a button with id="submit" is pressed and then scrolls to the first error that appears, if any (the "submit" buttons are used in our forms):
#scroll_to_error = ->
document.getElementById('submit').onclick = ->
if $('.alert-danger:visible:first').length > 0
$('html, body').animate { scrollTop: $('.alert-danger:visible:first').offset().top - 220 }, '1000'
return
I have the file included in our application.rb file in the config folder, so that it can be called in any HTML file in the views section ~~ however, I was wondering if there were a way so that it can be called in all the views that I have. Is there a certain way to do this?
Hope this is suffice info - sorry I could not provide more code snippets, as I am coding for a company (hence the "our").
One way to do this is to expose your code in a function so you can call it from the views.
Coffeescript file:
window.SomeNamespace ||= {}
window.SomeNamespace.scroll_to_error = ->
document.getElementById('submit').onclick = ->
if $('.alert-danger:visible:first').length > 0
$('html, body').animate { scrollTop: $('.alert-danger:visible:first').offset().top - 220 }, '1000'
View file: (say you are using erb)
<script>
$(function(){
window.SomeNamespace.scroll_to_error()
});
</script>
A better way to do this, however, is to separate view code and javascript code. elementaljs offers a small Javascript behaviors library for Rails, which I like a lot. You can "attach" a javascript behavior to a specific html element via data attribute, and the library does the above work for you.
Try something like this?
scrollToError = ->
$('*[data-scroll-to-error="true"]').click ->
alertElement = $('.alert-danger:visible:first')
if alertElement.length > 0
$('html, body').animate {
scrollTop: alertElement.offset().top - 220
}, '1000'
# Run with turbolinks.
$(document).on 'page:change', scrollToError
and a submit button with the data-scroll-to-error attribute set.
<input type="submit" data-scroll-to-error="true" />
I find this statement of yours confusing:
I have the file included in our application.rb file in the config
folder, so that it can be called in any HTML file in the views section
Assuming this is a standard Rails project, you should have included the file containing the Coffeescript code in assets/javascripts/application.js (the manifest file), and since application.js is the JS file required in the layout (isn't it? :-/), this code would execute on all of your views. I don't see how config/application.rb is involved here.
Update: Adding more information.
If you're unsure about how to application.js file (a manifest file) is used, please take the time to go through Rails' official documentation about it (especially the section about manifests).
http://guides.rubyonrails.org/asset_pipeline.html#manifest-files-and-directives
In your case, you'll want to modify application.js to include:
...
//= require error
...
Basically, you list all the asset files that you want loaded along with all pages that use a layout (probably application.html.erb), and then mention only the manifest file the layout. That way, Rails can concatenate and minify to generate a single asset file. The default application. manifest files are automatically precompiled - so you don't need to manually add new assets to the precompile list.
The reason why I wrote the selector as *[data-scroll-to-error="true"] was personal preference - the code reads better to me that way. If you're sure that the ID submit is (and will be in future) applied only to elements that require scrolling to error, then it's fine to change the first selector to $('#submit').click ->
Related
It is a standard in my company to place the JavaScript file that is uniquely associated with one page or view in the same folder where that page or view resides. Now that we are migrating to MVC we want to keep doing that. For example, if there is a file
~/Views/Customer/CustomerMgmt.cshtml
I need to be able to reference the following associated script file:
~/Views/Customer/CustomerMgmt.js
This js file has functionality that ONLY pertains to CustomerMgmt.cshtml the file will not be shared and thus no other consumer will make use of it. If I reference the JavaScript file using the following, it fails with a 404 error:
<script type = "text/JavaScript" src = "#Url.Content( "~/Views/Customer/CustomerMgmt.js" )">< /script>
The solutions I tried after researching include:
Add the JavaScript reference to BundlerConfig.RegisterBundles and use Scripts.Render in the CsHtml: I still get a 404 error.
Remove the filter that comes with ~/views/web.config:
< add name="BlockViewHandler" path="" verb="" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler" / >
This option allows me to reference ~/Views/Customer/CustomerMgmt.js but it is unsecure
Use blacklisting instead of whitelisting to only block *.CsHtml files: This would potentially allow other extensions that may be added in the future and not only *.Css and *.Js
Add the following to the handlers section:
< add name="JavaScriptHandler" path=".js" verb="" preCondition="integratedMode" type="System.Web.StaticFileHandler" / >
I still get a 404 error.
Add custom code to be able to reference the files I need (Helpers, jScript controllers, etc.)
I opted to use the following instructions that modify ~/views/web.config:
Add the following inside < system.webServer >< handlers>:
< add name="JavaScript" path="*.js" verb="GET,HEAD" type="System.Web.StaticFileHandler"/ >
< add name="CSS" path="*.css" verb="GET,HEAD" type="System.Web.StaticFileHandler"/ >
Add the following inside < system.web>< httpHandlers>:
< add path="*.js" verb="GET,HEAD" type="System.Web.StaticFileHandler" / >
< add path="*.css" verb="GET,HEAD" type="System.Web.StaticFileHandler" / >
< add path="* " verb="*" type="System.Web.HttpNotFoundHandler" / >
After I make this change I get a 500 error and the following (in brief):
An ASP.NET setting has been detected that does not apply in Integrated managed pipeline mode
And I’m presented with the following suggestions:
a. Migrate the configuration to the system.webServer/handlers section
b. Ignore this error by setting < system.webServer >, < validation validateIntegratedModeConfiguration="false" / >
c. Switch the application to a Classic mode application pool
After using the option “b” everything seems to work fine and I’m able to reference ~/Views/Customer/CustomerMgmt.js but I see several posts suggesting that this is not the best approach.
My questions are:
What is the best approach to follow for this case? I’m using MVC 5.2.3
Is the practice of putting a uniquely associated js file in the same folder as the view considered a bad practice? This file will ONLY be used by a particular view and we don’t want to clutter the ~/Scripts folder (or subfolders within).
I know that I can also create a new folder structure like “~/ViewScripts/…” and put the associated js files there. I will do that if necessary but, if possible I would like to keep adhering to the company standards and put the file, that is only being used by a particular view, together with that view.
Thanks to everyone in advance. Some of the items that I researched include:
MVC - Accessing css, image, js files in view folder
Placing js files in the views folder
How to reference javascript file in ASP.NET MVC 2 Views folder?
Where to put view-specific javascript files in an ASP.NET MVC application?
Cannot load resource MVC 4 JavaScript
Can I deploy a javascript file within MVC areas?
Serving static file on ASP.NET MVC (C#)
Serving static file on ASP.NET MVC (C#)
From this forum I managed to get some code that seems to do the job. I'm using it in a ViewComponent. I have a folder for the component and wanted to consolidate all the files in one folder, e.g.
\Component\
\Component\Component.cshtml
\Component\Component.js
\Component\ComponentModel.js
\Component\ComponentController.js
Assuming a consistent naming convention, I loaded it with this, which doesn't require any changes to web.config nor does it open up the client to loading other .js files.
<script>
#{ Html.RenderPartial(Regex.Replace(ViewContext.ExecutingFilePath, #"\.[^.]+$", #".js")); }
</script>
My application has many controllers and Rails creates a css and a js (coffee) file for each controller.
From what I understand Rails loads only the controller specific JS file (http://guides.rubyonrails.org/v3.2.8/asset_pipeline.html#how-to-use-the-asset-pipeline).
I have a feedback controller and in the view I load and external JS library with javascript_include_tag. In feedback.js I use this library. Works wonderful.
But now if I navigate to another controller I get a JS error saying that a function used in feedback.js is not found.
Why is Rails trying to load my feedback.js if im not in the feedback controller?
Rails asset pipeline does not load controller specific JS files for each controller. It loads all JS files required in application.js on every page. By convention it creates a JS file named for the resource when you create a new resource, to help you organize your JS code as it relates to each controller's views. But again, that JS code is loaded on every page that uses application.js, by default.
You can create controller specific JS files, but you have to define them in the precompile section of application.rb, and make sure they are not also included in application.js.
Example:
application.rb:
module YourApp
class Application < Rails::Application
# ...
config.assets.precompile += %w(feedback.js)
end
end
end
Then in your application.js you should remove the //= require feedback, which will keep feedback.js from loading by default. Finally, you have to manually include feedback.js in the views that need it with a javascript_include_tag, just like you are doing with your extra library.
I am doing this to load all JS files in app folder
ss.client.define('main', {
view: 'app.jade',
css: [
'libs/reset.css',
'app.styl'
],
code: [
'libs/jquery-2.1.0.min.js',
'libs/angular-1.2.10.min.js',
'libs/lodash-2.4.1.min.js',
'app'
],
tmpl: '*'
});
There are 3 files in app, 2 that came with the default project and 1 that I added. The first 2 work fine, but the one I added does not get executed!
The funny thing is that when there are errors in that file, I set them in the Chrome console, but no function gets executed or variable added to the page.
Any ideas why?
It will need access to the window variable/global-object.
Therefore you need to require it from your entry file. Typically this means having the lodash code file in your actual code (/client/code/[...]) directory. I.e. you wouldn't put it in your libs folder, but in your main app folder, although you can make another libs folder there.
This is what I've always had to do in order to require --for example-- bootstrapJS. It defies the organisation of the client side as they set it up, but it's the way things need to be done for stuff like this.
An alternative is to require it remotely (from CDN) from your main app.jade view file.
script(src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js")
$(function() {
$("#ll_search").submit(function() {
$.getScript(document.location.pathname + '/index.js.erb');
return false;
});
});
Line 3 in the snippet results in the following error:
ActionController::RoutingError (No route matches [GET] "/labs/index.js.erb").
I'm running Rails 3.1, which means jQuery is default. I triple-checked that a file "index.js.erb" actually exists. jQuery can't seem to see it though.
Remember that document.location.pathname will give you a root level path, which will (by default) in Rails take you to the public directory (unless a corresponding route exists). If you need to render this js file at request time (not recommended as far as performance) create an endpoint that renders the js and returns it appropriately with the right MIME type.
You probably don't want the raw ERB version (nothing on the client side would know what to do with it) and the script is probably in /assets in Rails 3.1 so try one of these:
$.getScript('/labs/index.js');
$.getScript('/assets/labs/index.js');
$.getScript('/assets/index.js');
I'm guessing a bit about where things are though.
I couldn't find any proper solution for automating Google App Engine CSS and Javascript minification.
If your JS/CSS files are to be used inside an HTML page, then a very good option is to have App Engine optimize your site automatically (minification, bundling, inlining and more) via the experimental "Page Speed" feature. You can turn this on by doing this:
Go to your projects' App Engine dashboard:
https://appengine.google.com/settings?&app_id=s~your_project_id
Click on "Application Settings" (bottom left under "Administration" section).
Scroll down to the "Performance" section and locate "PageSpeed Service:". Check the " Enable PageSpeed Service" checkbox and hit "Save".
This will add response filters that will automatically do stuff like combine and minify your JS, turn the minified bundle from a script reference to an inline script (to lesser the count of server requests) and more cool and effortless performance improvments. Read more about this feature here: https://developers.google.com/speed/pagespeed/service/faq
Write a deploy script that makes a copy of your app with minified JS and CSS, and then calls appcfg on it. You shouldn't be minifying it dynamically unless you're generating it dynamically.
I ended up creating this appengine script (uses memcache and slimit).
I found slimit to be the best minification script all around, but I'm thinking about using the one from Google instead.
http://ronreiterdotcom.wordpress.com/2011/08/30/automatic-javascript-minification-using-slimit-on-google-app-engine/
You can automate the process pretty efficiently by loading the content of your script into a string, processing it with jsmin and finally save and serve the result. Don't worry about performance, you only run jsmin when the application instance is created (certainty not for every request).
you can grab jsmin.py here.
lets say I have this function that reads the JS from the filesystem (uncompressed, debug version) and returns it's string content in the logger.py module:
class ScriptManager():
def get_javascript(self):
path_to_js = os.path.join(os.path.dirname(__file__), 'js/script.js')
return file(path_to_js,'rb').read()
process it over with jsmin. make sure to use proper caching headers. take this jsrendered sample module as an examp
js_compressed =
jsmin.jsmin(scripts.logger.ScriptManager().get_javascript())
JS_CACHE_FOR_DAYS = 30
class scriptjs(webapp2.RequestHandler):
def get(self):
self.response.headers['Content-Type'] = 'text/javascript'
expires_date = datetime.datetime.utcnow() + datetime.timedelta(JS_CACHE_FOR_DAYS)
expires_str = expires_date.strftime('%d %b %Y %H:%M:%S GMT')
self.response.headers.add_header('Expires', expires_str)
self.response.headers['Cache-Control'] = 'public'
self.response.cache_control.no_cache = None
self.response.out.write(js_compressed)
now return that from a dynamic contnet handler in your main.py:
app = webapp2.WSGIApplication([
('/scripts/script.js', jsrender.scriptjs),...
Nick's answer is the correct way to do it, but you could do it on the fly when the JS/CSS is requested - then set cache-control to public to cache the results upstream.
Slimmer - http://pypi.python.org/pypi/slimmer/
JSmin.py -
http://code.google.com/p/v8/source/browse/branches/bleeding_edge/tools/jsmin.py
Cache control header chatter -
http://groups.google.com/group/google-appengine/browse_thread/thread/be3fa7b5e170f378 and blog post - http://www.kyle-jensen.com/proxy-caching-on-google-appengine
You could try a build-time or a runtime solution (using maven plugin) provided by a tool called wro4j
Disclaimer: this is a project I'm working on.