Angular 4 with haml-loader - ruby-on-rails

I am trying to use haml with templates in my Rails 5.1 and Angular 4 project.
I have the haml template working but when I tried to implement Material 2 for Angular, it uses attributes that haml does not add to the DOM.
For example, I am trying to implement a sidenav and I created a component with the following template:
-#navbar.component.html.haml
%md-sidenav-container.example-container
%md-sidenav.example-sidenav{ '#sidenav' => true }
Jolly good!
.example-sidenav-content
%button{'md-button' => true, '(click)' => 'sidenav.open()'}
Button
And here is my component:
//navbar.component.ts
import { Component } from '#angular/core';
import navbar from './navbar.html.haml';
#Component({
selector: 'navbar',
template: navbar
})
export class NavbarComponent {}
'#sidvenav' attribute and 'md-button' attribute are not rendered to the DOM.
can I not use haml with Angular 4? should I not use haml with Angular 4? or am I just doing it wrong?
Under config/webpack/loaders I have a file called haml.js
module.exports = {
test: /\.html\.haml$/,
loader: 'haml-haml-loader'
}
And under javascript/app I have a file called haml.d.ts
declare module "*.haml" {
const content: string
export default content
}
This is what allows me to use template for haml. There are similar files for html and I render them as strings and they get compiled as needed by html-loader or haml-haml-loader.
Any help would be appreciated.

Related

Rails 7 Importmap Bootstrap

So, I've set up my rails 7 application by doing the following.
rails new .
To add bootstrap I've implemented it with importmap (no webpacker) as following
./bin/importmap pin bootstrap
which loaded these lines (I added the preload: true)
pin 'bootstrap', to: https://ga.jspm.io/npm:bootstrap#5.1.3/dist/js/bootstrap.esm.js', preload: true
pin '#popperjs/core', to: 'https://ga.jspm.io/npm:#popperjs/core#2.11.2/lib/index.js', preload: true
then on my application.js, I added
import "bootstrap"
import "#popperjs/core"
and I was able to use the toast element by doing as follow
# toast_controller.js
import { Controller } from "#hotwired/stimulus"
import * as bootstrap from 'bootstrap'
// Connects to data-controller="toast"
export default class extends Controller {
connect() {
const toast = new bootstrap.Toast(this.element, {
delay: 5000,
animation: true,
autohide: true
})
toast.show()
}
}
and it was working well, But I started facing issues with bootstrap when trying to use the tooltip on my menu
# layout_controller.js
import { Controller } from "#hotwired/stimulus"
import * as bootstrap from 'bootstrap'
// Connects to data-controller="toast"
export default class extends Controller {
connect() {
[].slice.call(document.querySelectorAll('[data-bs-togle-secondary="tooltip"]'))
.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl, {placement: "right"})
})
}
}
the reason for it is because popperjs uses process.env.NODE_ENV which doesn't exist since I didn't set up webpacker
so I had to do something ugly on my application layout and add it like this
<script>
const process = {}
process.env = {}
process.env.NODE_ENV = "<%= Rails.env %>"
</script>
Is there a better approach/fix for this kind of issue?
At the moment of writing this 11/04/2022 there is not a clear solution for now and your mentioned approach with defining in const process in tag works, one issue with that is that with importmaps+turbolink approach it will rise "Uncaught SyntaxError: redeclaration of const process".
Probably for now it's best to just follow this thread https://github.com/rails/importmap-rails/issues/65 as this issue is mentioned in the comments there. One of the quick fixes mentioned there is similar to yours: https://github.com/rails/importmap-rails/issues/65#issuecomment-999939960. With a combined solution of yours and the one in the comments, it seems that this works best for now and there is no redeclaration error.
<script>window.process = { env: { NODE_ENV: "#{Rails.env}" } }</script>
If the solution is introduced for this issue either on importmaps or popperjs side, then please update this comment or introduce a new answer.

Material UI Themes and Hyperstack

Material UI (React) uses a theaming solution where the theme object is created in JS then passed into the top level component. Creating the theme object in Opal can be complicated as the Material component expects a JS function to be passed in which actually creates the theme on the fly.
Has anyone got an example of this working well?
After some experimentation, I got this working well by mixing JS and Opal code so here is the solution in case anyone else comes up with this. (There may be a better 'more Opal' solution, so if there is please do post an alternative answer, but the one below does work well.
First import the JS libraries with webpack:
import { MuiThemeProvider, createMuiTheme } from '#material-ui/core/styles';
import indigo from '#material-ui/core/colors/indigo';
import pink from '#material-ui/core/colors/pink';
import red from '#material-ui/core/colors/red';
global.createMuiTheme = createMuiTheme;
global.MuiThemeProvider = MuiThemeProvider;
global.indigo = indigo;
global.pink = pink;
global.red = red;
Add the following to your Javascript assets:
// app/assets/javascripts/my_theme.js
const my_theme = createMuiTheme(
{ palette: {
primary: { main: pink[500] },
secondary: { main: indigo[500] }
}
});
Then provide the theme in your top-level Component code:
class MyRouter < HyperComponent
include Hyperstack::Router
render(DIV) do
MuiThemeProvider(theme: `my_theme` ) do
CssBaseline do
...
end
end
end
end

react-rails asset pipeline image path

I'm using the react-rails gem, and I'm having trouble figuring out how to load images from the asset pipeline.
I handle this currently by using the .js.jsx.erb extension, but as far as I know this is unconventional. (am I correct?)
Sometimes I just use an empty div and and set the div's background-image property as the image I intend to use.
What is the best way to go about loading images to react components when using react-rails?
There are three ways to import image...
1) If you are direct using jsx.erb or js.erb file...
var image_path = "<%= asset_path(my_image.png) %>"
OR
render: function() {
return (
<img src={"<%= asset_url('path/to/image.png') %>"} />
)
}
2) Passing as props Through .erb file to .js.erb or .jsx.erb file
in your .erb file
render_component('Component', img_src: image_url('the_path'))
in your .js.erb or .jsx.erb file
render: function() {
return (
<img src={this.props.img_src} />
)
}
3) Recommended: Use .js and .jsx file and render it using view file .html.erb
In view file example.html.erb
<%= react_component("Example", props: {}, prerender: false) %>
In .jsx file Example.jsx
import React, { Component } from "react";
import Image from "<IMAGE_PATH>/image.png";
export default class Example extends Component {
constructor(props, _railsContext) {
super(props);
}
render(){
return(
<div>
<img src={Image} alt="example" />
</div>
)
}
}
You need to register Example component. In your app > javascript > packs > application.js
import ReactOnRails from "react-on-rails";
import Exmaple from "<COMPONENT_PATH>/Exmaple";
ReactOnRails.register({
Exmaple
});
Source: github

I can't register components using React_on_Rails gem

The registration was working fine with the default HelloWorld app, but once I deleted the folder and created my own, it stopped working.
I made a new folder under app/bundles called posts with a startup folder.
In the startup folder, I'm registering the components like so:
import ReactOnRails from 'react-on-rails';
import PostContainer from '../containers/PostContainer';
ReactOnRails.register({
PostContainer
});
The PostContainer.jsx file is in the containers folder and it looks like this:
import React, { PropTypes, Component } from 'react';
export default class PostsContainer extends React.Component {
render() {
return (
<div>
<Header />
<PostList posts={this.props.posts} />
</div>
)
}
}
My webpack.config.js file looks like this:
entry: [
'es5-shim/es5-shim',
'es5-shim/es5-sham',
'babel-polyfill',
'./app/bundles/HelloWorld/startup/registration',
],
I tried changing the HelloWorld to posts or Posts, but it did not work.
Am I supposed to have a file called webpack.configure.build.js? Or is the webpack.config.js the file I need to edit?
Any help would be appreciated!
Did you add the component to the erb view file with the associated props passed in? You would need to add the props within your Posts controller or whichever controller is rendering your Posts view. I'm pretty sure you would then need to add a line in your webpack config:
config: {
'webpack-bundle': [
'...',
'./app/bundles/Posts/startup/registration'
]
}
Then in your view:
<%= react_component("PostsContainer", props: #posts_props, prerender: false) %>
Also make sure in your layout file you have:
<%= javascript_pack_tag 'webpack-bundle' %>
Note: PropTypes should be imported from the prop-types package now. The react team plucked that package out of the react core as of recent.

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/
)

Resources