Material-foundation

Just wondering if anyone had a working install of material-foundation on Phoenix1.3?
Or any material framework that works well with Phoenix and particularly gets selects right - so that I can move away from MDL.
I’ve today installed daemonite-material (no scss), mdc-web (selects are not good), material-bootstrap4 (confusing) and now material-foundation (compile never finishes). Frankly, they are all much to much like hard work to install and then the results are average.
The angular-material library was spot-on and I’m wondering why something that should just be super easy isn’t with Phoenix?

Any should work fine, phoenix is front-end agnostic, it is just the web server side. ^.^

Just follow the normal steps for using those front-end bits, just put it all in /assets, and if you switch from brunch just wire it up properly in the config. :slight_smile:

Well, I went back to material-components-web and had another crack at getting a working install and here it is.

First, I needed to update babel-brunch and brunch (I’m not sure which one was causing the problem) with this error:

error: undefined of node_modules/material-components-web/index.js failed. rror: ‘import’ and ‘export’ may only appear at the top level (21:0)

I’m now at babel-brunch 6.1.1 and brunch 2.10.9.

Second, I had to include this snippet of brunch-config:

npm: { 
compilers: ['babel-brunch']
}

Third, getting the sass right was frankly a lot of trial and error. Here’s what works for me in brunch-config

sass: {
  options: {
    includePaths: [
                    "node_modules",
                    "node_modules/material-components-web"
                  ]
  }
}

Fourth, the window.mdc.autoInit() script inside app.html.eex just didn’t work for me. I’m not sure why because it’s meant to be the default behaviour. Here’s my current app.js:

import * as mdc from 'material-components-web';

var x = document.getElementsByClassName('mdc-textfield');
var i;
for (i = 0; i < x.length; i++) {
    mdc.textfield.MDCTextfield.attachTo(x[i]);
}

var x = document.getElementsByClassName('mdc-button');
var i;
for (i = 0; i < x.length; i++) {
    mdc.ripple.MDCRipple.attachTo(x[i]);
}

I think this is called manual instantiation. I only have some text fields and buttons on the page so far :slight_smile:

Lastly, I was getting put off by the implementation of the custom select in mdc-web. But then I realised I could just implement the classes like I’d done with mdl (duh!). Here’s a select using the phoenix helper:

 <div class="mdc-form-field">
  <div class="mdc-textfield" data-mdc-auto-init="MDCTextfield">
    <%= select f, :type,
                  ["UK Public General Acts": "ukpga",
                  "Acts of the Scottish Parliament": "asp",
                  "Acts of the Northern Ireland Assembly": "nia",
                  "Measures of the Welsh Assembly": "mwa",
                  "UK Statutory Instruments": "uksi",
                  "Scottish Statutory Instrument": "ssi",
                  "Welsh Statutory Instrument": "wsi",
                  "Northern Ireland Statutory Rules": "nisr",
                  "UK Ministerial Orders": "ukmo"],
                  prompt: " ",
                  value: @conn.params["type"],
                  class: "mdc-textfield__input"
    %>
    <%= label f, :type, class: "mdc-textfield__label" %>
  </div>
</div>

I’m not sure I need data-mdc-auto-init given that I’m manually hooking in with the poorly documented attachTo.

Hope this might help someone avoid a weekend trying to work through how this stuff wires up. Back to the day job tomorrow.

Update. Should have included the app.scss:

@import "material-components-web";

Did you try the placing the autoInit() inside of your js instead of putting it in app.html.eex?

Something like this in your app.js:

import mdcAutoInit from '@material/auto-init';
document.addEventListener('DOMContentLoaded', () => mdcAutoInit(), false);

Doing on DOMContentLoaded should be similar to loading it in the footer, and doing it from your js gives you some confidence that you have what you need in scope without having to reason about the browser scope/load order.

I was looking at the autoInit docs and there is a documented way to manually attach the components using mdcAutoInit.register('MDCTextfield', MDCTextfield);. This might potentially avoid any weird bugs. Manually attaching the element to component like you have is basically what the autoInit is doing, except you are bypassing some error checking and the addition of some extra metadata to the element. I think if your manual instantiation is working inside of the scripts then calling mcdAutoInit() directly should also work.

But, If you leave it like you have it then here is a more declarative way of instantiating (if declarative is your cup of tea):

document.querySelectorAll(".mdc-textfield")
  .forEach(el => mdc.textfield.MDCTextField.attachTo(el))

document.querySelectorAll(".mdc-button")
  .forEach(el => mdc.ripple.MDCRipple.attachTo(el))

That’s a bug in material components. According to the ES2016 module spec all module links must be defined at the top level. Some older libs did things stupid, but if you are running in proper ES2016 module spec then it needs to be at the top level. Run older otherwise (babel is pluggable like that).

If you are bringing it in via brunch then I bet you did not expose it globally in brunch. By default brunch sandboxes all the modules to not touch global things, as they really really should not do so, and instead you should do something like require("material-components-web").autoInit(); or from where-ever it exposes it. If you really really want it globally (always poor form in my opinion) then you can add an option like this in the brunch config file, this is for jquery for example:

  npm: {
    enabled: true,
    globals: {
      $: 'jquery',
      jQuery: 'jquery'
    }
  }

That will bind the jquery module to the window.$ and window.jQuery variables for example. But again globals are bad form.[quote=“shotleybuilder, post:3, topic:8692”]
But then I realised I could just implement the classes like I’d done with mdl (duh!). Here’s a select using the phoenix helper:
[/quote]

I use a TON of helpers. Like my Surface.ex file has gotten quite sizeable at 341 lines so far, with a couple of gems like this (because some people at work are really really weird and like their dates formatted in a wtf way, I don’t get this format…):

  def format_date_wrong(%{year: year, month: month, day: day}) do
    year = String.pad_leading(to_string(year), 4, "0")
    month = String.pad_leading(to_string(month), 2, "0")
    day = String.pad_leading(to_string(day), 2, "0")
    "#{month}/#{day}/#{year}"
  end

But also a lot of useful stuff like:

  def collapsible(unique_id, opts \\ [], [do: body]) do
    title = Keyword.get(opts, :title, humanize(unique_id))
    class = Keyword.get(opts, :class, "")
    ~E"""
    <input type="checkbox" id="collapsible-<%= unique_id %>">
    <label for="collapsible-<%= unique_id %>"><%= title %></label>
    <div class="<%= class %> collapsible-<%= unique_id %>-area">
      <%= body %>
    </div>
    """
  end

And so forth. :slight_smile:

But yeah, all of what you did had nothing to do with Phoenix except the helper you made. ^.^

Ah, that would be the module name that you’d want to import, or put in the global section. :slight_smile:

@kylethebaker @OvermindDL1 many thanks for the interest.

I’m closer to understanding material-components-web install. I’d thought to include mdcAutoInit into app.js to make sure all was in scope, but wasn’t familiar with the js html DOM. I think material-components-web will force some familiarity!

I was wanting to only load what I really needed, so for the moment this is my refactored code:

import mdcAutoInit from '@material/auto-init';
import {MDCComponent, MDCFoundation} from '@material/base';
import {MDCRipple} from '@material/ripple';
import {MDCTextfield} from '@material/textfield';

mdcAutoInit.register('MDCRipple', MDCRipple);
mdcAutoInit.register('MDCTextfield', MDCTextfield);

document.addEventListener('DOMContentLoaded', () => mdcAutoInit(), false);

Peeking into the material-components-web module index.js file helped.