Bootstrap show/hide operations do nothing in Phoenix app

(Front-end newbie here; please forgive.)

I want to implement simple show/hide behavior in my Phoenix app. The CSS and JS files are untouched from directory-generation time. I see it uses Bootstrap v3.3.5.

So I would expect to paste the HTML from https://jsfiddle.net/clDjs/L8Lef3cx/1/ onto one of my pages and have it work the same way. But it doesn’t do anything when I click.

I’m sure I’m doing something obvious wrongly, or obviously wrong.

2 Likes

As documented at the bootstrap website, the actions that require javascript are not auto-included and you need to call them yourself. I.E., you could add something like this to your app.js for your specific example:

import $ from "jquery"

// Some bootstrap things need this, not most thankfully, keep disabled unless required, this one might require it?
// global.jQuery = require("jquery")

// Make sure you included bootstrap js in your npm and brunch config files or this will not work
global.bootstrap = require("bootstrap")

// Do the stuff to the existing html at page load time:
$(document).ready(() => {
  // This is optional if you want to do it on 'something other than default'
  $('[data-toggle="collapse"]').collapse()
}

Or something like that (I don’t use collapse, just dropdown and tooltip, and I’m fazing those out for other solutions over time).

I.E. Need to include bootstrap’s javascript. :slight_smile:

2 Likes

I’m having trouble making that work. I think frontend novices need a painfully explicit guide. I will write that up once/if I get things working. I have done the following:

  1. Added this to package.json:
"dependencies": {
   ...
   "jquery": "~1.9.1",
   "bootstrap": "~3.3.5"

(Q: I didn’t find a very clear way to know what versions of JQuery Bootstrap works with, so the above version is something of a guess.)

  1. Added this to brunch-config:
   files: {
     javascripts: {
       order: {
         before: [
           "web/static/vendor/js/jquery.min.js",
           "web/static/vendor/js/bootstrap.min.js"
        ]
      }

None of the above seems to interfere with what already worked. But when I add the first line of the previous reply to web/static/js/app.js, as in this:

import $ from "jquery"

… the result is this:

27 Jul 17:19:28 - error: Resolving deps of web/static/js/app.js failed. Could not load module 'jquery' from '/Users/bem/src/critter4us/eecrit/web/static/js'. Possible solution: run `npm install`. a

(npm install doesn’t make a difference.)

1 Like

Brunch uses a whitelist to allow files to be accessed by the front-end (to prevent random node modules from screwing up your site and hugely inflating the size), so it needs to be specified in brunch-config.js. Here is my section for example:

  npm: {
    enabled: true,
    whitelist: [
      "jquery",
      "tether",
      "bootstrap",
      "bootstrap-select",
      "material-design-lite",
      "highlight.js",
      "phoenix",
      "phoenix_html"],
    styles: {
      //bootstrap: ['scss/bootstrap.scss']
      bootstrap: ["dist/css/bootstrap.min.css"],
      tether: ["dist/css/tether.min.css"],
      "material-design-lite": [
        "dist/material.min.css",
        "dist/material.blue-light_blue.min.css"
      ],
      "material-design-icons": [
        "iconfont/material-icons.css"
      ]
    }
  }

The npm.whitelist object is a List of Strings of the NPM package name to allow in the final files.

You may not need this one right now if you included the bootstrap css yourself, but the npm.styles is an object of objects, the keys of which are NPM package names and the value is a List of Strings of the css file inside the NPM package to include in your css.

CSS does not have the concept of Modules like Javascript ES6/ES2015 has hence why they need their own specifier. But as stated, you can ignore the styles branch until you actually need it. :slight_smile:

1 Like

P.S. I did my best due diligence searching for solutions. I found a number of partial ones, but none that I could put together into working code.

I posted at same time as you so look above. ^.^

EDIT: Oh, and do note that you will still need what you already added, that is just ‘in addition to’.

The above still doesn’t work. I still get this message:

28 Jul 09:10:35 - error: Resolving deps of web/static/js/app.js failed. Could not load module 'jquery' from '/Users/bem/src/critter4us/eecrit/web/static/js'. Possible solution: run `npm install`. a

Here are the diffs I’ve made to the files originally installed when the project was created:

brunch_config.js

package.json

The one line I’ve added (so far) to web/static/js/app.js:

Hmm… Does the jquery lib have to be manually put in web/static/js? I’d been assuming npm install would do whatever was required.

You should not move any node package from the node_modules to your directories as brunch can link those in. However the "web/static/vendor/js/jquery.min.js" (code tags in future? Pictures are horrible to copy/paste text… :frowning: ) implies that you have another jquery, and same with bootstrap, that is in your ‘order’ section.

Better yet, here is my entire npm package.json file:

{
  "repository": {},
  "license": "MIT",
  "scripts": {
    "deploy": "brunch build --production",
    "watch": "brunch watch --stdin"
  },
  "dependencies": {
    "bootstrap": "^4.0.0-alpha.2",
    "tether": "~1.3.2",
    "bootstrap-select": ">=1.10.0",
    "material-design-lite": ">=1.1.3",
    "material-design-icons": ">=2.2.3",
    "jquery": ">=3.0",
    "moment": ">=2.13.0",
    "highlight.js": ">=9.5.0",
    "phoenix": "file:deps/phoenix",
    "phoenix_html": "file:deps/phoenix_html"
  },
  "devDependencies": {
    "babel-brunch": "~6.0.0",
    "babel-plugin-transform-object-rest-spread": ">=6.8.0",
    "brunch": "~2.8.2",
    "clean-css-brunch": "~2.0.0",
    "elm-brunch": "0.6.0",
    "elmx": "1.0.5",
    "css-brunch": "~2.0.0",
    "javascript-brunch": "~2.0.0",
    "uglify-js-brunch": "~2.0.1"
  }
}

And my brunch-config.js file:

exports.config = {
  // See http://brunch.io/#documentation for docs.
  files: {
    javascripts: {
      joinTo: "js/app.js",
      order: {
        before: [
          "dist/js/jquery.min.js", // I don't think these four are necessary, just old holdovers...
          "dist/js/tether.min.js",
          "dist/js/bootstrap.min.js",
          "dist/js/bootstrap-select.min.js"
        ],
        after: [
          "web/static/js/app.js" // concat app.js last
        ]
      }
    },
    stylesheets: {
      joinTo: "css/app.css",
      order: {
        before: [
          "dist/css/tether.min.css",
          "dist/css/bootstrap.min.css",
          "dist/css/bootstrap-select.min.css",
          "dist/material.min.css",
          "dist/material.blue-light_blue.min.css",
          "iconfont/material-icons.css"
        ],
        after: [
          "web/static/css/app.css" // concat app.css last
        ]
      }
    },
    templates: {
      joinTo: "js/app.js"
    }
  },

  conventions: {
    // This option sets where we should place non-css and non-js assets in.
    // By default, we set this to "/web/static/assets". Files in this directory
    // will be copied to `paths.public`, which is "priv/static" by default.
    assets: /^(web\/static\/assets)/
  },


  // Phoenix paths configuration
  paths: {
    // Dependencies and current project directories to watch
    watched: [
      "web/elm",
      "web/static",
      "test/static"
    ],

    // Where to compile files to
    public: "priv/static"
  },

  // Configure your plugins
  plugins: {
    babel: {
      presets: ['es2015'],
      plugins: ["transform-object-rest-spread"],
      // Do not use ES6 compiler in vendor code
      ignore: [/web\/static\/vendor/],
      compact: false
    },
    uglify: {
      mangle: true
    },
    elmBrunch: {
      elmFolder: '.',
      mainModules: ['web/elm/MessengerApp.elm', 'web/elm/NotificationsApp.elm'],
      // If specified, all mainModules will be compiled to a single file (optional and merged with outputFolder)
      outputFolder: 'web/static/js',
      outputFile: 'elm.js',
      makeParameters: ['--warn']
    }
  },

  modules: {
    autoRequire: {
      "js/app.js": ["web/static/js/app"]
    }
  },

  npm: {
    enabled: true,
    whitelist: [
      "jquery",
      "tether",
      "bootstrap",
      "bootstrap-select",
      "material-design-lite",
      "highlight.js",
      "phoenix",
      "phoenix_html"],
    styles: {
      bootstrap: ["dist/css/bootstrap.min.css"],
      tether: ["dist/css/tether.min.css"],
      "material-design-lite": [
        "dist/material.min.css",
        "dist/material.blue-light_blue.min.css"
      ],
      "material-design-icons": [
        "iconfont/material-icons.css"
      ]
    }
  }
};

And my entire app.js (give-or-take a touch):

import "phoenix_html"

import $ from "jquery"
import "jquery"
import moment from "moment"
import "bootstrap-select"


// Ugh...
global.jQuery = require("jquery")  // Needed for tether or bootstrap...
global.Tether = require("tether")
global.bootstrap = require("bootstrap")
global.moment = moment


import socket from "./socket"

import setup_help from './components/help'
import setup_elm_messenger_app from './elm_apps/messenger'
import setup_elm_notifications_app from './elm_apps/notifications'

$(document).ready(() => {
  $('.dropdown-toggle').dropdown()
  $('[data-toggle="tooltip"]').tooltip()

  setInterval(() => {
    $.each($('.rel-time-display'), (index, val) => {
      let elem = $(val)
      let old_time = elem.text()
      let new_time = moment(elem.attr('data-time')).fromNow()
      if(old_time != new_time) {
        elem.text(new_time)
      }
    })
  },5000);

  if( /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent) ) {
    $('.selectpicker').selectpicker('mobile');
  }
  else {
    $('.selectpicker').selectpicker()
  }
})


let socket_setup_once = false
socket.onOpen(() => {
  $(document).ready(function(){
    if(socket_setup_once) return
    socket_setup_once = true

    setup_help(socket)

    setup_elm_notifications_app(document.querySelector('#notifications-container'), socket)

    if(setup_elm_messenger_app(undefined, socket)) {
      $("#messenger-popup-dropdown").remove()
      $("#messenger-popup-button").removeClass('dropdown-toggle')
      $("#messenger-popup-button").addClass('disabled')
    }
    else {
      setup_elm_messenger_app(document.querySelector('#messenger-popup-container'), socket)
    }

  })
})

socket.connect()

And I know that all works. So perhaps use it as an example? :slight_smile:

1 Like

Works. I’ll write something up for other newbies, see if I can make a pull request.

1 Like

Cool. :slight_smile:

Actually, I was wrong - I hadn’t changed the app.js. When I use yours, I still get the error:

28 Jul 10:49:29 - error: Resolving deps of web/static/js/app.js failed. Could not load module 'bootstrap' from '/Users/bem/src/critter4us/eecrit/web/static/js'. Possible solution: run `npm install`. a

Here’s the prefix of your file I used to get the above line:

import "phoenix_html"

import $ from "jquery"
import "jquery"
import moment from "moment"
import "bootstrap-select"


// Ugh...
global.jQuery = require("jquery")  // Needed for tether or bootstrap...
global.Tether = require("tether")
global.bootstrap = require("bootstrap")

Removing the last line lets it load without error.

Maybe I should have been a carpenter, like my dad wanted.

Ah, it was this in yours:

"bootstrap": "^4.0.0-alpha.2",

My npm must not want to download alpha versions, so it just silently did nothing.

o.O

Have I mentioned much how much I dislike the whole javascript ecosystem? >.>

You should be able to use the latest v3 of bootstrap though, I’m just using v4 because of certain changes in it that I like, however I am slowly fazing bootstrap out and replacing with material.

Here are the diffs, stripped down to just bootstrap and bootstrap-select. https://github.com/marick/eecrit/commit/699c0e83993e9e940144b3d4a60d0b0afe04fa18

1 Like

Yeah this part:

+// Original version had this note about next line:
+// “Needed for tether or bootstrap…”
+// Because I don’t know how/when the problem would
+// show up if it’s required for bootstrap, I’m leaving
+// it in.

Bootstrap inside its ‘require-style’ interface looks for a global jquery for whatever-the-frick-reason (might just be broken like that in v4, I’m unsure). It is utterly retarded though. Just one of many reasons I am slowly converting the bootstrap site into material (material handles resizing so much more gracefully than bootstrap as well).

Oh hey, I just noticed your project description at https://github.com/marick/eecrit of:

Elixir/Elm version of Critter4Us

I’ve no clue what Critter4Us is but I use Elm, if you need any help with that integration that was also included in my above code (and I can supply some more of the Elm stuff if you need). :slight_smile:

Hmm, going back to the initial non-working case, it should be enough to just do npm install --save jquery in order to have jQuery or anything else that’s compilant with CommonJS (anything non ancient) available for importing. Sometimes it’s good to issue a clean brunch build too, but that’s it.

npm.whitelist doesn’t appear by default in newest Phoenix boilerplate and order.before is redundant if things are imported directly from NPM. I guess your initial version didn’t work due to pinning down an old version of jquery. It’s either that or some weird Brunch bug related to module wrapping.

I added those order.before's just to follow the pattern when the project was first set up when I did not know how brunch worked yet and just never ended up removing them. And as I recall from the docs the npm.whitelist if missing will import all node modules that you have, which makes my output huge, so I like to whittle it down, thus I guess it does not need to be there either, but I also like controlling exactly what goes in the output files. :slight_smile:

Elm tips would be great. You may be the only person in the universe who has more than one entry in the mainModules array. I’m trying to do the same, and keep having problems with There are two Elm modules called DatePicker4Us on this page! Rename one of them.

What I’m trying to do, just to get started is put this on the front page:

main =
    Html.text "Consider this text a promise of future beauty"

… and a use of Bogdanp/elm-datepicker on another, both as embeds. If you had a stripped-down example of one of your elm-embedding .html files (and maybe templates/layout/app.html.eex and web/static/js/app.js), I think that would help a lot.