Using 'material-ui' with react-rails gem? - ruby-on-rails

I would like to use the material-ui component library in my Rails 4 app. I am currently using the react-rails gem to add .jsx compilation to the asset pipeline. I have added material-ui via rails-assets in the gemfile like so:
source 'https://rails-assets.org' do
gem 'rails-assets-material-ui'
end
And I have required the library in my application.js file like so:
//= require material-ui
However I keep getting the error "couldn't find file 'material-ui". How can I use the material-ui component library in my Rails app with the react-rails gem?

Ok so here is what I have working so far...
to the gemfile I have added:
gem 'react-rails'
gem "browserify-rails"
This gives us our react library, helpers and jsx compilation as well as the ability to use the require() sytax to require modules in our JS. browserify-rails also allows us to require npm modules in your Rails assets via a package.json file.
We can add the material-ui library to our app via this package.json file...
"dependencies" : {
"browserify": "~> 10.2.4",
"browserify-incremental": "^3.0.1",
"material-ui": "0.13.1"
},
The material ui library uses the require syntax to join all the different jsx component files together in the right order so this is why we need to use browserify-rails.
Next to keep our react code together I made a new directory in asset/javascripts called /react...
react
L /components
L react.js
L react-libraries.js
L theme.js
Now as part of 'material-ui' dependencies we have the react library. This means at the moment we have two copies of the library. One from the 'react-rails' gem and one from the 'material-ui' library dependencies from 'browserify-rails'. Lets use the one from 'material-ui' dependencies and leave the one from 'react-rails'.
in react.js:
//= require ./react-libraries
//= require react_ujs
//= require_tree ./components
Then in react-libraries.js
//React Library
React = require('react');
//Material Design Library
MaterialUi = require('material-ui/lib');
injectTapEventPlugin = require('react-tap-event-plugin'); injectTapEventPlugin();
//Material Design Library Custom Theme
MyRawTheme = require('./theme');
ThemeManager = require('material-ui/lib/styles/theme-manager');
Then we want to include all of this in the asset pipeline with...
//= require react/react
in application.js.
Now you can write your components in jsx files in /react/components/
You may also want to namespace your components with...
//Custom Components Namespace
Components = {};
in react-libraries.js
You can customise your theme in theme.js like this...
Colors = require('material-ui/lib/styles/colors');
ColorManipulator = require('material-ui/lib/utils/color-manipulator');
Spacing = require('material-ui/lib/styles/spacing');
module.exports = {
spacing: Spacing,
fontFamily: 'Roboto, sans-serif',
palette: {
primary1Color: Colors.grey300,
primary2Color: Colors.grey300,
primary3Color: Colors.lightBlack,
accent1Color: '#01A9F4',
accent2Color: Colors.grey100,
accent3Color: Colors.grey500,
textColor: Colors.darkBlack,
alternateTextColor: Colors.white,
canvasColor: Colors.white,
borderColor: Colors.grey300,
disabledColor: ColorManipulator.fade(Colors.darkBlack, 0.3)
}
};
Hope that helps :)

Related

How to migrate requiring of select2.js from sprockets to webpacker

I'm in the process of migrating a Rails 5.1.5 project, which uses CoffeeScript, from using sprockets to using webpacker. The project also uses select2.js. With sprockets, I did the following:
Install jquery-rails (jQuery is a dependency for select2).
Put select2.js code in vendor/assets/javscripts.
In application.js.coffee, add:
#= require select2
After that I was able to use select2 to in my application.js.coffee file:
$(document).on 'turbolinks:load' ->
$('select').select2
So far I've described the pretty standard way of including/using javascript libraries with sprockets.
However, with webpacker I can't make select2 work and I'm not sure why. I have two hypothesis:
I'm not importing/requiring it properly;
it doesn't find jQuery at some point of the load process;
So for jQuery, I did the following:
yarn add jquery
included in my environment.js:
const webpack = require('webpack');
environment.plugins.append('Provide', new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
}));
I've removed the jquery-rails gem, as well as #= require 'jquery' and tested that jquery works, so I guess I have correctly included it. However, I tried several ways of importing select2 (using es6 imports) and none of them worked. I tried:
import select2 from 'select2';
import select2 from 'select2/dist/js/select2'
import select2 from 'select2/dist/js/select2.js'
import 'select2/dist/js/select2.js'
I even tried to import it from the old vendor location by writing inside app/javascript/pack/application.js.coffee:
import '../../../vendor/assets/javascripts/select2'
I can confirm that the file contents is imported, as I put a console.log within the select2 file under node_modules/select2/dist/js/select.js and it did get printed. However, I also get the error TypeError: $(...).select2 is not a function when I execute $('select').select2() in the browser's dev tool console.
What am I missing or doing wrong?
P.S. I can provide much more info, but I didn't want my question to get any more bloated.
P.P.S. With my modest JS knowledge, I looked at the source code but couldn't recognize what exactly they are exporting and how am I supposed to import it.
I know this is an old post, but just in case someone else could benefit:
app/javascript/packs/application.js
...other requires...
require('select2')
window.Rails = Rails
import 'bootstrap'
...other imports...
import 'select2'
import 'select2/dist/css/select2.css'
$(document).on("turbolinks:load", () => {
$('.select2').select2()
})
My similar problem
I have stumble upon the same problem with another web component (Switchery):
I imported the component with yarn add switchery (no error)
I could import it correctly through WebPack with import 'switchery' (no error bundling the pack)
But when I was trying to use the Switchery object in the browser like they say in the doc:
var elem = document.querySelector('.js-switch');
var init = new Switchery(elem);
I would get the error: ReferenceError: Switchery is not defined
Note: I didn't want to install RequireJS as WebPack is supposed to do the same thing (and even better) nowadays.
My solution:
The problem was the webpack doesn't expose the pack-generated variables and classes in the global scope!
So to fix this, I needed to do two things:
Explicitly give a name to the imported class from Switchery:
import Switchery from 'switchery'
Use this Class only in the same JS file where the import was done
Testing hack:
If you want to try that out and "go back" to the mess that sprocket allowed, in the same file, you can expose "globally" the variable so you can use in from the browser:
import Switchery from 'switchery'
window.Switchery = Swicthery
now you can execute the switchery almost like in the example:
var init = new window.Switchery(elem);
Hope that helps...

React Error (Only a ReactOwner can have refs.)

I've just installed a fresh react into my rails project with 'React-rails' and added searchkit on top of it. But I'm getting some errors with it.
Uncaught Invariant Violation: addComponentAsRefTo(...): Only a ReactOwner can have refs. You might be adding a ref to a component that was not created inside a component's render method, or you have multiple copies of React loaded.
app/assets/javascripts/components/search.js.jsx
const host = "http://demo.searchkit.co/api/movies"
const sk = new Searchkit.SearchkitManager(host, {})
var Search = React.createClass({
render: function() {
const SearchkitProvider = Searchkit.SearchkitProvider;
const Searchbox = Searchkit.SearchBox;
const Hits = Searchkit.Hits;
const NoHits = Searchkit.NoHits;
const HitsStats = Searchkit.HitsStats;
const Layout = Searchkit.Layout;
const LayoutBody = Searchkit.LayoutBody;
const LayoutResults = Searchkit.LayoutResults;
const SearchBox = Searchkit.SearchBox;
const TopBar = Searchkit.TopBar;
const SideBar = Searchkit.SideBar;
const ActionBar = Searchkit.ActionBar;
const ActionBarRow = Searchkit.ActionBarRow;
const HierarchicalMenuFilter = Searchkit.HierarchicalMenuFilter;
const RefinementListFilter = Searchkit.RefinementListFilter;
const SelectedFilters = Searchkit.SelectedFilters;
const ResetFilters = Searchkit.ResetFilters;
const MovieHitsGridItem = Searchkit.MovieHitsGridItem;
return (<div>
<SearchkitProvider searchkit={sk}>
<Layout>
<TopBar>
<SearchBox autofocus={true} searchOnChange={true} prefixQueryFields={["actors^1","type^2","languages","title^10"]}/>
</TopBar>
<LayoutBody>
<SideBar>
<HierarchicalMenuFilter fields={["type.raw", "genres.raw"]} title="Categories" id="categories"/>
<RefinementListFilter id="actors" title="Actors" field="actors.raw" operator="AND" size={10}/>
</SideBar>
<LayoutResults>
<ActionBar>
<ActionBarRow>
<HitsStats/>
</ActionBarRow>
<ActionBarRow>
<SelectedFilters/>
<ResetFilters/>
</ActionBarRow>
</ActionBar>
<Hits mod="sk-hits-grid" hitsPerPage={10} itemComponent={MovieHitsGridItem} sourceFilter={["title", "poster", "imdbId"]}/>
<NoHits/>
</LayoutResults>
</LayoutBody>
</Layout>
</SearchkitProvider>
</div>);
}
});
Fairly new with React, so not too sure why these problems are occuring?
Thanks
Ok, I just browsed through this searchkit library. Based on the fact it's a React component and you are using react-rails, I'm almost certain you are running into the issue of having two React instances at one time. react-rails drawback is the difficulty to integrate external libraries with it. It's quick to setup and start using but as soon as you want to install other react libraries, you will hit a wall.
I had this issue before and what I did was to use https://github.com/netguru/react_webpack_rails instead. If you want something abit more then have a look at https://github.com/shakacode/react_on_rails. These two options require more effort to setup but well worth it if you are serious about React and its ecosystem.
BTW: Searchkit looks great!
It seems I'm about late for the party but, just in case, someone may experience this issue again and need a more direct solution.
For your reference:
Rails 5.2.0
Ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin17]
React-rails (2.3.1)
I believe this could happen on different rails and ruby versions too.
As like what #kasperite said, I'm sure you are having two React instances by mistake. You are either 'require react' twice or running into the dual instances by the 'require_tree .' in the 'application.js' file.
To be more specific, you should check the following lines in the 'application.js' file:
//= require react
//= require react_ujs
//= require components
//= require_tree .
The issue is that you first load React by require react directive, then load it again using the require_tree . directive.
Ref: https://guides.rubyonrails.org/asset_pipeline.html#manifest-files-and-directives
Or direct quote from the official guide as in the above link
The require_tree directive tells Sprockets to recursively include all JavaScript files in the specified directory into the output. These paths must be specified relative to the manifest file. You can also use the require_directory directive which includes all JavaScript files only in the directory specified, without recursion.
To sort the issue, you just need to completely delete the require_tree . directive or unset it by removing the equal sign - turn this //= require_tree . to // require_tree .
Hope this help and cheers.

how to install and use angularJS plugin in rails

I wanted to use this angular-number-picker plugin in my rails project.
I downloaded the .min.js file and the .js file and put the in the plugins folder. Then I also included the js in
/global_plugins.js
//= require global/plugins/angular-number-picker/angular-number-picker.min.js
and then I also inject it to the dependency of my app
/app.js
var app = angular.module('fotograft', ['ngResource', 'angular-loading-bar', 'cgBusy', 'ngFileUpload', 'MessageCenterModule', 'rails', 'Devise', 'google.places', 'ui.sortable','checklist-model',
'textAngular', 'angularMoment', '720kb.socialshare', 'ngSanitize', '720kb.tooltips', 'angular-md5', 'angularUtils.directives.dirPagination', 'infinite-scroll', 'datatables', 'datatables.bootstrap',
'ngResource', 'frapontillo.bootstrap-switch', 'angular-bootstrap-select','angular-number-picker', 'angular-bootstrap-select.extra', 'ngTextTruncate', 'chart.js', 'timer', 'siyfion.sfTypeahead'])
The problem is when I tried to load any page, the page will not respond. So I tried to view the console and debug, the error was that
Error: [$injector:modulerr] http://errors.angularjs.org/1.3.16/$injector/modulerr?
I truncated the rest of the error messages, because they are kind of long and look gibberish.
So, I would like to ask what am I missing here? I saw from the author's webpage that I need to include the src to the plugin in the html page, but I have no idea which page I should include it in.
Appreciate it if any rails and angularJS developer can help me out here. Thank you very much.
If you double check the link in the source code you provided, it looks like the module name that you have to register is supposed to be angularNumberPicker instead of angular-number-picker.
So you want your app.js module registry to look like this:
var app = angular.module('fotograft', ['ngResource', 'angular-loading-bar', 'cgBusy', 'ngFileUpload', 'MessageCenterModule', 'rails', 'Devise', 'google.places', 'ui.sortable','checklist-model',
'textAngular', 'angularMoment', '720kb.socialshare', 'ngSanitize', '720kb.tooltips', 'angular-md5', 'angularUtils.directives.dirPagination', 'infinite-scroll', 'datatables', 'datatables.bootstrap',
'ngResource', 'frapontillo.bootstrap-switch', 'angular-bootstrap-select','angularNumberPicker', 'angular-bootstrap-select.extra', 'ngTextTruncate', 'chart.js', 'timer', 'siyfion.sfTypeahead']);

removing bootstrap.js and jquery dependencies with main-bower-files and angular bootstrap ui

Bower and main-bower-files are fantastic, however, when using them with Angular Bootstrap UI, more things are installed/included than required.
Basically : Angular Bootstrap UI, replaces the need for bootstrap.js and it's jquery dependency. However when installing bootstrap, jquery gets installed, then my gulp task which uses main-bower-files, includes jquery and bootstrap.js in my html files.
Is there a way to tell bower, and/or main-bower-files and/or Bootstrap, that jquery and bootstrap.js are not required anymore.
So far I tried commenting the jquery dependency and dist/js/bootstrap.js lines in bower_components/bootstrap/bower.json, but the files are still being included.
1) Switch to wiredep, which I'd recommend. Then you can do something like this:
gulp.task('wiredep', function () {
var wiredep = require('wiredep').stream;
gulp.src('app/*.html')
.pipe(wiredep({
directory: 'app/bower_components',
exclude: ['bootstrap']
}))
.pipe(gulp.dest('app'))
});
Note that the above will remove the whole bootstrap, not just its .js files. The exclude array can contain also regexps, which is what is probably needed if you want to retain for instance styles.
And in your HTML file (for javascript):
Replace the js with css where you want to inject styles.
2) Override the bower main files for Bootstrap: provide the following options to main-bower-files:
{
"overrides": {
"bootstrap": {
"main": [
// files you want to include
]
}
}
}
You'll have to check what you don't want to exclude and add them to the main array above.
See also: https://github.com/ck86/gulp-bower-files/issues/33

Why is Angularjs code not rendering in rails even though Angularjs is running?

I'm trying to implement some angularjs into my Rails application.
I've done the following, and Angular is running (I typed angular into dev console) but not rendering/executing my angular code in my HAML file.
a. Added angular to my gemfile:
source 'https://rails-assets.org'
gem "rails-assets-angular"
group :development, :test do
gem 'rails-assets-angular-mocks'
end
b. Added //= require angular and //= require angular/admin/adminApp (my Angular App file) to app/assets/javascripts/application.js
c. Enabled Angular by adding %html{ 'ng-app' => true } to the tag in the layout app/views/layouts/application.html.haml
d. Added an adminApp:
adminApp = angular.module('adminApp', ['ngResource'])
e. Added and adminController:
'use strict';
angular.module('adminApp')
.controller('AdminCtrl', function ($scope, $http) {
$scope.test = "This is working!"
});
f. Added the following to one of my app's index.html.haml:
UPDATE: changed haml so that angular elements are inside the ng-controller div
%div{"ng-controller" => "AdminCtrl"}
{{test}}
%p Hello {{"World" + "!"}}
Upon loading that page, I get the non-angularized output:
{{test}}
Hello {{"World" + "!"}}
From everything I've read, it should be working. Just not sure where I've gone wrong. Have I missed anything?
It seems that you are injecting ngResource.
It's not included by default in angular so you need add the following if you want to use it:
in the Gemfile: gem "rails-assets-angular-resource"
application.js: //= require angular-resource
Also change %html{ 'ng-app' => true } to %html{'ng-app' => "adminApp"}

Resources