React-Rails: Use components with translations I18n - ruby-on-rails-4

I have added to my project react-rails gem and I want to use for translated components.
I cannot put in the precompiled assets erb templates, but still I am trying to create components, make them available in all the project and then use them in some partial with some translation.
Working Scenario
# app/view/example/_react_component.coffee.erb
DOM = React.DOM
FormInput = React.createClass
displayName: "FormInput"
render: ->
DOM.div
className: 'row control-group'
DOM.div
className: 'form-group col-xs-12 floating-label-form-group controls'
DOM.label
htmlFor: #props.id
#props.label
DOM.input
id: #props.id
className: 'form-control'
placeholder: #props.placeholder
type: #props.type
DOM.p
className: 'help-block text-danger'
formInput = React.createFactory(FormInput)
window.ValidationFormInput = React.createClass
displayName: "ValidationFormInput"
getInitialState: ->
{ }
render: ->
formInput
id: "<%= t('validation_form.id') %>"
label: "<%= t('validation_form.label') %>"
placeholder: "<%= t('validation_form.placeholder') %>"
type: 'text'
validationFormInput = React.createFactory(ValidationFormInput)
# app/view/example/index.html.erb
<%= react_component('ValidationFormInput', {}, {class: "container"}) %>
Desired Scenario (not working)
# app/assets/javascripts/components/form_input.coffee
DOM = React.DOM
FormInput = React.createClass
displayName: "FormInput"
render: ->
DOM.div
className: 'row control-group'
DOM.div
className: 'form-group col-xs-12 floating-label-form-group controls'
DOM.label
htmlFor: #props.id
#props.label
DOM.input
id: #props.id
className: 'form-control'
placeholder: #props.placeholder
type: #props.type
DOM.p
className: 'help-block text-danger'
formInput = React.createFactory(FormInput)
# app/view/example/_react_component.coffee.erb
window.ValidationFormInput = React.createClass
displayName: "ValidationFormInput"
getInitialState: ->
{ }
render: ->
formInput
id: "<%= t('validation_form.id') %>"
label: "<%= t('validation_form.label') %>"
placeholder: "<%= t('validation_form.placeholder') %>"
type: 'text'
validationFormInput = React.createFactory(ValidationFormInput)
# app/view/example/index.html.erb
<%= react_component('ValidationFormInput', {}, {class: "container"}) %>
I guess that the issue is related to the scope of the definition of my component, but I cannot figure out how to make the component available for any partial.
Thank you in advance
Edit
In order to make the translations available, I found the gem I18n-js. After installing, I can easily run a rake task to create a js version of my config/locales/* translations

Excellent question.
There are a few ways to do this.
1- Usually, this is not just a question about how to pass data from Rails to React but rather how to generally pass data to Javascript. You can store the data in a meta in the header and access it from Javascript. This way you can still have your JS compressed and fast. (Instead of js.erb etc)
2- Passing all the translations to the react component. Basically, you can pass arguments to the react component, one of which is the translations. If it's a few translations, it's fine but if your list grows, the load would be heavy on your page.
3- Make your own Javascript translator. Here's a CoffeeScript example that I have created; make sure to add it in your assets' list before the other files.
In my code, I'm pulling the locale from meta (as you can see in the code). Feel free to edit this.
class Translator
#en = {
internet_connection_lost: "Your internet connection has been lost"
attempting_to_reconnect: "Attempting to reconnect!"
failed_to_reconnect: "Failed to reconnect..."
connection_success: "Connected"
reconnecting: "Reconnecting..."
bid_received: "Bid received. New balance $$bid_balance$$"
}
#ar = {
internet_connection_lost: "لقد فقدت الاتصال بالإنترنت"
attempting_to_reconnect: "نحاول إعادة الاتصال!"
failed_to_reconnect: "لم تنجح إعادة الاتصال بالشبكة..."
connection_success: "متصل بشبكة الإنترنت"
reconnecting: "إعادة الاتصال جارية..."
bid_received: "تم تلقي العرض. رصيد جديد $$bid_balance$$"
}
#get_translations: (locale) ->
switch (locale)
when 'en'
#en
when 'ar'
#ar
#translate: (val, interpolation) ->
# get locale from meta
locale = $("meta[name='locale']").attr("content") || "en"
translation = Translator.get_translations(locale)[val]
if interpolation
console.log "#{JSON.stringify(interpolation)}"
for k,v of interpolation
console.log "#{translation} : #{k} : #{v}"
translation = translation.replace(k, v)
return translation
window.Translator = Translator
And this is how you can use the Translator
message = Translator.translate(
"bid_received", { "$$bid_balance$$": 10 }
)

Related

Inject data into Sass variables in Ember application

I'm working on an application that renders many, separate "sites" as subdirectories- e.g. /client/1, /client/2, etc. For each of these, two color values can be specified in the admin portion of the application.
I'd like to know if there's a method to inject the values, which were initially posted to and then retrieved from the back-end API by Ember, into a SCSS file for preprocessing?
I've found no solution thus far.
In our Ember/Rails application, we are generating CSS files for each client based on some settings in the database. For example, our Tenant model has two fields:
{
primary_color: 'ff3300',
secondary_color: '00ff00'
}
We expose routes
scope '/stylesheets', format: 'css' do
get 'tenant/:tenant_id', to: 'stylesheets#tenant_css'
end
And our controller looks something like this:
class StylesheetsController < ApplicationController
layout nil
rescue_from 'ActiveRecord::RecordNotFound' do
render nothing: true, status: 404
end
def tenant_css
# fetch model
tenant = Tenant.find(params[:tenant_id])
# cache the css under a unique key for tenant
cache_key = "tenant_css_#{tenant.id}"
# fetch the cache
css = Rails.cache.fetch(cache_key) do
# pass sass "params"
render_css_for 'tenant', {
primary_color: tenant.primary_color,
secondary_color: tenant.secondary_color
}
end
render_as_css css
end
protected
# our renderer, could also add a custom one, but simple enough here
def render_as_css(css)
render text: css, content_type: 'text/css'
end
# looks for a template in views/stylesheets/_#{template}.css.erb
def render_css_for(template, params = {})
# load the template, parse ERB w params
scss = render_to_string partial: template, locals: { params: params }
load_paths = [Rails.root.join('app/assets/stylesheets')]
# parse the rendered template via Saas
Sass::Engine.new(scss, syntax: :scss, load_paths: load_paths).render
end
end
This way, you can link to /stylesheets/tenant/1.css which will render the CSS for the tenant using the Sass engine.
In this case, in views/stylesheets/_tenant.css.erb, you'd have something like this (it's an ERB file but you can use Sass in there now):
#import "bootstrap-buttons";
<% if params[:primary_color].present? %>
$primary-color: <%= params[:primary_color] %>;
h1, h2, h3, h4, h5, h6 {
color: $primary-color;
}
<% end %>
<% if params[:secondary_color].present? %>
$secondary-color: <%= params[:secondary_color] %>;
a {
color: $secondary-color;
&:hover {
color: darken($secondary-color, 10%);
}
}
<% end %>
You'll note that I can now use #import to import anything in your stylesheet path for the Sass engine (in this case, I can utilize some helpers from Bootstrap Sass lib).
You'll want to have some sort of cache cleaner to wipe the cache when your model backing the CSS is updated:
class Tenant < ActiveRecord::Base
after_update do
Rails.cache.delete("tenant_css_#{id}")
end
end
So that's the Rails side in a nutshell.
In Ember, my guess is you'll want to load the stylesheet based on an ID, so that stylesheet cannot be hard-coded into "index.html". Ember CSS Routes addon might serve you well, but I found that it just appends <link> to the header, so if you need to swap CSS stylesheets at any time, this won't work. I got around this in a route like so:
afterModel(model, transition) {
// dynamically form the URL here
const url = "/stylesheets/tenant/1";
// build link object
const $link = $('<link>', { rel: 'stylesheet', href: url, id: 'tenant-styles' });
// resolve the promise once the stylesheet loads
const promise = new RSVP.Promise((resolve, reject) => {
$link.on('load', () => {
$link.appendTo('head');
resolve();
}).on('error', () => {
// resolve anyway, no stylesheet in this case
resolve();
});
});
return promise;
},
// remove the link when exiting
resetController(controller, isExiting, transition) {
this._super(...arguments);
if (isExiting) {
$('#tenant-styles').remove();
}
}
You could also add a blank element in the <head> and then use Ember Wormhole to format a <link> tag and render into the "wormhole".
Edit
You could also look into rendering Sass directly in the client application. For something as simple as two colors, this wouldn't have much of performance impact, especially if you used a service worker or similar to cache the results.

Using hapi and handlebars, with the default layout support from hapi, can alternative layout be selected from with a page?

Another answer and the hapi api indicate that hapi has built in support for layouts when using handles bars. It seems however, to only allow one layout defined in the config as an alternative to the default 'layout.html' layout.
In that answer it is shown how to use handlebars-layouts to use the handlebars layout support to do this within a page like so:
{{#extend "layout2"}}{{/extend}}
While I am able to use handlebars-layouts, I would like to just use as much built in stuff which hapi provides.
Is it possible then to have more than the default layout and select that layout in a page template? Maybe like this:
{{!< layout/layout2}}
You can override the views manager configuration in reply.view():
options - optional object used to override the server's views manager configuration for this response. Cannot override isCached, partialsPath, or helpersPath which are only loaded at initialization.
Here is an example:
index.js:
var Handlebars = require('handlebars');
var Hapi = require('hapi');
var Path = require('path');
var server = new Hapi.Server()
server.connection({
host: '127.0.0.1',
port: 8000
});
server.views({
engines: {
html: Handlebars.create()
},
path: Path.join(__dirname, 'views'),
layoutPath: Path.join(__dirname, 'views/layouts'),
layout: 'default'
});
server.route({
method: 'GET',
path: '/default',
handler: function (request, reply) {
reply.view('item', { title: 'Item Title', body: 'Item Body' });
}
});
server.route({
method: 'GET',
path: '/custom',
handler: function (request, reply) {
reply.view('item', { title: 'Item Title', body: 'Item Body' }, { layout: 'custom' });
}
});
server.start();
views/layouts/custom.html:
<html>
<body>
<h1>Custom Layout</h1>
{{{content}}}
</body>
</html>
views/layouts/default.html:
<html>
<body>
<h1>Default Layout</h1>
{{{content}}}
</body>
</html>
views/item.html:
{{body}}
When you visit http://localhost:8000/default, it will use default.html. However http://localhost:8000/custom will use custom.html.

Ember button not staying disabled

I have an ember form where the button is supposed to be disabled for the creation of the object. The first two forms I did this on worked fine, this one is giving me issues. The button is disabled but then renables before the controller action is done.
Goal: Prevent double click on the Create button from creating a duplicate object
I attempted to do this by disabling the button after the first click
View (just the button piece)
<button type="submit" class="btn btn-primary" type="submit" id="submit-attribute"{{action 'submit' newAttribute}} {{bind-attr disabled="isProcessing"}}>Create</button>
Controller (in CoffeeScript)
App.SpecificationNewAttributeController = Ember.ObjectController.extend
isProcessing: false
newAttribute: Ember.Object.create({
name: ""
datatype: ""
group: ""
})
datatypes: ['number', 'range', 'list', 'boolean', 'date']
actions:
submit: (content) ->
#set "isProcessing", true
specification = controller.get('content')
specId = specification.get('identifier')
revision = specification.get('revision')
specificationAttribute = #store.createRecord "SpecificationAttribute",
name: content.name
group: content.group
datatype: content.datatype
specification: specification
specificationIdentifier: specId
revision: revision
specificationAttribute.save().then ((specificationAttribute) =>
attributeId = specificationAttribute.get("id")
specification.get('specificationAttributes').addObject specificationAttribute
specification.save().then ((specification) =>
specification.reload()
groups = specification.get('specificationGroups')
group = groups.where(display_name: content.group)[0]
controller.transitionToRoute('specification', specification).then ->
$.growl.notice title: "Specification", message: "New Attribute Added"
)
#set "isProcessing", false
)
Answering this myself. It is a lame mistake but maybe it will help someone. I needed to move the #set "isProcessing", false into the inner save().then block:
actions:
submit: (content) ->
#set "isProcessing", true
controller = #controllerFor('specification')
specification = controller.get('content')
specId = specification.get('identifier')
revision = specification.get('revision')
specificationAttribute = #store.createRecord "SpecificationAttribute",
name: content.name
group: content.group
datatype: content.datatype
specification: specification
specificationIdentifier: specId
revision: revision
specificationAttribute.save().then ((specificationAttribute) =>
attributeId = specificationAttribute.get("id")
specification.get('specificationAttributes').addObject specificationAttribute
specification.save().then ((specification) =>
specification.reload()
groups = specification.get('specificationGroups')
group = groups.where(display_name: content.group)[0]
#set "isProcessing", false
controller.transitionToRoute('specification', specification).then ->
$.growl.notice title: "Specification", message: "New Attribute Added"
)
)

Full Calendar and auto Filtering Events Rails 4

I am building a calendar application which requires certain events to display on the calendar based on the location that is being viewed. I have full calendar working in which it displays ALL the events in the database. I am trying to achieve an auto filter to only show the events that pertain to the location being viewed.
Current Setup (My Events Model is called "Campaigns" to align with my application)
Campaign Controller
def index
#campaigns = Campaign.all
#campaigns = #campaigns.after(params['start']) if (params['start'])
#campaigns = #campaigns.before(params['end']) if (params['end'])
respond_to do |format|
format.html # index.html.erb
format.json { render json: #campaigns }
end
end
Campaign Model
belongs_to :location
scope :before, lambda {|end_time| {:conditions => ["ends_at < ?", Campaign.format_date(end_time)] }}
scope :after, lambda {|start_time| {:conditions => ["starts_at > ?", Campaign.format_date(start_time)] }}
# need to override the json view to return what full_calendar is expecting.
# http://arshaw.com/fullcalendar/docs/event_data/Event_Object/
def as_json(options = {})
{
:id => self.id,
:title => self.name,
:description => "",
:start => starts_at.rfc822,
:end => ends_at.rfc822,
:allDay => false,
:recurring => false,
:url => Rails.application.routes.url_helpers.campaign_path(friendly_id)
}
end
def self.format_date(date_time)
Time.at(date_time.to_i).to_formatted_s(:db)
end
Script inside "Location" show.html.erb
$(document).ready(function() {
var date = new Date();
var d = date.getDate();
var m = date.getMonth();
var y = date.getFullYear();
$('#calendar').fullCalendar({
editable: true,
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
defaultView: 'month',
loading: function(bool){
if (bool)
$('#loading').show();
else
$('#loading').hide();
},
// a future calendar might have many sources.
eventSources: [{
url: '/campaigns',
data: { <%= #locations %> }, #added but not filtering as I had hoped
color: 'blue',
textColor: 'white',
ignoreTimezone: false
}],
timeFormat: 'h:mm t{ - h:mm t} ',
dragOpacity: "0.5",
});
});
Currently I am able to achieve in displaying the campaigns that belong to the given location on the display side in the location show with:
<strong>Campaigns:</strong>
<%= render partial: #campaigns %>
</p>
And then in the location controller
def show
#campaigns = #location.campaigns
end
I have tried for hours to figure this out with no luck in getting the same result to the calendar. Can someone please help me in figuring out what is required to filter the "Campaigns" that pertain to the viewing location??
Thanks!
Have you tried this ?
eventSources: [{
url: '/campaigns',
data: { 'location': <%= #locations %> }, <- try this like so
color: 'blue',
textColor: 'white',
ignoreTimezone: false
}],

How to create a multi-use partial "template" in AngularJS?

I have a large list of items. Each item has it's own details.
In my main view/partial, I simply display a large list list of the item names.
When the user clicks on an item, I want the page to go to a partial which works as a "template", displaying information based on which list item is clicked, and hence possibly what the URL looks like. E.g. /listItem1/
This diagram below hopefully sums up what I want to achieve pretty clearly.
How can I do this?
Right now, I have a pretty standard set up in which I have all the information for each list item in an array of object literals, which is contained in a controller injected into the main app module. Like so:
var app = angular.module('app', [/*nodependencies*/]);
var controllers = {};
app.controller(controllers);
controllers.listController = function ($scope){
$scope.list = [
{name: 'List Item 1 Name', detail1: 'blahblah1', detail2: 'blahblah2'},
{name: 'List Item 2 Name', detail1: 'blahblah1', detail2: 'blahblah2'},
{name: 'List Item 3 Name', detail1: 'blahblah1', detail2: 'blahblah2'}
..... and so on
I know how to create basic views/partials as well. But what would be my next steps?
You can do what you want, using the built-in router which ships with AngularJS.
var app = angular.module('app', [/*nodependencies*/])
.config(function($routeProvider) {
$routeProvider
.when('/:itemId', {
templateUrl: '/path/to/partial',
controller : function($scope, $routeParams) {
$scope.item = $routeParams.itemId;
}
})
});
Basically, what the above means, is that if you browse to pdf/item/1
Then you will have access in your controller to $routeParams.itemId which will be equal to 1. You can then do whatever logic is necessary with this information on your partial to show the information you want.
Hope this helps.
Update
Please look at the controller, this is how you would get the param you passed via the URL, you would then do whatever it is you need to do with that param in the controller, and pass the data back to the view.
You can create a small directive that will use the multi-use partial to display each item on the list
Take a look at this working example (http://plnkr.co/edit/0jNVxRg6g3p8uxpustzz?p=preview)
var myApp = angular.module('myApp', []);
myApp.controller('listController', ['$scope', function ($scope) {
$scope.list = [
{
name: 'List Item 1 Name',
url: 'pdfs/item1.pdf',
detail: 'blahblah'
},
{
name: 'List Item 2 Name',
url: 'pdfs/item2.pdf',
detail: 'blahblah'
},
{
name: 'List Item 3 Name',
url: 'pdfs/item3.pdf',
detail: 'blahblah'
}
];
$scope.selectItem = function(item){
$scope.selected = item;
}
}]);
myApp.directive('listItem', [function () {
return {
restrict: 'A',
scope: {
item: '='
},
templateUrl: 'multiple-partial.html',
link: function (scope, element, iAttrs) {
}
};
}])