materialize
materialize copied to clipboard
Use Materialize as modular components
Hi,
I have a lot of issue to have materialize working as modular component. I don't want to have to load the entire JS library (almost 200KB) but only some components.
I was used to Foundation, which was working very simply. But with Materialize, everytime I want to add some component, it is long process to find which component need which dependency.
For example the Modal component
import 'materialize-css/js/modal';
Then I got the error Cash is not defined, so:
import 'materialize-css/js/cash';
import 'materialize-css/js/modal';
Then I got the Component is not defined, so again:
import 'materialize-css/js/component';
import 'materialize-css/js/cash';
import 'materialize-css/js/modal';
But here, I am still getting the issue.
I know several issues have already been reported, but none of them has been abe to help me so far...
Is there (probably) a better to achieve what I am trying to do?
I can't find a dependency documentation, which will list all module interdependency. Is this plan?
Thanks.
You can take a look at our Gruntfile to get a better sense of how we are compiling things. But in general, you should import in this order:
'js/cash.js',
'js/component.js',
'js/global.js',
'js/anime.min.js',
Still having the issue
import 'materialize-css/js/cash.js';
import 'materialize-css/js/component.js';
import 'materialize-css/js/global.js';
import 'materialize-css/js/anime.min.js';
import 'materialize-css/js/modal.js';
$(document).ready(function() {
$('.modal').modal();
});
This error:
app.js:14686 Uncaught ReferenceError: Component is not defined
at app.js:14686
at Object._defaults.alignment (app.js:14940)
at __webpack_require__ (app.js:20)
at Object.<anonymous> (app.js:13164)
at __webpack_require__ (app.js:20)
at Object.defineProperty.value (app.js:10475)
at __webpack_require__ (app.js:20)
I use Webpack to bundle the JS, maybe the issue is linked to this?
I tried to import all modules such this:
import 'materialize-css/js/cash.js';
import 'materialize-css/js/component.js';
import 'materialize-css/js/global.js';
import 'materialize-css/js/anime.min.js';
import 'materialize-css/js/collapsible.js';
import 'materialize-css/js/dropdown.js';
import 'materialize-css/js/modal.js';
import 'materialize-css/js/materialbox.js';
import 'materialize-css/js/parallax.js';
import 'materialize-css/js/tabs.js';
import 'materialize-css/js/tooltip.js';
import 'materialize-css/js/waves.js';
import 'materialize-css/js/toasts.js';
import 'materialize-css/js/sidenav.js';
import 'materialize-css/js/scrollspy.js';
import 'materialize-css/js/autocomplete.js';
import 'materialize-css/js/forms.js';
import 'materialize-css/js/slider.js';
import 'materialize-css/js/cards.js';
import 'materialize-css/js/chips.js';
import 'materialize-css/js/pushpin.js';
import 'materialize-css/js/buttons.js';
import 'materialize-css/js/datepicker.js';
import 'materialize-css/js/timepicker.js';
import 'materialize-css/js/characterCounter.js';
import 'materialize-css/js/carousel.js';
import 'materialize-css/js/tapTarget.js';
import 'materialize-css/js/select.js';
import 'materialize-css/js/range.js';
I get the same issue.. only the following code work correctly:
import 'materialize-css/';
but this is very heavy to use only the modal.
Same here, except I can't do
import
'materialize-css/';
then it says jquery is not defined O_o
Did you import jQuery, something as follow:
window.$ = window.jQuery = require('jquery');
@geosenna no, I've tried
import $ from 'jquery';
and also
import jQuery from 'jquery';
but didn't work. Moved to gulp for now as this project I'm working on needs to be done fast 😠
@acburst any idea?
@geosenna I have done the following and works:
- In component.js replace the first line with
export default class Component {
in your modal file add at the top add:
import Component from '../path-to/component';
In the source bundle (application.js in my app) I don't import component.js again, only in the modal or whatever other 'component' you need to import it from there.
Hope this helps.
I have done what you said, but I get the same error.
app.js:17024 Uncaught ReferenceError: Component is not defined
at app.js:17024
at Object.<anonymous> (app.js:17384)
at __webpack_require__ (app.js:20)
at Object.<anonymous> (app.js:10845)
at __webpack_require__ (app.js:20)
at Object.defineProperty.value (app.js:10477)
at __webpack_require__ (app.js:20)
at Object._slice (app.js:12803)
at __webpack_require__ (app.js:20)
at Object.ua (app.js:10920)
import 'materialize-css/js/cash.js';
import Component from 'materialize-css/js/component.js';
import 'materialize-css/js/global.js';
import 'materialize-css/js/anime.min.js';
import 'materialize-css/js/modal.js';
And I modify the Component.js file itself from materialize.
You need to move 'import Component from 'materialize-css/js/component.js';' to modal.js
Actually this this the content of the modal.js file, not my source bundle.
any though @acburst ?
I still have the same issue:
Uncaught ReferenceError: Component is not defined
at
class Modal extends Component {
I am really struggling with this... Is anyone succeed to using both Webpack and Materialize Modal?
I haven't been able to use it as modules, but I've managed to make it work with webpack (the RC version). Here's the app.js:
// noinspection JSLint
import jQuery from "jquery"; // jshint ignore:line
// noinspection JSLint
window.$ = window.jQuery = jQuery;
// noinspection JSLint
import * as M from "materialize-css"; // jshint ignore:line
document.addEventListener('DOMContentLoaded', function () {
// noinspection JSLint
'use strict';
// noinspection JSUnresolvedVariable
M.Sidenav.init(document.querySelectorAll('.sidenav'), {
edge: 'right'
});
M.Collapsible.init(document.querySelectorAll('.collapsible'), {
accordion: false
});
if (document.querySelectorAll('.parallax').length) {
M.Parallax.init(document.querySelectorAll('.parallax'), {
responsiveThreshold: 0
});
}
if (document.querySelectorAll('.tooltipped').length) {
M.Tooltip.init(document.querySelectorAll('.tooltipped'));
}
});
Here's the package.json:
{
"private": true,
"scripts": {
"dev": "npm run development",
"development": "cross-env NODE_ENV=development webpack --progress --hide-modules --config build/webpack.config.js",
"prod": "npm run production",
"production": "cross-env NODE_ENV=production webpack --progress --hide-modules --config build/webpack.config.js",
"watch": "npm run development -- --watch && webpack-dev-server --progress --colors"
},
"dependencies": {
"jquery": "^3.3.1",
"materialize-css": "^1.0.0-rc.2"
},
"devDependencies": {
"autoprefixer": "^8.2.0",
"babel-core": "^6.24.1",
"babel-loader": "^7.1.0",
"babel-preset-env": "^1.3.3",
"browser-sync": "^2.18.8",
"browser-sync-webpack-plugin": "^2.2.2",
"clean-webpack-plugin": "^0.1.16",
"copy-webpack-plugin": "^4.0.1",
"cross-env": "^5.0.1",
"css-loader": "^0.28.0",
"dotenv": "^5.0.1",
"eslint": "^4.10.0",
"eslint-loader": "^2.0.0",
"extract-text-webpack-plugin": "^3.0.0",
"file-loader": "^1.1.11",
"imagemin-jpeg-recompress": "^5.1.0",
"imagemin-webpack-plugin": "^2.1.0",
"img-loader": "^2.0.0",
"isdev": "^1.0.1",
"node-sass": "^4.5.2",
"postcss-loader": "^2.1.3",
"sass-loader": "^6.0.3",
"style-loader": "^0.20.3",
"stylelint": "^9.1.3",
"stylelint-webpack-plugin": "^0.10.3",
"webpack": "~3.11.0",
"webpack-cli": "^2.0.13",
"webpack-dev-server": "^3.1.1",
"webpack-merge": "^4.1.0"
}
}
If anyone is interested, I can make a boilerplate od sorts that I'm using as there are some more config files.
Yes me too, my issue is about using the modal as component because of this error:
Uncaught ReferenceError: Component is not defined
I am using webpack with the modular components. So if you need to transpile to es5, please add babel in your set up. This example is the dropdown, not the modal, but that should work similar. I do not use jQuery, only cash-dom. The animate file is from the materialize repository, as there is one change that makes it not possible to load it via npm, but that is not relevant for this issue.
I hope this helps as a reference to get a working modal in your application.
app.js:
import "cash-dom";
import 'components/global/global';
import "components/animate/anime";
import "components/dropdown/dropdown";
import "init/init";
package.json:
"dependencies": {
"cash-dom": "2.1.6",
},
"devDependencies": {
"webpack-dev-server": "2.11.2"
}
components/global/global.js: (modified)
(function(window) {
window.M = {};
})(window);
M.keys = {
TAB: 9,
ENTER: 13,
ESC: 27,
ARROW_UP: 38,
ARROW_DOWN: 40
};
/**
* TabPress Keydown handler
*/
M.tabPressed = false;
M.keyDown = false;
let docHandleKeydown = function(e) {
M.keyDown = true;
if (e.which === M.keys.TAB || e.which === M.keys.ARROW_DOWN || e.which === M.keys.ARROW_UP) {
M.tabPressed = true;
}
};
let docHandleKeyup = function(e) {
M.keyDown = false;
if (e.which === M.keys.TAB || e.which === M.keys.ARROW_DOWN || e.which === M.keys.ARROW_UP) {
M.tabPressed = false;
}
};
let docHandleFocus = function(e) {
if (M.keyDown) {
document.body.classList.add('keyboard-focused');
}
};
let docHandleBlur = function(e) {
document.body.classList.remove('keyboard-focused');
};
document.addEventListener('keydown', docHandleKeydown, true);
document.addEventListener('keyup', docHandleKeyup, true);
document.addEventListener('focus', docHandleFocus, true);
document.addEventListener('blur', docHandleBlur, true);
/**
* Gets id of component from a trigger
* @param {Element} trigger trigger
* @returns {string}
*/
M.getIdFromTrigger = function(trigger) {
let id = trigger.getAttribute('data-target');
if (!id) {
id = trigger.getAttribute('href');
if (id) {
id = id.slice(1);
} else {
id = '';
}
}
return id;
};
M.getPositionFromTrigger = function(trigger) {
let position = trigger.dataset.alignment;
return position;
};
M.getCoverTrigger = function(trigger) {
let coverTrigger = trigger.dataset.cover;
return coverTrigger;
};
/**
* Automatically initialize components
* @param {Element} context DOM Element to search within for components
*/
M.AutoInit = function(context) {
// Use document.body if no context is given
let root = !!context ? context : document.body;
let registry = {
Dropdown: root.querySelectorAll('.dropdown-trigger:not(.no-autoinit)')
};
for (let pluginName in registry) {
let plugin = M[pluginName];
plugin.init(registry[pluginName]);
}
};
M.checkPossibleAlignments = function(el, container, bounding, offset) {
let canAlign = {
top: true,
right: true,
bottom: true,
left: true,
spaceOnTop: null,
spaceOnRight: null,
spaceOnBottom: null,
spaceOnLeft: null
};
let containerAllowsOverflow = getComputedStyle(container).overflow === 'visible';
let containerRect = container.getBoundingClientRect();
let containerHeight = Math.min(containerRect.height, window.innerHeight);
let containerWidth = Math.min(containerRect.width, window.innerWidth);
let elOffsetRect = el.getBoundingClientRect();
let scrollLeft = container.scrollLeft;
let scrollTop = container.scrollTop;
let scrolledX = bounding.left - scrollLeft;
let scrolledYTopEdge = bounding.top - scrollTop;
let scrolledYBottomEdge = bounding.top + elOffsetRect.height - scrollTop;
// Check for container and viewport for left
canAlign.spaceOnRight = !containerAllowsOverflow
? containerWidth - (scrolledX + bounding.width)
: window.innerWidth - (elOffsetRect.left + bounding.width);
if (canAlign.spaceOnRight < 0) {
canAlign.left = false;
}
// Check for container and viewport for Right
canAlign.spaceOnLeft = !containerAllowsOverflow
? scrolledX - bounding.width + elOffsetRect.width
: elOffsetRect.right - bounding.width;
if (canAlign.spaceOnLeft < 0) {
canAlign.right = false;
}
// Check for container and viewport for Top
canAlign.spaceOnBottom = !containerAllowsOverflow
? containerHeight - (scrolledYTopEdge + bounding.height + offset)
: window.innerHeight - (elOffsetRect.top + bounding.height + offset);
if (canAlign.spaceOnBottom < 0) {
canAlign.top = false;
}
// Check for container and viewport for Bottom
canAlign.spaceOnTop = !containerAllowsOverflow
? scrolledYBottomEdge - (bounding.height - offset)
: elOffsetRect.bottom - (bounding.height + offset);
if (canAlign.spaceOnTop < 0) {
canAlign.bottom = false;
}
return canAlign;
};
components/global/component.js (imported in dropdown.js)
export default class Component {
/**
* Generic constructor for all components
* @constructor
* @param {Element} el
* @param {Object} options
*/
constructor(classDef, el, options) {
// Display error if el is valid HTML Element
if (!(el instanceof Element)) {
console.error(Error(el + ' is not an HTML Element'));
}
// If exists, destroy and reinitialize in child
let ins = classDef.getInstance(el);
if (!!ins) {
ins.destroy();
}
this.el = el;
this.$el = cash(el);
}
/**
* Initializes components
* @param {class} classDef
* @param {Element | NodeList | jQuery} els
* @param {Object} options
*/
static init(classDef, els, options) {
let instances = null;
if (els instanceof Element) {
instances = new classDef(els, options);
} else if (!!els && (els.jquery || els.cash || els instanceof NodeList)) {
let instancesArr = [];
for (let i = 0; i < els.length; i++) {
instancesArr.push(new classDef(els[i], options));
}
instances = instancesArr;
}
return instances;
}
}
components/dropdown/dropdown.js: (modified)
import Component from '../global/component';
(function($, anim) {
'use strict';
let _defaults = {
alignment: 'left',
autoFocus: true,
constrainWidth: false,
container: null,
coverTrigger: false,
closeOnClick: true,
hover: false,
inDuration: 150,
outDuration: 250,
onOpenStart: null,
onOpenEnd: null,
onCloseStart: null,
onCloseEnd: null,
onItemClick: null
};
/**
* @class
*/
class Dropdown extends Component {
constructor(el, options) {
super(Dropdown, el, options);
this.el.M_Dropdown = this;
Dropdown._dropdowns.push(this);
this.id = M.getIdFromTrigger(el);
this.position = M.getPositionFromTrigger(el);
this.coverTrigger = M.getCoverTrigger(el);
this.dropdownEl = document.getElementById(this.id);
this.$dropdownEl = $(this.dropdownEl);
/**
* Options for the dropdown
* @member Dropdown#options
* @prop {String} [alignment='left'] - Edge which the dropdown is aligned to
* @prop {Boolean} [autoFocus=true] - Automatically focus dropdown el for keyboard
* @prop {Boolean} [constrainWidth=true] - Constrain width to width of the button
* @prop {Element} container - Container element to attach dropdown to (optional)
* @prop {Boolean} [coverTrigger=true] - Place dropdown over trigger
* @prop {Boolean} [closeOnClick=true] - Close on click of dropdown item
* @prop {Boolean} [hover=false] - Open dropdown on hover
* @prop {Number} [inDuration=150] - Duration of open animation in ms
* @prop {Number} [outDuration=250] - Duration of close animation in ms
* @prop {Function} onOpenStart - Function called when dropdown starts opening
* @prop {Function} onOpenEnd - Function called when dropdown finishes opening
* @prop {Function} onCloseStart - Function called when dropdown starts closing
* @prop {Function} onCloseEnd - Function called when dropdown finishes closing
*/
this.options = $.extend({}, Dropdown.defaults, options);
/**
* Describes open/close state of dropdown
* @type {Boolean}
*/
this.isOpen = false;
/**
* Describes if dropdown content is scrollable
* @type {Boolean}
*/
this.isScrollable = false;
/**
* Describes if touch moving on dropdown content
* @type {Boolean}
*/
this.isTouchMoving = false;
this.focusedIndex = -1;
this.filterQuery = [];
/**
* Read the position from the data-alignment attribute.
*/
if(this.position) {
this.options.alignment = this.position;
}
if(this.coverTrigger === "true") {
this.options.coverTrigger = true;
}
// Move dropdown-content after dropdown-trigger
if (!!this.options.container) {
$(this.options.container).append(this.dropdownEl);
} else {
this.$el.after(this.dropdownEl);
}
this._makeDropdownFocusable();
this._resetFilterQueryBound = this._resetFilterQuery.bind(this);
this._handleDocumentClickBound = this._handleDocumentClick.bind(this);
this._handleDocumentTouchmoveBound = this._handleDocumentTouchmove.bind(this);
this._handleDropdownClickBound = this._handleDropdownClick.bind(this);
this._handleDropdownKeydownBound = this._handleDropdownKeydown.bind(this);
this._handleTriggerKeydownBound = this._handleTriggerKeydown.bind(this);
this._setupEventHandlers();
}
static get defaults() {
return _defaults;
}
static init(els, options) {
return super.init(this, els, options);
}
/**
* Get Instance
*/
static getInstance(el) {
let domElem = !!el.jquery ? el[0] : el;
return domElem.M_Dropdown;
}
/**
* Teardown component
*/
destroy() {
this._resetDropdownStyles();
this._removeEventHandlers();
Dropdown._dropdowns.splice(Dropdown._dropdowns.indexOf(this), 1);
this.el.M_Dropdown = undefined;
}
/**
* Setup Event Handlers
*/
_setupEventHandlers() {
// Trigger keydown handler
this.el.addEventListener('keydown', this._handleTriggerKeydownBound);
// Item click handler
this.dropdownEl.addEventListener('click', this._handleDropdownClickBound);
// Hover event handlers
if (this.options.hover) {
this._handleMouseEnterBound = this._handleMouseEnter.bind(this);
this.el.addEventListener('mouseenter', this._handleMouseEnterBound);
this._handleMouseLeaveBound = this._handleMouseLeave.bind(this);
this.el.addEventListener('mouseleave', this._handleMouseLeaveBound);
this.dropdownEl.addEventListener('mouseleave', this._handleMouseLeaveBound);
// Click event handlers
} else {
this._handleClickBound = this._handleClick.bind(this);
this.el.addEventListener('click', this._handleClickBound);
}
}
/**
* Remove Event Handlers
*/
_removeEventHandlers() {
this.el.removeEventListener('keydown', this._handleTriggerKeydownBound);
this.dropdownEl.removeEventListener('click', this._handleDropdownClickBound);
if (this.options.hover) {
this.el.removeEventListener('mouseenter', this._handleMouseEnterBound);
this.el.removeEventListener('mouseleave', this._handleMouseLeaveBound);
this.dropdownEl.removeEventListener('mouseleave', this._handleMouseLeaveBound);
} else {
this.el.removeEventListener('click', this._handleClickBound);
}
}
_setupTemporaryEventHandlers() {
// Use capture phase event handler to prevent click
document.body.addEventListener('click', this._handleDocumentClickBound, true);
document.body.addEventListener('touchend', this._handleDocumentClickBound);
document.body.addEventListener('touchmove', this._handleDocumentTouchmoveBound);
this.dropdownEl.addEventListener('keydown', this._handleDropdownKeydownBound);
}
_removeTemporaryEventHandlers() {
// Use capture phase event handler to prevent click
document.body.removeEventListener('click', this._handleDocumentClickBound, true);
document.body.removeEventListener('touchend', this._handleDocumentClickBound);
document.body.removeEventListener('touchmove', this._handleDocumentTouchmoveBound);
this.dropdownEl.removeEventListener('keydown', this._handleDropdownKeydownBound);
}
_handleClick(e) {
e.preventDefault();
this.open();
}
_handleMouseEnter() {
this.open();
}
_handleMouseLeave(e) {
let toEl = e.toElement || e.relatedTarget;
let leaveToDropdownContent = !!$(toEl).closest('.dropdown-content').length;
let leaveToActiveDropdownTrigger = false;
let $closestTrigger = $(toEl).closest('.dropdown-trigger');
if (
$closestTrigger.length &&
!!$closestTrigger[0].M_Dropdown &&
$closestTrigger[0].M_Dropdown.isOpen
) {
leaveToActiveDropdownTrigger = true;
}
// Close hover dropdown if mouse did not leave to either active dropdown-trigger or dropdown-content
if (!leaveToActiveDropdownTrigger && !leaveToDropdownContent) {
this.close();
}
}
_handleDocumentClick(e) {
let $target = $(e.target);
if (
this.options.closeOnClick &&
$target.closest('.dropdown-content').length &&
!this.isTouchMoving
) {
// isTouchMoving to check if scrolling on mobile.
setTimeout(() => {
this.close();
}, 0);
} else if (
$target.closest('.dropdown-trigger').length ||
!$target.closest('.dropdown-content').length
) {
setTimeout(() => {
this.close();
}, 0);
}
this.isTouchMoving = false;
}
_handleTriggerKeydown(e) {
// ARROW DOWN OR ENTER WHEN SELECT IS CLOSED - open Dropdown
if ((e.which === M.keys.ARROW_DOWN || e.which === M.keys.ENTER) && !this.isOpen) {
e.preventDefault();
this.open();
}
}
/**
* Handle Document Touchmove
* @param {Event} e
*/
_handleDocumentTouchmove(e) {
let $target = $(e.target);
if ($target.closest('.dropdown-content').length) {
this.isTouchMoving = true;
}
}
/**
* Handle Dropdown Click
* @param {Event} e
*/
_handleDropdownClick(e) {
// onItemClick callback
if (typeof this.options.onItemClick === 'function') {
let itemEl = $(e.target).closest('li')[0];
this.options.onItemClick.call(this, itemEl);
}
}
/**
* Handle Dropdown Keydown
* @param {Event} e
*/
_handleDropdownKeydown(e) {
if (e.which === M.keys.TAB) {
e.preventDefault();
this.close();
// Navigate down dropdown list
} else if ((e.which === M.keys.ARROW_DOWN || e.which === M.keys.ARROW_UP) && this.isOpen) {
e.preventDefault();
let direction = e.which === M.keys.ARROW_DOWN ? 1 : -1;
let newFocusedIndex = this.focusedIndex;
let foundNewIndex = false;
do {
newFocusedIndex = newFocusedIndex + direction;
if (
!!this.dropdownEl.children[newFocusedIndex] &&
this.dropdownEl.children[newFocusedIndex].tabIndex !== -1
) {
foundNewIndex = true;
break;
}
} while (newFocusedIndex < this.dropdownEl.children.length && newFocusedIndex >= 0);
if (foundNewIndex) {
this.focusedIndex = newFocusedIndex;
this._focusFocusedItem();
}
// ENTER selects choice on focused item
} else if (e.which === M.keys.ENTER && this.isOpen) {
// Search for <a> and <button>
let focusedElement = this.dropdownEl.children[this.focusedIndex];
let $activatableElement = $(focusedElement)
.find('a, button')
.first();
// Click a or button tag if exists, otherwise click li tag
!!$activatableElement.length ? $activatableElement[0].click() : focusedElement.click();
// Close dropdown on ESC
} else if (e.which === M.keys.ESC && this.isOpen) {
e.preventDefault();
this.close();
}
// CASE WHEN USER TYPE LETTERS
let letter = String.fromCharCode(e.which).toLowerCase(),
nonLetters = [9, 13, 27, 38, 40];
if (letter && nonLetters.indexOf(e.which) === -1) {
this.filterQuery.push(letter);
let string = this.filterQuery.join(''),
newOptionEl = $(this.dropdownEl)
.find('li')
.filter((el) => {
return (
$(el)
.text()
.toLowerCase()
.indexOf(string) === 0
);
})[0];
if (newOptionEl) {
this.focusedIndex = $(newOptionEl).index();
this._focusFocusedItem();
}
}
this.filterTimeout = setTimeout(this._resetFilterQueryBound, 1000);
}
/**
* Setup dropdown
*/
_resetFilterQuery() {
this.filterQuery = [];
}
_resetDropdownStyles() {
this.$dropdownEl.css({
display: '',
width: '',
height: '',
left: '',
top: '',
'transform-origin': '',
transform: '',
opacity: ''
});
}
_makeDropdownFocusable() {
// Needed for arrow key navigation
this.dropdownEl.tabIndex = 0;
}
_focusFocusedItem() {
if (
this.focusedIndex >= 0 &&
this.focusedIndex < this.dropdownEl.children.length &&
this.options.autoFocus
) {
this.dropdownEl.children[this.focusedIndex].focus();
}
}
_getDropdownPosition() {
let offsetParentBRect = this.el.offsetParent.getBoundingClientRect();
let triggerBRect = this.el.getBoundingClientRect();
let dropdownBRect = this.dropdownEl.getBoundingClientRect();
let idealHeight = dropdownBRect.height;
let idealWidth = dropdownBRect.width;
let idealXPos = triggerBRect.left - dropdownBRect.left;
let idealYPos = triggerBRect.top - dropdownBRect.top;
let dropdownBounds = {
left: idealXPos,
top: idealYPos,
height: idealHeight,
width: idealWidth
};
// Countainer here will be closest ancestor with overflow: hidden
let closestOverflowParent = this.dropdownEl.offsetParent;
let alignments = M.checkPossibleAlignments(
this.el,
closestOverflowParent,
dropdownBounds,
this.options.coverTrigger ? 0 : triggerBRect.height
);
let verticalAlignment = 'top';
let horizontalAlignment = this.options.alignment;
idealYPos += this.options.coverTrigger ? 0 : triggerBRect.height;
// Reset isScrollable
this.isScrollable = false;
if (!alignments.top) {
if (alignments.bottom) {
verticalAlignment = 'bottom';
} else {
this.isScrollable = true;
// Determine which side has most space and cutoff at correct height
if (alignments.spaceOnTop > alignments.spaceOnBottom) {
verticalAlignment = 'bottom';
idealHeight += alignments.spaceOnTop;
idealYPos -= alignments.spaceOnTop;
} else {
idealHeight += alignments.spaceOnBottom;
}
}
}
// If preferred horizontal alignment is possible
if (!alignments[horizontalAlignment]) {
let oppositeAlignment = horizontalAlignment === 'left' ? 'right' : 'left';
if (alignments[oppositeAlignment]) {
horizontalAlignment = oppositeAlignment;
} else {
// Determine which side has most space and cutoff at correct height
if (alignments.spaceOnLeft > alignments.spaceOnRight) {
horizontalAlignment = 'right';
idealWidth += alignments.spaceOnLeft;
idealXPos -= alignments.spaceOnLeft;
} else {
horizontalAlignment = 'left';
idealWidth += alignments.spaceOnRight;
}
}
}
if (verticalAlignment === 'bottom') {
idealYPos =
idealYPos - dropdownBRect.height + (this.options.coverTrigger ? triggerBRect.height : 0);
}
if (horizontalAlignment === 'right') {
idealXPos = idealXPos - dropdownBRect.width + triggerBRect.width;
}
return {
x: idealXPos,
y: idealYPos,
verticalAlignment: verticalAlignment,
horizontalAlignment: horizontalAlignment,
height: idealHeight,
width: idealWidth
};
}
/**
* Animate in dropdown
*/
_animateIn() {
anim.remove(this.dropdownEl);
anim({
targets: this.dropdownEl,
opacity: {
value: [0, 1],
easing: 'easeOutQuad'
},
scaleX: [0.3, 1],
scaleY: [0.3, 1],
duration: this.options.inDuration,
easing: 'easeOutQuint',
complete: (anim) => {
if (this.options.autoFocus) {
this.dropdownEl.focus();
}
// onOpenEnd callback
if (typeof this.options.onOpenEnd === 'function') {
let elem = anim.animatables[0].target;
this.options.onOpenEnd.call(elem, this.el);
}
}
});
}
/**
* Animate out dropdown
*/
_animateOut() {
anim.remove(this.dropdownEl);
anim({
targets: this.dropdownEl,
opacity: {
value: 0,
easing: 'easeOutQuint'
},
scaleX: 0.3,
scaleY: 0.3,
duration: this.options.outDuration,
easing: 'easeOutQuint',
complete: (anim) => {
this._resetDropdownStyles();
// onCloseEnd callback
if (typeof this.options.onCloseEnd === 'function') {
let elem = anim.animatables[0].target;
this.options.onCloseEnd.call(this, this.el);
}
}
});
}
/**
* Place dropdown
*/
_placeDropdown() {
// Set width before calculating positionInfo
let idealWidth = this.options.constrainWidth
? this.el.getBoundingClientRect().width
: this.dropdownEl.getBoundingClientRect().width;
this.dropdownEl.style.width = idealWidth + 'px';
let positionInfo = this._getDropdownPosition();
this.dropdownEl.style.left = positionInfo.x + 'px';
this.dropdownEl.style.top = positionInfo.y + 'px';
this.dropdownEl.style.height = positionInfo.height + 'px';
this.dropdownEl.style.width = positionInfo.width + 'px';
this.dropdownEl.style.transformOrigin = `${
positionInfo.horizontalAlignment === 'left' ? '0' : '100%'
} ${positionInfo.verticalAlignment === 'top' ? '0' : '100%'}`;
}
/**
* Open Dropdown
*/
open() {
if (this.isOpen) {
return;
}
this.isOpen = true;
// onOpenStart callback
if (typeof this.options.onOpenStart === 'function') {
this.options.onOpenStart.call(this, this.el);
}
// Reset styles
this._resetDropdownStyles();
this.dropdownEl.style.display = 'block';
this._placeDropdown();
this._animateIn();
this._setupTemporaryEventHandlers();
}
/**
* Close Dropdown
*/
close() {
if (!this.isOpen) {
return;
}
this.isOpen = false;
this.focusedIndex = -1;
// onCloseStart callback
if (typeof this.options.onCloseStart === 'function') {
this.options.onCloseStart.call(this, this.el);
}
this._animateOut();
this._removeTemporaryEventHandlers();
if (this.options.autoFocus) {
this.el.focus();
}
}
/**
* Recalculate dimensions
*/
recalculateDimensions() {
if (this.isOpen) {
this.$dropdownEl.css({
width: '',
height: '',
left: '',
top: '',
'transform-origin': ''
});
this._placeDropdown();
}
}
}
/**
* @static
* @memberof Dropdown
*/
Dropdown._dropdowns = [];
window.M.Dropdown = Dropdown;
})(cash, M.anime);
init/init.js:
document.addEventListener('DOMContentLoaded', function() {
M.Dropdown.init($('.dropdown-trigger'));
});
@geosenna were you able to get it figured out? I’m just getting into materialize-css and I wanted to make sure I could do modular imports. For instance, if I just wanted the animations and a couple components, how to go about doing that. Did the suggestion from @maikelGG help?
I didn't try it yet.. I am really relaying on materialize team to do something official about it.
@geosenna I have done the following and works:
- In component.js replace the first line with
export default class Component {
in your modal file add at the top add:
import Component from '../path-to/component';
In the source bundle (application.js in my app) I don't import component.js again, only in the modal or whatever other 'component' you need to import it from there.
Hope this helps.
I've fixed my moduling problem with @MaikelGG 's answer (quoted here). I was having the Component is not defined
error.
I'm using Webpack for bundling with babel-loader
. I simply copied the component,js
and sidenav.js
from the node-modules
folder to my assets/js
folder, and made the changes there.Then it's just importing them in my index.js
file. I've used Materialize's Gruntfile for the import order and loaded the equivalent babel plugins in Webpack as seen in their package.json
. Sample config from my project:
> index.js
:
import "materialize-css/js/cash";
import "./materialize-js/component";
import "materialize-css/js/global";
import "materialize-css/js/anime.min";
import "materialize-css/js/waves";
import "./materialize-js/sidenav";
import "materialize-css/js/forms";
> ./materialize-js/component
:
1. export default class Component {
> ./materialize-js/sidenav
:
1. import Component from "./component";
> webpack.config.js
:
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
plugins: [
"@babel/plugin-transform-arrow-functions",
"@babel/plugin-transform-block-scoping",
"@babel/plugin-transform-classes",
"@babel/plugin-transform-template-literals",
"@babel/plugin-transform-object-super"
]
}
}
> package.json (fyi)
"devDependencies": {
"@babel/core": "^7.1.5",
"@babel/preset-env": "^7.1.5",
"babel-loader": "^8.0.4",
"css-loader": "^1.0.1",
"materialize-css": "^1.0.0",
"mini-css-extract-plugin": "^0.4.4",
"node-sass": "^4.10.0",
"sass-loader": "^7.1.0",
"style-loader": "^0.23.0",
"webpack": "^4.25.1",
"webpack-cli": "^3.1.2"
}
Haven't encountered any errors so far. I hope my input is useful! Cheers ~
Thanks. Finally i use a fork where I remove the component I don't use from the gruntfile source when compiling the JS.
@geosigno Can you share the fork you're using? I'm able to follow @cristovaov's suggested modifications, but have to update all the components that I'm using similar to sidenav.js.
https://github.com/geosigno/materialize
To make tooltip work, with ember octane:
app.import('node_modules/materialize-css/js/cash.js');
app.import('node_modules/materialize-css/js/component.js');
app.import('node_modules/materialize-css/js/global.js');
app.import('node_modules/materialize-css/js/tooltip.js');
This is what I ended up doing:
import {
Modal,
Dropdown
} from 'materialize-css';
document.addEventListener('DOMContentLoaded', function () {
var modals = M.Modal.init(document.querySelectorAll('.modal'), {});
var dropdowns = M.Dropdown.init(document.querySelectorAll('.dropdown-trigger'), {});
});
Import the components you want to use and initialize them individually.
Hope it helps!
@bruno-fernandes this, from my testing, imports the whole lib. I only imported Cash, Component and Global and all functions of the lib are working.
do you have remove all the not used components from sass? how we can import only components needed in sass files?
do you have remove all the not used components from sass? how we can import only components needed in sass files?
I just copy the main sass file from materialize, update the paths and then tag out the components I don't need.
you have config the theming variables and filter out the imports on materialize.scss file? ok thank you!! =D
you have config the theming variables and filter out the imports on materialize.scss file? ok thank you!! =D
Yup, something like this:
@charset "UTF-8";
// Color
//@import "~materialize-css/sass/components/color-variables";
@import "colors";
@import "~materialize-css/sass/components/color-classes";
// Variables;
//@import "~materialize-css/sass/components/variables";
@import "vars";
// Reset
@import "~materialize-css/sass/components/normalize";
// components
@import "~materialize-css/sass/components/global";
//@import "~materialize-css/sass/components/badges";
//@import "~materialize-css/sass/components/icons-material-design";
//@import "~materialize-css/sass/components/grid";
@import "grid";
@import "~materialize-css/sass/components/navbar";
@import "~materialize-css/sass/components/typography";
//@import "~materialize-css/sass/components/transitions";
@import "~materialize-css/sass/components/cards";
//@import "~materialize-css/sass/components/toast";
//@import "~materialize-css/sass/components/tabs";
//@import "~materialize-css/sass/components/tooltip";
@import "~materialize-css/sass/components/buttons";
@import "~materialize-css/sass/components/dropdown";
@import "~materialize-css/sass/components/waves";
//@import "~materialize-css/sass/components/modal";
//@import "~materialize-css/sass/components/collapsible";
//@import "~materialize-css/sass/components/chips";
//@import "~materialize-css/sass/components/materialbox";
//@import "~materialize-css/sass/components/forms/forms";
//@import "~materialize-css/sass/components/table_of_contents";
//@import "~materialize-css/sass/components/sidenav";
//@import "~materialize-css/sass/components/preloader";
//@import "~materialize-css/sass/components/slider";
//@import "~materialize-css/sass/components/carousel";
//@import "~materialize-css/sass/components/tapTarget";
//@import "~materialize-css/sass/components/pulse";
//@import "~materialize-css/sass/components/datepicker";
//@import "~materialize-css/sass/components/timepicker";
And I override some files by tagging them, copying it's contents and placing in new file and then make changes.
For those who want to import "modules", without forking/copypasting original classes.
As mentioned here: https://github.com/Dogfalo/materialize/issues/5958#issuecomment-397577854
all JS imports are working except class Component
. Webpack is just excluding it from compilation
As a workaround, you can use ProvidePlugin
and exports-loader
plugin to fix it. In webpack configuration add the following plugin usage:
plugins: [new webpack.ProvidePlugin({Component: 'exports-loader?Component!materialize-css/js/component.js'})]
(if you want to read about the plugin, please check https://webpack.js.org/guides/shimming/#shimming-globals)
Works perfectly for me, and Component class is exported automatically everywhere.
With exports-loader being updated, meaning this needs updating again, is there any chance this'll ever get fixed?
@geosenna I have done the following and works:
- In component.js replace the first line with
export default class Component {
in your modal file add at the top add:
import Component from '../path-to/component';
In the source bundle (application.js in my app) I don't import component.js again, only in the modal or whatever other 'component' you need to import it from there. Hope this helps.I've fixed my moduling problem with @MaikelGG 's answer (quoted here). I was having the
Component is not defined
error.I'm using Webpack for bundling with
babel-loader
. I simply copied thecomponent,js
andsidenav.js
from thenode-modules
folder to myassets/js
folder, and made the changes there.Then it's just importing them in myindex.js
file. I've used Materialize's Gruntfile for the import order and loaded the equivalent babel plugins in Webpack as seen in theirpackage.json
. Sample config from my project:
> index.js
:import "materialize-css/js/cash"; import "./materialize-js/component"; import "materialize-css/js/global"; import "materialize-css/js/anime.min"; import "materialize-css/js/waves"; import "./materialize-js/sidenav"; import "materialize-css/js/forms";
> ./materialize-js/component
:1. export default class Component {
> ./materialize-js/sidenav
:1. import Component from "./component";
> webpack.config.js
:use: { loader: "babel-loader", options: { presets: ["@babel/preset-env"], plugins: [ "@babel/plugin-transform-arrow-functions", "@babel/plugin-transform-block-scoping", "@babel/plugin-transform-classes", "@babel/plugin-transform-template-literals", "@babel/plugin-transform-object-super" ] } }
> package.json (fyi)
"devDependencies": { "@babel/core": "^7.1.5", "@babel/preset-env": "^7.1.5", "babel-loader": "^8.0.4", "css-loader": "^1.0.1", "materialize-css": "^1.0.0", "mini-css-extract-plugin": "^0.4.4", "node-sass": "^4.10.0", "sass-loader": "^7.1.0", "style-loader": "^0.23.0", "webpack": "^4.25.1", "webpack-cli": "^3.1.2" }
Haven't encountered any errors so far. I hope my input is useful! Cheers ~
This worked for me, but I had to make the following adjustments for working with Nextjs serverside react framework. There were issues with when things were loading in, server side vs client side.
// require instead of import (have to do this often for things that need window) if (typeof window !== 'undefined') { var cash = require ("materialize-css/js/cash"); var Component = require("../materialize/js/component.js"); var M = require("materialize-css/js/global"); var S = require('../materialize/js/sidenav'); var C = require('../materialize/js/collapsible.js'); var sidenav = document.querySelectorAll('.sidenav'); var sinstances = M.Sidenav.init(sidenav, {}); var collapisable = document.querySelectorAll('.collapsible'); var cinstances = M.Collapsible.init(collapisable, {}); } //anime.min.js had to be imported in components like sidenav.js and collapisble.js import Component from "./component"; import "./anime.min.js"; (function($, anim) {
@ChildishGiant for the exports-loader 1.1.1 I believe it should look like this:
plugins: [new webpack.ProvidePlugin({Component: 'exports-loader?type=commonjs&exports=single Component!materialize-css/js/component.js'})]
Unfortunately, I cannot test it, because I switched to vue-cli and not managing webpack plugins anymore
And actually there is another solution, without using the ProvidePlugin: before importing any materialize.js code, you could write
import Component from 'exports-loader?exports=default Component!materialize-css/js/component.js'
global.Component = Component;