Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
after upgrade Chrome browser to v. 91.0.4472.106, console show this error:
Uncaught TypeError: Cannot read property 'documentElement' of null
at Iframe.initializeIframe (Iframe.js?bust=f74493421b3bb4c9f2ea18198ca25746b5ef8a20:202)
this is content of Iframe.js:
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
/**
* Module: TYPO3/CMS/Rtehtmlarea/HTMLArea/Editor/Iframe
* The editor iframe
*/
define(['TYPO3/CMS/Rtehtmlarea/HTMLArea/UserAgent/UserAgent',
'TYPO3/CMS/Rtehtmlarea/HTMLArea/DOM/Walker',
'TYPO3/CMS/Rtehtmlarea/HTMLArea/Util/TYPO3',
'TYPO3/CMS/Rtehtmlarea/HTMLArea/Util/Util',
'TYPO3/CMS/Rtehtmlarea/HTMLArea/DOM/DOM',
'TYPO3/CMS/Rtehtmlarea/HTMLArea/Event/Event',
'TYPO3/CMS/Rtehtmlarea/HTMLArea/Event/KeyMap'],
function (UserAgent, Walker, Typo3, Util, Dom, Event, KeyMap) {
/**
* Editor iframe constructor
*
* #param {Object} config
* #constructor
* #exports TYPO3/CMS/Rtehtmlarea/HTMLArea/Editor/Iframe
*/
var Iframe = function (config) {
Util.apply(this, config);
};
Iframe.prototype = {
/**
* Render the iframe (called by framework rendering)
*
* #param object container: the container into which to insert the iframe (that is the framework)
* #return void
*/
render: function (container) {
this.config = this.getEditor().config;
this.createIframe(container);
if (!this.config.showStatusBar) {
Dom.addClass(this.getEl(), 'noStatusBar');
}
this.initStyleChangeEventListener();
if (UserAgent.isOpera) {
var self = this;
Event.one(this.getEl(), 'load', function (event) { self.initializeIframe(); return true; })
} else {
this.initializeIframe();
}
},
/**
* Get the element to which the iframe is rendered
*/
getEl: function () {
return this.el;
},
/**
* The editor iframe may become hidden with style.display = "none" on some parent div
* This breaks the editor in Firefox: the designMode attribute needs to be reset after the style.display of the container div is reset to "block"
* In all browsers, it breaks the evaluation of the framework dimensions
*/
initStyleChangeEventListener: function () {
if (this.isNested) {
if (typeof MutationObserver === 'function') {
var self = this;
this.mutationObserver = new MutationObserver( function (mutations) { self.onNestedShowMutation(mutations); });
var options = {
attributes: true,
attributeFilter: ['class', 'style']
};
for (var i = this.nestedParentElements.sorted.length; --i >= 0;) {
var nestedElement = document.getElementById(this.nestedParentElements.sorted[i]);
this.mutationObserver.observe(nestedElement, options);
this.mutationObserver.observe(nestedElement.parentNode, options);
}
} else {
this.initMutationEventsListeners();
}
}
},
/**
* When Mutation Observer is not available, listen to DOMAttrModified events
*/
initMutationEventsListeners: function () {
var self = this;
var options = {
delay: 50
};
for (var i = this.nestedParentElements.sorted.length; --i >= 0;) {
var nestedElement = document.getElementById(this.nestedParentElements.sorted[i]);
Event.on(
nestedElement,
'DOMAttrModified',
function (event) { return self.onNestedShow(event); },
options
);
Event.on(
nestedElement.parentNode,
'DOMAttrModified',
function (event) { return self.onNestedShow(event); },
options
);
}
},
/**
* editorId should be set in config
*/
editorId: null,
/**
* Get a reference to the editor
*/
getEditor: function () {
return RTEarea[this.editorId].editor;
},
/**
* Get a reference to the toolbar
*/
getToolbar: function () {
return this.framework.toolbar;
},
/**
* Get a reference to the statusBar
*/
getStatusBar: function () {
return this.framework.statusBar;
},
/**
* Get a reference to a button
*/
getButton: function (buttonId) {
return this.getToolbar().getButton(buttonId);
},
/**
* Flag set to true when the iframe becomes usable for editing
*/
ready: false,
/**
* Create the iframe element at rendering time
*
* #param object container: the container into which to insert the iframe (that is the framework)
* #return void
*/
createIframe: function (container) {
if (this.autoEl && this.autoEl.tag) {
this.el = document.createElement(this.autoEl.tag);
if (this.autoEl.id) {
this.el.setAttribute('id', this.autoEl.id);
}
if (this.autoEl.cls) {
this.el.setAttribute('class', this.autoEl.cls);
}
if (this.autoEl.src) {
this.el.setAttribute('src', this.autoEl.src);
}
this.el = container.appendChild(this.el);
}
},
/**
* Get the content window of the iframe
*/
getIframeWindow: function () {
return this.el.contentWindow ? this.el.contentWindow : this.el.contentDocument;
},
/**
* Proceed to build the iframe document head and ensure style sheets are available after the iframe document becomes available
*/
initializeIframe: function () {
var self = this;
var iframe = this.getEl();
// All browsers
if (!iframe || (!iframe.contentWindow && !iframe.contentDocument)) {
window.setTimeout(function () {
self.initializeIframe();
}, 50);
// All except WebKit
} else if (iframe.contentWindow && !UserAgent.isWebKit && (!iframe.contentWindow.document || !iframe.contentWindow.document.documentElement)) {
window.setTimeout(function () {
self.initializeIframe();
}, 50);
// WebKit
} else if (UserAgent.isWebKit && (!iframe.contentDocument.documentElement || !iframe.contentDocument.body)) {
window.setTimeout(function () {
self.initializeIframe();
}, 50);
} else {
this.document = iframe.contentWindow ? iframe.contentWindow.document : iframe.contentDocument;
this.getEditor().document = this.document;
this.createHead();
// Style the document body
Dom.addClass(this.document.body, 'htmlarea-content-body');
// Start listening to things happening in the iframe
// For some unknown reason, this is too early for Opera
if (!UserAgent.isOpera) {
this.startListening();
}
// Hide the iframe
this.hide();
// Set iframe ready
this.ready = true;
/**
* #event HTMLAreaEventIframeReady
* Fires when the iframe style sheets become accessible
*/
Event.trigger(this, 'HTMLAreaEventIframeReady');
}
},
/**
* Show the iframe
*/
show: function () {
this.getEl().style.display = '';
Event.trigger(this, 'HTMLAreaEventIframeShow');
},
/**
* Hide the iframe
*/
hide: function () {
this.getEl().style.display = 'none';
},
/**
* Build the iframe document head
*/
createHead: function () {
var head = this.document.getElementsByTagName('head')[0];
if (!head) {
head = this.document.createElement('head');
this.document.documentElement.appendChild(head);
}
if (this.config.baseURL) {
var base = this.document.getElementsByTagName('base')[0];
if (!base) {
base = this.document.createElement('base');
base.href = this.config.baseURL;
head.appendChild(base);
}
this.getEditor().appendToLog('HTMLArea.Iframe', 'createHead', 'Iframe baseURL set to: ' + base.href, 'info');
}
var link0 = this.document.getElementsByTagName('link')[0];
if (!link0) {
link0 = this.document.createElement('link');
link0.rel = 'stylesheet';
link0.type = 'text/css';
link0.href = this.config.editedContentStyle;
head.appendChild(link0);
this.getEditor().appendToLog('HTMLArea.Iframe', 'createHead', 'Skin CSS set to: ' + link0.href, 'info');
}
var pageStyle;
for (var i = 0, n = this.config.pageStyle.length; i < n; i++) {
pageStyle = this.config.pageStyle[i];
var link = this.document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = pageStyle;
head.appendChild(link);
this.getEditor().appendToLog('HTMLArea.Iframe', 'createHead', 'Content CSS set to: ' + link.href, 'info');
}
},
/**
* Focus on the iframe
*/
focus: function () {
try {
if (UserAgent.isWebKit) {
this.getEl().focus();
}
this.getEl().contentWindow.focus();
} catch(e) { }
},
/**
* Flag indicating whether the framework is inside a tab or inline element that may be hidden
* Should be set in config
*/
isNested: false,
/**
* All nested tabs and inline levels in the sorting order they were applied
* Should be set in config
*/
nestedParentElements: {},
/**
* Set designMode
*
* #param boolean on: if true set designMode to on, otherwise set to off
*
* #rturn void
*/
setDesignMode: function (on) {
if (on) {
if (!UserAgent.isIE) {
if (UserAgent.isGecko) {
// In Firefox, we can't set designMode when we are in a hidden TYPO3 tab or inline element
if (!this.isNested || Typo3.allElementsAreDisplayed(this.nestedParentElements.sorted)) {
this.document.designMode = 'on';
this.setOptions();
}
} else {
this.document.designMode = 'on';
this.setOptions();
}
}
if (UserAgent.isIE || UserAgent.isWebKit) {
this.document.body.contentEditable = true;
}
} else {
if (!UserAgent.isIE) {
this.document.designMode = 'off';
}
if (UserAgent.isIE || UserAgent.isWebKit) {
this.document.body.contentEditable = false;
}
}
},
/**
* Set editing mode options (if we can... raises exception in Firefox 3)
*
* #return void
*/
setOptions: function () {
if (!UserAgent.isIE) {
try {
if (this.document.queryCommandEnabled('insertBrOnReturn')) {
this.document.execCommand('insertBrOnReturn', false, this.config.disableEnterParagraphs);
}
if (this.document.queryCommandEnabled('styleWithCSS')) {
this.document.execCommand('styleWithCSS', false, this.config.useCSS);
} else if (UserAgent.isGecko && this.document.queryCommandEnabled('useCSS')) {
this.document.execCommand('useCSS', false, !this.config.useCSS);
}
if (UserAgent.isGecko) {
if (this.document.queryCommandEnabled('enableObjectResizing')) {
this.document.execCommand('enableObjectResizing', false, !this.config.disableObjectResizing);
}
if (this.document.queryCommandEnabled('enableInlineTableEditing')) {
this.document.execCommand('enableInlineTableEditing', false, (this.config.buttons.table && this.config.buttons.table.enableHandles) ? true : false);
}
}
} catch(e) {}
}
},
/**
* Mutations handler invoked when an hidden TYPO3 hidden nested tab or inline element is shown
*/
onNestedShowMutation: function (mutations) {
for (var i = mutations.length; --i >= 0;) {
var targetId = mutations[i].target.id;
if (this.nestedParentElements.sorted.indexOf(targetId) !== -1 || this.nestedParentElements.sorted.indexOf(targetId.replace('_div', '_fields')) !== -1) {
this.onNestedShowAction();
}
}
},
/**
* Handler invoked when an hidden TYPO3 hidden nested tab or inline element is shown
*/
onNestedShow: function (event) {
Event.stopEvent(event);
var target = event.target;
var delay = event.data.delay;
var self = this;
window.setTimeout(function () {
var styleEvent = true;
// In older versions of Gecko attrName is not set and referring to it causes a non-catchable crash
if ((UserAgent.isGecko && navigator.productSub > 2007112700) || UserAgent.isOpera || UserAgent.isIE) {
styleEvent = (event.originalEvent.attrName === 'style') || (event.originalEvent.attrName === 'className') || (event.originalEvent.attrName === 'class');
}
if (styleEvent && (self.nestedParentElements.sorted.indexOf(target.id) != -1 || self.nestedParentElements.sorted.indexOf(target.id.replace('_div', '_fields')) != -1)) {
self.onNestedShowAction();
}
}, delay);
return false;
},
/**
* Take action when nested tab or inline element is shown
*/
onNestedShowAction: function () {
// Check if all container nested elements are displayed
if (Typo3.allElementsAreDisplayed(this.nestedParentElements.sorted)) {
if (this.getEditor().getMode() === 'wysiwyg') {
if (UserAgent.isGecko) {
this.setDesignMode(true);
}
Event.trigger(this, 'HTMLAreaEventIframeShow');
} else {
Event.trigger(this.framework.getTextAreaContainer(), 'HTMLAreaEventTextAreaContainerShow');
}
this.getToolbar().update();
}
},
/**
* Instance of DOM walker
*/
htmlRenderer: null,
/**
* Getter for the instance of DOM walker
*/
getHtmlRenderer: function () {
if (!this.htmlRenderer) {
this.htmlRenderer = new Walker({
keepComments: !this.config.htmlRemoveComments,
removeTags: this.config.htmlRemoveTags,
removeTagsAndContents: this.config.htmlRemoveTagsAndContents,
baseUrl: this.config.baseURL
});
}
return this.htmlRenderer;
},
/**
* Get the HTML content of the iframe
*/
getHTML: function () {
return this.getHtmlRenderer().render(this.document.body, false);
},
/**
* Start listening to things happening in the iframe
*/
startListening: function () {
var self = this;
// Create keyMap so that plugins may bind key handlers
this.keyMap = new KeyMap(this.document.documentElement, (UserAgent.isIE || UserAgent.isWebKit) ? 'keydown' : 'keypress');
// Special keys map
this.keyMap.addBinding(
{
key: [Event.DOWN, Event.UP, Event.LEFT, Event.RIGHT],
alt: false,
handler: function (event) { return self.onArrow(event); }
}
);
this.keyMap.addBinding(
{
key: Event.TAB,
ctrl: false,
alt: false,
handler: function (event) { return self.onTab(event); }
}
);
this.keyMap.addBinding(
{
key: Event.SPACE,
ctrl: true,
shift: false,
alt: false,
handler: function (event) { return self.onCtrlSpace(event); }
}
);
if (UserAgent.isGecko || UserAgent.isIE || UserAgent.isWebKit) {
this.keyMap.addBinding(
{
key: [Event.BACKSPACE, Event.DELETE],
alt: false,
handler: function (event) { return self.onBackSpace(event); }
});
}
if (!UserAgent.isIE && !this.config.disableEnterParagraphs) {
this.keyMap.addBinding(
{
key: Event.ENTER,
shift: false,
handler: function (event) { return self.onEnter(event); }
});
}
if (UserAgent.isWebKit) {
this.keyMap.addBinding(
{
key: Event.ENTER,
alt: false,
handler: function (event) { return self.onWebKitEnter(event); }
});
}
// Hot key map (on keydown for all browsers)
var hotKeys = [];
for (var key in this.config.hotKeyList) {
if (key.length === 1) {
hotKeys.push(key);
}
}
// Make hot key map available, even if empty, so that plugins may add bindings
this.hotKeyMap = new KeyMap(this.document.documentElement, 'keydown');
if (hotKeys.length > 0) {
this.hotKeyMap.addBinding({
key: hotKeys,
ctrl: true,
shift: false,
alt: false,
handler: function (event) { return self.onHotKey(event); }
});
}
Event.on(
this.document.documentElement,
(UserAgent.isIE || UserAgent.isWebKit) ? 'keydown' : 'keypress',
function (event) { return self.onAnyKey(event); }
);
Event.on(
this.document.documentElement,
'mouseup',
function (event) { return self.onMouse(event); }
);
Event.on(
this.document.documentElement,
'click',
function (event) { return self.onMouse(event); }
);
if (UserAgent.isGecko) {
Event.on(
this.document.documentElement,
'paste',
function (event) { return self.onPaste(event); }
);
}
Event.on(
this.document.documentElement,
'drop',
function (event) { return self.onDrop(event); }
);
if (UserAgent.isWebKit) {
Event.on(
this.document.body,
'dragend',
function (event) { return self.onDrop(event); }
);
}
},
/**
* Handler for other key events
*/
onAnyKey: function (event) {
if (this.inhibitKeyboardInput(event)) {
return false;
}
/**
* #event HTMLAreaEventWordCountChange
* Fires when the word count may have changed
*/
Event.trigger(this, 'HTMLAreaEventWordCountChange', [100]);
if (!event.altKey && !(event.ctrlKey || event.metaKey)) {
var key = Event.getKey(event);
// Detect URL in non-IE browsers
if (!UserAgent.isIE && (key !== Event.ENTER || (event.shiftKey && !UserAgent.isWebKit))) {
this.getEditor().getSelection().detectURL(event);
}
// Handle option+SPACE for Mac users
if (UserAgent.isMac && key === Event.NON_BREAKING_SPACE) {
return this.onOptionSpace(key, event);
}
}
return true;
},
/**
* On any key input event, check if input is currently inhibited
*/
inhibitKeyboardInput: function (event) {
// Inhibit key events while server-based cleaning is being processed
if (this.getEditor().inhibitKeyboardInput) {
Event.stopEvent(event);
return true;
} else {
return false;
}
},
/**
* Handler for mouse events
*/
onMouse: function (event) {
// In WebKit, select the image when it is clicked
if (UserAgent.isWebKit && /^(img)$/i.test(event.target.nodeName) && event.type === 'click') {
this.getEditor().getSelection().selectNode(event.target);
}
this.getToolbar().updateLater(100);
return true;
},
/**
* Handler for paste operations in Gecko
*/
onPaste: function (event) {
// Make src and href urls absolute
if (UserAgent.isGecko) {
var self = this;
window.setTimeout(function () {
Dom.makeUrlsAbsolute(self.getEditor().document.body, self.config.baseURL, self.getHtmlRenderer());
}, 50);
}
return true;
},
/**
* Handler for drag and drop operations
*/
onDrop: function (event) {
var self = this;
// Clean up span elements added by WebKit
if (UserAgent.isWebKit) {
window.setTimeout(function () {
self.getEditor().getDomNode().cleanAppleStyleSpans(self.getEditor().document.body);
}, 50);
}
// Make src url absolute in Firefox
if (UserAgent.isGecko) {
window.setTimeout(function () {
Dom.makeUrlsAbsolute(event.target, self.config.baseURL, self.getHtmlRenderer());
}, 50);
}
this.getToolbar().updateLater(100);
return true;
},
/**
* Handler for UP, DOWN, LEFT and RIGHT arrow keys
*/
onArrow: function (event) {
this.getToolbar().updateLater(100);
return true;
},
/**
* Handler for TAB and SHIFT-TAB keys
*
* If available, BlockElements plugin will handle the TAB key
*/
onTab: function (event) {
if (this.inhibitKeyboardInput(event)) {
return false;
}
var keyName = (event.shiftKey ? 'SHIFT-' : '') + 'TAB';
if (this.config.hotKeyList[keyName] && this.config.hotKeyList[keyName].cmd) {
var button = this.getButton(this.config.hotKeyList[keyName].cmd);
if (button) {
Event.stopEvent(event);
/**
* #event HTMLAreaEventHotkey
* Fires when the button hotkey is pressed
*/
Event.trigger(button, 'HTMLAreaEventHotkey', [keyName, event]);
return false;
}
}
return true;
},
/**
* Handler for BACKSPACE and DELETE keys
*/
onBackSpace: function (event) {
if (this.inhibitKeyboardInput(event)) {
return false;
}
if ((!UserAgent.isIE && !event.shiftKey) || UserAgent.isIE) {
if (this.getEditor().getSelection().handleBackSpace()) {
Event.stopEvent(event);
return false;
}
}
// Update the toolbar state after some time
this.getToolbar().updateLater(200);
return true;
},
/**
* Handler for ENTER key in non-IE browsers
*/
onEnter: function (event) {
if (this.inhibitKeyboardInput(event)) {
return false;
}
this.getEditor().getSelection().detectURL(event);
if (this.getEditor().getSelection().checkInsertParagraph()) {
Event.stopEvent(event);
// Update the toolbar state after some time
this.getToolbar().updateLater(200);
return false;
}
// Update the toolbar state after some time
this.getToolbar().updateLater(200);
return true;
},
/**
* Handler for ENTER key in WebKit browsers
*/
onWebKitEnter: function (event) {
if (this.inhibitKeyboardInput(event)) {
return false;
}
if (event.shiftKey || this.config.disableEnterParagraphs) {
var editor = this.getEditor();
editor.getSelection().detectURL(event);
if (UserAgent.isSafari) {
var brNode = editor.document.createElement('br');
editor.getSelection().insertNode(brNode);
brNode.parentNode.normalize();
// Selection issue when an URL was detected
if (editor._unlinkOnUndo) {
brNode = brNode.parentNode.parentNode.insertBefore(brNode, brNode.parentNode.nextSibling);
}
if (!brNode.nextSibling || !/\S+/i.test(brNode.nextSibling.textContent)) {
var secondBrNode = editor.document.createElement('br');
secondBrNode = brNode.parentNode.appendChild(secondBrNode);
}
editor.getSelection().selectNode(brNode, false);
Event.stopEvent(event);
// Update the toolbar state after some time
this.getToolbar().updateLater(200);
return false;
}
}
// Update the toolbar state after some time
this.getToolbar().updateLater(200);
return true;
},
/**
* Handler for CTRL-SPACE keys
*/
onCtrlSpace: function (event) {
if (this.inhibitKeyboardInput(event)) {
return false;
}
this.getEditor().getSelection().insertHtml(' ');
Event.stopEvent(event);
return false;
},
/**
* Handler for OPTION-SPACE keys on Mac
*/
onOptionSpace: function (key, event) {
if (this.inhibitKeyboardInput(event)) {
return false;
}
this.getEditor().getSelection().insertHtml(' ');
Event.stopEvent(event);
return false;
},
/**
* Handler for configured hotkeys
*/
onHotKey: function (event) {
var key = Event.getKey(event);
if (this.inhibitKeyboardInput(event)) {
return false;
}
var hotKey = String.fromCharCode(key).toLowerCase();
/**
* #event HTMLAreaEventHotkey
* Fires when the button hotkey is pressed
*/
Event.trigger(this.getButton(this.config.hotKeyList[hotKey].cmd), 'HTMLAreaEventHotkey', [hotKey, event]);
return false;
},
/**
* Cleanup (called by framework)
*/
onBeforeDestroy: function () {
// Remove listeners on nested elements
if (this.isNested) {
if (this.mutationObserver) {
this.mutationObserver.disconnect();
} else {
for (var i = this.nestedParentElements.sorted.length; --i >= 0;) {
var nestedElement = document.getElementById(this.nestedParentElements.sorted[i]);
Event.off(nestedElement);
Event.off(nestedElement.parentNode);
}
}
}
Event.off(this);
Event.off(this.getEl());
Event.off(this.document.body);
Event.off(this.document.documentElement);
// Cleaning references to DOM in order to avoid IE memory leaks
this.document = null;
this.el = null;
}
};
return Iframe;
});
error on line 202 by documentElement || !iframe.contentDocument.body)) {:
} else if (UserAgent.isWebKit && (!iframe.contentDocument.documentElement || !iframe.contentDocument.body)) {
How i can fix it ?
I had the same problem and found the bugfix. In old versions, the iframe source was initialized with about:blank in the rtehtmlarea extension, this is the root cause for the problem you are facing. There was an official bugfix 2 years ago - link to Github: https://github.com/FriendsOfTYPO3/rtehtmlarea/commit/a20e23445ca760ba94ed06dca05266b6e22a25fb
You can backport the fix or update the extension, then it is working as expected in the new Chrome version.
Apparently you're using a TYPO3 version prior to v8.7 with rtehtmlarea which is outdated and unsupported since quite a while. With TYPO3 v8.7 ckeditor was introduced.
Maybe this StackOverflow answer helps you a little bit. Otherwise just use Firefox.
But you're strongly advised to upgrade your TYPO3 installation to the latest version v10.4.
I installed Shuffle.js from a codepen demo with some customizations to style and figure cards. I've added in recommended js code but the shuffle function doesn't seem work.
Getting several errors:
console errors
I've tried updating script based on some past answers to this problem. I've also downloaded and added the actual js file on my site and referenced it in the script. Here's what my script looks like right now:
<script src="/v/vspfiles/assets/js/shuffle.js"></script>
<script>
'use strict';
var Shuffle = window.shuffle;
var Demo = function (element) {
this.element = element;
// Log out events.
this.addShuffleEventListeners();
this.shuffle = new Shuffle(element, {
itemSelector: '.picture-item',
sizer: element.querySelector('.my-sizer-element'),
});
this._activeFilters = [];
this.addFilterButtons();
this.addSorting();
this.addSearchFilter();
this.mode = 'exclusive';
};
Demo.prototype.toArray = function (arrayLike) {
return Array.prototype.slice.call(arrayLike);
};
Demo.prototype.toggleMode = function () {
if (this.mode === 'additive') {
this.mode = 'exclusive';
} else {
this.mode = 'additive';
}
};
/**
* Shuffle uses the CustomEvent constructor to dispatch events. You can listen
* for them like you normally would (with jQuery for example). The extra event
* data is in the `detail` property.
*/
Demo.prototype.addShuffleEventListeners = function () {
var handler = function (event) {
console.log('type: %s', event.type, 'detail:', event.detail);
};
this.element.addEventListener(Shuffle.EventType.LAYOUT, handler, false);
this.element.addEventListener(Shuffle.EventType.REMOVED, handler, false);
};
Demo.prototype.addFilterButtons = function () {
var options = document.querySelector('.filter-options');
if (!options) {
return;
}
var filterButtons = this.toArray(
options.children
);
filterButtons.forEach(function (button) {
button.addEventListener('click', this._handleFilterClick.bind(this), false);
}, this);
};
Demo.prototype._handleFilterClick = function (evt) {
var btn = evt.currentTarget;
var isActive = btn.classList.contains('active');
var btnGroup = btn.getAttribute('data-group');
// You don't need _both_ of these modes. This is only for the demo.
// For this custom 'additive' mode in the demo, clicking on filter buttons
// doesn't remove any other filters.
if (this.mode === 'additive') {
// If this button is already active, remove it from the list of filters.
if (isActive) {
this._activeFilters.splice(this._activeFilters.indexOf(btnGroup));
} else {
this._activeFilters.push(btnGroup);
}
btn.classList.toggle('active');
// Filter elements
this.shuffle.filter(this._activeFilters);
// 'exclusive' mode lets only one filter button be active at a time.
} else {
this._removeActiveClassFromChildren(btn.parentNode);
var filterGroup;
if (isActive) {
btn.classList.remove('active');
filterGroup = Shuffle.ALL_ITEMS;
} else {
btn.classList.add('active');
filterGroup = btnGroup;
}
this.shuffle.filter(filterGroup);
}
};
Demo.prototype._removeActiveClassFromChildren = function (parent) {
var children = parent.children;
for (var i = children.length - 1; i >= 0; i--) {
children[i].classList.remove('active');
}
};
Demo.prototype.addSorting = function () {
var menu = document.querySelector('.sort-options');
if (!menu) {
return;
}
menu.addEventListener('change', this._handleSortChange.bind(this));
};
Demo.prototype._handleSortChange = function (evt) {
var value = evt.target.value;
var options = {};
function sortByDate(element) {
return element.getAttribute('data-created');
}
function sortByTitle(element) {
return element.getAttribute('data-title').toLowerCase();
}
if (value === 'date-created') {
options = {
reverse: true,
by: sortByDate,
};
} else if (value === 'title') {
options = {
by: sortByTitle,
};
}
this.shuffle.sort(options);
};
// Advanced filtering
Demo.prototype.addSearchFilter = function () {
var searchInput = document.querySelector('.js-shuffle-search');
if (!searchInput) {
return;
}
searchInput.addEventListener('keyup', this._handleSearchKeyup.bind(this));
};
/**
* Filter the shuffle instance by items with a title that matches the search input.
* #param {Event} evt Event object.
*/
Demo.prototype._handleSearchKeyup = function (evt) {
var searchText = evt.target.value.toLowerCase();
this.shuffle.filter(function (element, shuffle) {
// If there is a current filter applied, ignore elements that don't match it.
if (shuffle.group !== Shuffle.ALL_ITEMS) {
// Get the item's groups.
var groups = JSON.parse(element.getAttribute('data-groups'));
var isElementInCurrentGroup = groups.indexOf(shuffle.group) !== -1;
// Only search elements in the current group
if (!isElementInCurrentGroup) {
return false;
}
}
var titleElement = element.querySelector('.picture-item__title');
var titleText = titleElement.textContent.toLowerCase().trim();
return titleText.indexOf(searchText) !== -1;
});
};
document.addEventListener('DOMContentLoaded', function () {
window.demo = new Demo(document.getElementById('grid'));
});
</script>
Any insight into what I need to remedy to get this working properly would be great. Thanks!
I have below code for dragscroll which runs automatically when page load.
But I want to call this function manually because I'm uppending the html using DOM element.
I have tried with window.factory as well as window.reset but it is not working for me.
My Javascript module file:
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(['exports'], factory);
} else if (typeof exports !== 'undefined') {
factory(exports);
} else {
factory((root.dragscroll = {}));
}
}(this, function (exports) {
var _window = window;
var _document = document;
var mousemove = 'mousemove';
var mouseup = 'mouseup';
var mousedown = 'mousedown';
var EventListener = 'EventListener';
var addEventListener = 'add'+EventListener;
var removeEventListener = 'remove'+EventListener;
var newScrollX, newScrollY;
var dragged = [];
var reset = function(i, el) {
for (i = 0; i < dragged.length;)
{
<some code>
}
// cloning into array since HTMLCollection is updated dynamically
dragged = [].slice.call(_document.getElementsByClassName('dragscroll'));
for (i = 0; i < dragged.length;)
{
<some code>
}, 0
);
_window[addEventListener](
mouseup, cont.mu = function() {pushed = 0;}, 0
);
_window[addEventListener](
mousemove,
cont.mm = function(e) {
if (pushed) {
(scroller = el.scroller||el).scrollLeft -=
newScrollX = (- lastClientX + (lastClientX=e.clientX));
scroller.scrollTop -=
newScrollY = (- lastClientY + (lastClientY=e.clientY));
if (el == _document.body) {
(scroller = _document.documentElement).scrollLeft -= newScrollX;
scroller.scrollTop -= newScrollY;
}
}
}, 0
);
})(dragged[i++]);
}
}
if (_document.readyState == 'complete') {
reset();
} else {
_window[addEventListener]('load', reset, 0);
}
exports.reset = reset;
}));
})
I want to call above function manually.
Can you try with
window.dragscroll.reset();
this will call your reset function manually.
Let me know if it helps.
This looks like a standard JavaScript module that exports a reset() method. Depending on your environment you can load this module, e.g with CommonJS it would be
const module = require('name/path/of_your_module_file');
and then to call it when the DOM load is complete with jQuery:
$(() => {
module.reset();
});
When searching for a way to warn a user before leaving web page if changes haven't been saved, I found this solution: https://stackoverflow.com/a/48238659/9512437, but the warning pops up even if the user is hitting the save button. I tried adding an event when the user clicks the submit button to set a variable to keep the warning from appearing, but now the warning never appears.
Here is what I tried:
<script>
"use strict";
var btn_click = false;
(() => {
const modified_inputs = new Set;
const defaultValue = "defaultValue";
// store default values
addEventListener("beforeinput", (evt) => {
const target = evt.target;
if (!(defaultValue in target || defaultValue in target.dataset)) {
target.dataset[defaultValue] = ("" + (target.value || target.textContent)).trim();
}
});
// detect input modifications
addEventListener("input", (evt) => {
const target = evt.target;
let original;
if (defaultValue in target) {
original = target[defaultValue];
} else {
original = target.dataset[defaultValue];
}
if (original !== ("" + (target.value || target.textContent)).trim()) {
if (!modified_inputs.has(target)) {
modified_inputs.add(target);
}
} else if (modified_inputs.has(target)) {
modified_inputs.delete(target);
}
});
addEventListener("beforeunload", (evt) => {
if (modified_inputs.size && !btn_click) {
const unsaved_changes_warning = "Changes you made may not be saved.";
evt.returnValue = unsaved_changes_warning;
return unsaved_changes_warning;
}
});
addEventListener("")
})();
document.getElementById("submit").onclick = function save() {
btn_click = true;
}
</script>
Any idea what I did wrong?
Turns out I was doing a couple things wrong. My guess is adding the document.getElementById("submit").onclick under "use strict" either caused an error (https://www.w3schools.com/js/js_strict.asp) or caused a problem with the detection since simply adding the function even without the && !btn_click caused it to not work. I also had to change if (modified_inputs.size && !btn_click) { to if (modified_inputs.size >> 0 && !btn_click) {.
In the end, the solution that ended up working for me is as follows:
<script>
var btn_click = false;
function save() {
btn_click = true;
}
"use strict";
(() => {
const modified_inputs = new Set;
const defaultValue = "defaultValue";
// store default values
addEventListener("beforeinput", (evt) => {
const target = evt.target;
if (!(defaultValue in target || defaultValue in target.dataset)) {
target.dataset[defaultValue] = ("" + (target.value || target.textContent)).trim();
}
});
// detect input modifications
addEventListener("input", (evt) => {
const target = evt.target;
let original;
if (defaultValue in target) {
original = target[defaultValue];
} else {
original = target.dataset[defaultValue];
}
if (original !== ("" + (target.value || target.textContent)).trim()) {
if (!modified_inputs.has(target)) {
modified_inputs.add(target);
}
} else if (modified_inputs.has(target)) {
modified_inputs.delete(target);
}
});
addEventListener("beforeunload", (evt) => {
if (modified_inputs.size >> 0 && !btn_click) {
const unsaved_changes_warning = "Changes you made may not be saved.";
evt.returnValue = unsaved_changes_warning;
return unsaved_changes_warning;
}
});
addEventListener("")
})();
</script>
Then add your onclick to the element:
<button type="submit" class="w3-button w3-right w3-theme" id="button" onclick="save()">Save</button>
I have an eventlistener that looks like this:
window.addEventListener('scroll', scroll.throttle(
triggered,
{state: state, wrapper: wrapper, children: children, scroll: scroll},
50
));
And I have a class that looks like this:
Scroll = class{
constructor(){
this.on = true;
}
throttle(fn, v, wait){
var time = Date.now();
return () => {
if ((time + wait - Date.now()) < 0 && this.on) {
fn(v);
time = Date.now();
}
}
}
triggered(o){
if(o.state.check !== 0){
o.scroll.on = false;
o.wrapper.classList.toggle('flic-down', o.state.check === 1)
o.wrapper.classList.toggle('flic-up', o.state.check === -1)
o.state.update();
o.wrapper.classList.add('flic-transition')
setTimeout(()=>{this.changeDone(o)}, 1200);
}
}
changeDone(o) {
o.wrapper.classList.remove('flic-transition', 'flic-up', 'flic-down');
o.children.setClasses(o.state.state);
o.wrapper.getElementsByClassName('flic-active')[0].scrollIntoView(true);
o.scroll.on = true;
}
},
I don't like passing state, wrapper, children and scroll as variables. I would prefer to store them in the class when instantiating them. I understand the problem is that "this" won't be passed correctly and that it can be bound. But because the throttle function I don't understand how to pass this.
I would recommend to separate the throttling from the scrolling.
class Throttle {
constructor() {
this.on = true;
}
get(fn, wait) {
var time = Date.now();
return (...args) => {
if ((time + wait - Date.now()) < 0 && this.on) {
fn(...args);
time = Date.now();
}
}
}
}
class Scroll {
constructor(state, wrapper, children) {
this.state = state;
this.wrapper = wrapper;
this.children = children;
this.throttle = new Throttle();
}
triggered() {
if (this.state.check !== 0) {
this.throttle.on = false;
this.wrapper.classList.toggle('flic-down', this.state.check === 1)
this.wrapper.classList.toggle('flic-up', this.state.check === -1)
this.state.update();
this.wrapper.classList.add('flic-transition')
setTimeout(()=>{this.changeDone()}, 1200);
}
}
changeDone() {
this.wrapper.classList.remove('flic-transition', 'flic-up', 'flic-down');
this.children.setClasses(this.state.state);
this.wrapper.getElementsByClassName('flic-active')[0].scrollIntoView(true);
this.throttle.on = true;
}
}
You then would do
const scroll = new Scroll(state, wrapper, children);
window.addEventListener('scroll', scroll.throttle.get(() => scroll.triggered(), 50));
Notice the arrow function being passed to throttle.get that calls triggered on the scroll instance, instead of passing the method without a context.
If you want to mix them both in the same class, I don't see why throttle would take a fn and v as parameters. You're only using it to call triggered anyway:
class ThrottledScroll {
constructor(state, wrapper, children) {
this.state = state;
this.wrapper = wrapper;
this.children = children;
this.throttle = new Throttle();
this.on = true;
}
get(wait) {
var time = Date.now();
return () => {
if ((time + wait - Date.now()) < 0 && this.on) {
this.triggered();
time = Date.now();
}
}
}
triggered() {
if (this.state.check !== 0) {
this.on = false;
this.wrapper.classList.toggle('flic-down', this.state.check === 1)
this.wrapper.classList.toggle('flic-up', this.state.check === -1)
this.state.update();
this.wrapper.classList.add('flic-transition')
setTimeout(()=>{this.changeDone()}, 1200);
}
}
changeDone() {
this.wrapper.classList.remove('flic-transition', 'flic-up', 'flic-down');
this.children.setClasses(this.state.state);
this.wrapper.getElementsByClassName('flic-active')[0].scrollIntoView(true);
this.on = true;
}
}
with
const scroll = new ThrottledScroll(state, wrapper, children);
window.addEventListener('scroll', scroll.get(50));
In your class constructor, you can bind your class methods' context to the class with this.methodName = this.methodName.bind(this). ReyHaynes is right, this can also be accomplished by defining class methods as arrow functions, but I believe that's still an uncomfirmed ES7 feature, so you might not want to use that.
If that's unclear or unhelpful, you might find some helpful context here. It's a React resource, but, if I understand correctly, your problem is one dealt with in React all the time.
Adding the bind to the constructor worked for the class, that handles the binding issue when called out of context:
Scroll = class{
constructor(){
this.on = true;
this.triggered = this.triggered.bind(this)
this.changeDone = this.changeDone.bind(this)
}
throttle(fn, v, wait){
var time = Date.now();
return () => {
if ((time + wait - Date.now()) < 0 && this.on) {
fn(v);
time = Date.now();
}
}
}
triggered(o){
if(o.state.check !== 0){
o.scroll.on = false;
o.wrapper.classList.toggle('flic-down', o.state.check === 1)
o.wrapper.classList.toggle('flic-up', o.state.check === -1)
o.state.update();
o.wrapper.classList.add('flic-transition')
setTimeout(()=>{this.changeDone(o)}, 1200);
}
}
changeDone(o) {
o.wrapper.classList.remove('flic-transition', 'flic-up', 'flic-down');
o.children.setClasses(o.state.state);
o.wrapper.getElementsByClassName('flic-active')[0].scrollIntoView(true);
o.scroll.on = true;
}
}
The event listener needed to have scroll.triggered vs triggered unless you deconstructed (const { triggered } = scroll) before it and we didn't see that:
window.addEventListener('scroll', scroll.throttle(
scroll.triggered,
{state: state, wrapper: wrapper, children: children, scroll: scroll},
50
))