Rails Image assets in AngularJS Directive and template - ruby-on-rails

I have Rails 4 Application with AngularJS using these gems:
gem 'angularjs-rails'
gem 'angular-rails-templates'
gem 'asset_sync'
It works great with a template like this:
<img ng-controller='LikePostController'
ng-dblclick='like(post);'
ng-src='{{post.photo.standard}}'
class='lazy post_photo pt_animate_heart'
id='post_{{post.id}}_image'
/>
The Image render correctly. However in my other js
petto.directive('ptAnimateHeart', ['Helper', function(Helper){
linkFunc = function(scope, element, attributes) {
$heartIcon = $("#heart_icon");
if($heartIcon.length == 0) {
$heartIcon = $("<img id='heart_icon' src='/assets/feed.icon.heart.png' alt='Like' /> ");
$(document.body).append($heartIcon);
}
element.on('dblclick', function(event){
$animateObj = $(this);
Helper.animateHeart($animateObj);
});
}
return {
restrict: 'C',
link: linkFunc
}
}])
I got 'assets/feed.icon.heart.png' was not found error from the browser console. I have feed.icon.heart.png located under app/assets/feed.icon.heart.png.
ps: Forget to mention I use assets sync gem to host assets in amazon s3. the image worked well in development but not in production.

Hardcoded asset links only work in development because in production the assets get precompiled. Which means, amongst other things, the filename changes from:
my_image.png
into something like this (it adds and unique md5-hash):
"my_image-231a680f23887d9dd70710ea5efd3c62.png"
Try this:
Change the javascript file extension to: yourjsfile.js.erb
And the link to:
$heartIcon = $("<img id='heart_icon' src='<%= image-url("feed.icon.heart.png") %>' alt='Like' /> ");
For better understanding The Asset Pipeline — Ruby on Rails Guides

You can define the following method somewhere in your helpers, e.g. in app/helpers/application_helper.rb:
def list_image_assets(dir_name)
path = File.expand_path("../../../app/assets/images/#{dir_name}", __FILE__)
full_paths = Dir.glob "#{path}/**.*"
assets_map = {}
full_paths.each do |p|
original_name = File.basename p
asset_path = asset_path p[p.index("#{dir_name}")..-1]
assets_map[original_name] = asset_path
end
assets_map.to_json
end
One can modify the method to work with any assets you wish, not just the ones located in subdirs of app/assets/images as in this example. The method will return a map with all the original asset names as keys and their 'compiled' names as values.
The map returned can be passed to any angular controller via ng-init (not generally recommended, but appropriate in this case):
<div ng-controller="NoController" ng-init="assets='<%=list_image_assets "images_dir_name"%>'"></div>
To make the assets really usable in angular, define a new $scope valiable in the controller:
$scope.$watch('assets', function(value) {
if (value) {
$scope.assets = JSON.parse(value);
}
});
Having this in the $scope, it's possible to use assets names as usual, in e.g. ng-src directives, and this won't brake after the precompile process.
<img ng-src={{::assets['my_image.png']}}/>

Just do the following:
app.run(function($rootScope,$location){
$rootScope.auth_url = "http://localhost:3000"
$rootScope.image_url = $rootScope.auth_url + "/uploads/user/image/"
});
In controller inject dependency for $rootScope
and in views
<img ng-src="{{user.image.url}}" width="100px" height="100px">
Note: It's working great in Rails API and it assumes that you've user object available so that it could specify the correct image in the /uploads/image/ directory

Related

Is it possible to use webpack dynamic imports with rails asset pipeline?

My problem is that after the bundles are emitted by webpack, rails assets pipeline adds hashes to the file names so it's impossible to dynamically import them
My current setup is
import modal from "./modal"
button.addEventListener("click", () => {
new modal().open();
}
While I want:
button.addEventListener("click", () => {
const modal = (await import(/* webpackChunkName: "modal" */ "./modal")).default;
new modal().open();
}
But in this case, a request would fire to get dist/modal.bundle.js, without rails hash, while its under dist/modal.bundle-g454545g4v45geg.js. The bundle names are resolved with asset_path(...) in the html,
But I don't think there is a way of telling js code what the hash is... Am I missing something? Is there any way to use dynamic imports with Rails Asset Pipeline?
My webpack.config.js:
module.exports = [
{
entry: {
main_layout: "./app/assets/layouts/main_layout.ts",
home_page: "./app/assets/pages/home/home.ts"
},
output: {
filename: "[name].bundle.js",
path: path.resolve(__dirname, "app", "assets", "dist")
},
...
I've never tried this but the docs are clear webpacker supports erb.
Run rails webpacker:install:erb
Change your appliaction.js pack to application.js.erb
You should now be able to call <%= asset_path("modal.bundle.js") %> within your pack.

How to define ruby code inside a haml file present in javascript folder

For my project, I am using Ruby on Rails and Angular and I have a haml file present in /project_name/app/assets/javascript folder. I want to call a ruby class from the haml file but unfortunately I am not able to do that.
.small-12.columns
.booking-time
%span.bold
- if ABC::D.data(:hide_hours_field) #ruby code
{{ item | timeformat }}
- else
{{ item | differentTimeFormat }}
Whenever I start the server, it's is showing it can't access the ruby class. I can easily access the ruby class in other haml files but not the one present in javascript folder. Can anyone help me here?
Disclaimer: I'm bad at Angular (and didn't even touch version 2). What follows is not a best practice or anything.
So, you need to configure your angular view with some knowledge from ruby side. You can't conditionally render it and you can't call ruby from angular controllers (obviously). I suggest smuggling data via window object.
In an appropriate view in your application, put this JS snippet.
<script type='text/javascript'>
window.jsEnv = {
hide_hours_field: <%= ABC::D.data[:hide_hours_field] %>
}
</script>
Then you can reference that via $window object in angular
Controller
function MyController($window) {
this.hideHours = function() {
return !!$window.jsEnv.hide_hours_field;
}
}
MyController.$inject = ['$window'];
angular.module('myApp').controller('MyController', MyController);
View
.small-12.columns(ng-controller='MyController as vm')
.booking-time
%span.bold(ng-if='vm.hideHours()')
{{ item | timeformat }}
%span.bold(ng-unless='vm.hideHours()')
{{ item | differentTimeFormat }}
I suggest you use angular constant instead of window then you can use it as service:
// index.html
<script type="text/javascript">
angular.module('myApp').constant('CURRENT_USER', #{current_user.to_json})
</script>
// controller
function ApiConsoleController($scope, CURRENT_USER) {
console.debug(CURRENT_USER)
}
Also, you can try to use angular-rails-templates
// application.rb
config.angular_templates.ignore_prefix = %w(
angular/components/
angular/shared/
angular/directives/
angular/validators/
)

Grails: render file from assets folder into gsp

I use require.js in a Grails project. There are a couple of single JavaScript files containing the require.js modules defined with define.
There is also a *.gsp file which generates the require.js config and the entry point starting with require() as there is some dynamic configs to be generated. It looks somehow like this:
<%# page contentType="application/javascript;charset=UTF-8" %>
require(['a', 'b'], function(a, b){
...
var a = ${controllerPropertyA};
...
some functions
...
});
In my layout I integrate require.js like this:
<script data-main='http://example.com/exampleController/dynamicGenerateMethod?domain=xyz.com' src='http://example.com/assets/require.js'></script>
All the modules a , b, and so on are asynchronously loaded by require.js. Now I would like to bundle them into a single file - I could use the require.js optimize tool but I prefer to use the assets-pipeline. This works as far as that I get all modules bundled into a single optimized-modules.js which is available on http://example.com/assets/optimized-modules.js.
The question: I would like to have the optimized JavaScript code in the dynamically rendered GSP file. So how can I inject the optimized-modules.js file into the GSP I'm dynamically rendering? I already thought about a tag defined in the tag library so that my *.gsp would look like
<%# page contentType="application/javascript;charset=UTF-8" %>
<g:renderFile file="/assets/optimized-modules.js" />
require(['a', 'b'], function(a, b){
...
var a = ${controllerPropertyA};
...
some functions
...
});
and the tag definition somehow like that:
def g:renderFile = { attrs, body ->
def filePath = attrs.file
if (!filePath) {
throwTagError("'file' attribute must be provided")
}
//out << filePath
out << request.servletContext.getResource(filePath).file
//out << grailsResourceLocator.findResourceForURI(filePath).file.text
//out << grailsApplication.mainContext.getResource(filePath).file.text
//out << Holders.getServletContext().getResource(filePath).getContent()
//IOUtils.copy(request.servletContext.getResourceAsStream(filePath), out);
}
But I can't get the content of the minified optimized-modules.js which was done by the assets-pipeline plugin on startup. Any thoughts on this?
Ok, I finally found it out by myself:
Instead of using the grailsResourceLocator I had to use the assetResourceLocator which is the way to go if you try to access assets resources.
My tag definition now looks like:
def renderFile = { attrs, body ->
def filePath = attrs.file
if (!filePath) {
throwTagError("'file' attribute must be provided")
}
ServletContextResource bar = (ServletContextResource)assetResourceLocator.findAssetForURI(filePath)
String fileAsPlainString = bar.getFile().getText("UTF-8")
out << fileAsPlainString
}
That way I can inject a compile assets javascript file into my GSP - perfect!

Jasmine, RequireJS and Rails

I'm starting to make the move over to requireJS for a project I'm building. I'm currently using jasminerice, rails 3.2 and the require-rails gem.
I've tried to implement http://ryantownsend.co.uk/post/31662285280/jasminerice-and-requirejs-rails-fix with little success, the specs don't run at all.
I am starting to think it maybe I might be better to use requirejs on it's own or maybe the jasmine gem?
I'm not sold on either jasminerice or require-rails gems, so does anyone have any advice on the best tools, and any tips on how to get it up and running/good tutorials?
Ok as I didn't get any response I managed to find a slightly hacky way of making it work.
If you create a file in your view folder jasminerice/spec/index.html.erb (or haml) and copy the html from the jasminerice gem. Replace the spec.js call with:
%script{"data-main"=>"/assets/#{#specenv}", src:"/assets/require.js"}
Then write your spec file like require template like so:
require.config {
paths:{
'jquery':'/assets/jquery'
'underscore': '/assets/underscore-min'
'sinon':'sinon-1.6.0'
'jasmine-sinon':'jasmine-sinon'
'my_js':'my_js'
'my_spec':'my_spec'
}
}
require ['sinon', 'jasmine-sinon', 'jquery', 'underscore', 'my_js', 'my_spec'], () ->
jasmine.getEnv().execute()
This will prevent jasminerice triggering the tests
jasmine.rice.autoExecute = false
Set up your tests with a beforeFilter similar to this(taken from http://kilon.org/blog/2012/08/testing-backbone-requirejs-applications-with-jasmine/)
describe "MySpec", ->
beforeEach ->
flag = false
#thing = ""
that = #
require ['myjs'], (Myjs) ->
flag = true
that.thing = new Myjs()
waitsFor ->
flag
it 'It should exsist', ->
expect(#thing).toBeDefined()
Hope that helps anyone with a similar issue and if anyone has a better solution please post! :)
I have the same setup, here's what I did (starting from the blog post mentioned in the original question):
1. Create a helper to load all spec files
In a file lib/jasminerice/spec_helper.rb, put the following code:
require "requirejs-rails"
module Jasminerice
module SpecHelper
include RequirejsHelper
def spec_files
Rails.application.assets.each_logical_path.select { |lp| lp =~ %r{^spec/.*\.js$} }
end
end
end
This will create a helper method spec_files which you can call in the Jasminerice runner view to automatically get all your specs, so you don't need to update the list of specs every time you add a new one.
2. Override default Jasminerice index view
Create a view named app/views/jasminerice/spec/index.html.erb with the following:
<!doctype html>
<head>
<title>Jasmine Spec Runner</title>
<%= stylesheet_link_tag "jasmine", "spec" %>
<%= requirejs_include_tag 'application' %>
<%= javascript_include_tag "jasminerice", "spec", :debug => true %>
<script>
jasmine.rice.autoExecute = false;
require([<%= spec_files.map { |f| "'#{f.sub(/\.js$/,'')}'" }.join(',').html_safe %>],
function() { jasmine.getEnv().execute() },
function(err) {
var failedId = err.requireModules && err.requireModules[0];
requirejs.undef(failedId);
define(failedId, function() { return function() { console.debug(failedId + ': ' + err); null }; });
require([ failedId ], function() {} );
});
</script>
<%= csrf_meta_tags %>
</head>
<body>
</body>
</html>
This will require all the specs before running Jasmine (with jasmine.getEnv().execute()). I have an ugly hack in there to take the array of spec paths and generate an array of module names in quotes to pass to require.
I've also included an error callback in case there's a problem loading a module -- if you don't do this, your specs will hang when a module load fails. That's especially a problem when you're running them on the command line through guard-jasmine, which is what I do.
Unfortunately I haven't found a very good way to handle such errors -- here I write some info to console.debug and then required the failed module, returning an anonymous function in its place. This allows the specs to run but produces unpredictable results (which is better than no results). I've been struggling to find a better way to deal with this situation, suggestions would be much appreciated.
3. Write some specs
My Jasmine specs take the form:
define (require) ->
MyModule = require 'my-module'
# any other dependencies needed to test
describe 'MyModule', ->
it 'exists', ->
expect(MyModule).toBeDefined()
etc. Note that all my testing dependencies (jasmine, sinon, jasmine-sinon, etc.) I load outside of require, in spec.js.coffee:
#=require sinon
#=require jasmine-sinon
#=require_tree ./helpers/
I put any other helper functions I need in the helpers directory.
4. Bonus
One other tip: if you have problems because your browser won't reload modules even when they change, I use a trick of adding a dummy argument with a timestamp so that the browser will always see a new file and correctly load it.
I created this function in ApplicationController which I load in a before filter:
before_filter :set_requirejs_config
def set_requirejs_config
opts = { :urlArgs => "bust=#{Time.now.to_i}" }) if Rails.env == "development"
Requirejs::Rails::Engine.config.requirejs.run_config.merge!(opts)
end
This adds a query param bust=... to the end of each module name if we're in development mode, so that we always reload modules and get the most up-to-date version. Somewhere there's a post on SO explaining how to do this in RequireJS, but to get it to work with requirejs-rails you have to put it into ApplicationController (and not config/requirejs.yml) so that it is loaded every time you load the page.
Hope that might provide some hints to anyone else using this configuration!

How to Use 'Ajax Upload' with Ruby on Rails?

I'm using this Script http://valums.com/ajax-upload/ to get Ajax upload working with Ruby on Rails
but when i try uploading a file on my controller action I only get this:
Parameters: {"qqfile"=>"Foo.png"}
{"qqfile"=>"Foo.png", "action"=>"ul_file", "controller"=>"upload_files"}
but i cant use this as a file and do things as
thefile = params[:qqfile]
foo = thefile.original_filename.slice(thefile.original_filename.rindex("."), thefile.original_filename.length).downcase
what do i need to do to get this script working with Ruby on Rails?
Thanks.
///////// Updated
This is how the JS looks Like
$(document).ready(function(){
var uploader = new qq.FileUploader({
// pass the dom node (ex. $(selector)[0] for jQuery users)
element: $('#file-uploader')[0],
// path to server-side upload script
action: '/NzzT/upload_files/ul_file'
});
});
my solution posted in this comment
I write QqFile for easy uploader. With paperclip example
Here's a good solution using CarrierWave:
https://github.com/jnicklas/carrierwave/wiki/How-to%3A-Upload-from-a-string-in-Rails-3
https://groups.google.com/forum/#!topic/carrierwave/KBn2WmrwRAs
The syntax looks like this and works like a charm:
file = CarrierwaveStringIO.new(params[:qqfile], request.raw_post)

Resources