Error using this.$el.queryselector on mounted() - javascript

So I'm very new to using vue and currently I'm working on my first project using vue on it and I'm at a loss. So let me explain..
First my markup. In my markup I added a <template v-if="data"></template> and all of the markup is wrapped in there. Here's what it looks like:
<div class="main-template">
<template v-if="data">
<!-- markup -->
</template>
</div
Now there are there is an image slider inside my template and I want to initialize the slider inside my vue instance, so based on my research I did something like this:
methods: {
sliderInit(){
const ImgSlider = this.$el.querySelector('.slider-container'),
var flikty = new Flickity(ImgSlider, {
contain: true,
imagesLoaded: true,
cellAlign: 'left',
pageDots: false,
arrowShape: 'M 15,50 L 65,100 L 70,95 L 25,50 L 70,5 L 65,0 Z',
lazyLoad: 5 }
)
}
},
mounted() {
this.sliderInit()
}
But I get an error saying that Bad element for Flikity: null. So I tried console logging my ImgSlider and it turns out it returns null. I've tried other solutions such as using watch: and this.$nextTick() but ImgSlider always logs as null.
Now I did solve it once using update() and setTimeout(() => {}, timeout) they worked but update reinitializes the slider on every state change and setTimeout isn't ideal. It's not ideal because besides initializing the slider I need to add events using Flikity's API, and if it's not even possible for me to normally select elements in my vue instance using querySelector then I wouldn't able to code the other events properly, that's why just using setTimeout isn't ideal.
So to be more specific, how can I be able to use querySelector properly? Is there anything wrong in my code that prevents me from using querySelector on this.$el? If I can make the query selector run properly on mounted then that would already be a big help, I can at least take it from there. Thanks! :D

you can use ref instead
<div ref="mainTemplate" class="main-template">
<template v-if="data" />
</div>
...
sliderInit(){
const ImgSlider = this.$refs.mainTemplate
}

You can use double next tick for example:
this.$nextTick(() => { this.$nextTick(() => { //code }) })
Other than that you can try to use Flickity-Vue you will not need such a work around but it means other dependency so it is your choice.
Another solution is to select element with vanilla js. You can even write it like as it states in docs:
var flikty = new Flickity('.main-template', {
contain: true,
imagesLoaded: true,
cellAlign: 'left',
pageDots: false,
arrowShape: 'M 15,50 L 65,100 L 70,95 L 25,50 L 70,5 L 65,0 Z',
lazyLoad: 5 }
)
}

Related

Adding JavaScript to my Plotly Dash app (Python)

I'm building a dashboard using Dash in Python. I have configured all the graphs nicely (it's running on the server here) and the next step is to create a responsive navbar and a footer. Currently looks like this:
And when I shrink the width, it looks like this:
I want to add functionality to this button so it would hide the three links on click. I'm trying to toggle the CSS 'active' attribute using JavaScript with this piece of code:
var toggleButton = document.getElementsByClassName('toggle-button')[0]
var navBarLinks = document.getElementsByClassName('navbar-links')[0]
function toggleFunction() {
navBarLinks.classList.toggle('active')
}
toggleButton.addEventListener('click', toggleFunction)
Basically, when the navbar-links class is active, I want it to be set as display: flex, and when it's not active I want it to be display: none
The HTML elements defined in Python screen are here:
html.Nav([
html.Div('Covid-19 global data Dashboard', className='dashboard-title'),
html.A([html.Span(className='bar'),
html.Span(className='bar'),
html.Span(className='bar')],
href='#', className='toggle-button'),
html.Div(
html.Ul([
html.Li(html.A('Linked-In', href='#')),
html.Li(html.A('Source Code', href='#')),
html.Li(html.A('CSV Data', href='#'))
]),
className='navbar-links'),
], className='navbar')
I didn't expect that there would be issues with accessing elements through JavaScript. After doing some research I found out that JavaScript when executes getElementsByClassName function the returned value is null. That is because the function is run before the page is rendered (as far as I understand). It gives me this error:
This project is getting quite big, so I don't know which parts should I include in this post, but I will share the git repository and the preview of the page. Is there an easy solution to it?
You can defer the execution of JavaScript code until after React has loaded via the DeferScript component from dash-extensions. Here is a small example,
import dash
import dash_html_components as html
from html import unescape
from dash_extensions import DeferScript
mxgraph = r'{"highlight":"#0000ff","nav":true,"resize":true,"toolbar":"zoom layers lightbox","edit":"_blank","xml":"<mxfile host=\"app.diagrams.net\" modified=\"2021-06-07T06:06:13.695Z\" agent=\"5.0 (Windows)\" etag=\"4lPJKNab0_B4ArwMh0-7\" version=\"14.7.6\"><diagram id=\"YgMnHLNxFGq_Sfquzsd6\" name=\"Page-1\">jZJNT4QwEIZ/DUcToOriVVw1JruJcjDxYho60iaFIaUs4K+3yJSPbDbZSzN95qPTdyZgadm/GF7LAwrQQRyKPmBPQRzvktidIxgmwB4IFEaJCUULyNQvEAyJtkpAswm0iNqqegtzrCrI7YZxY7Dbhv2g3r5a8wLOQJZzfU4/lbByoslduPBXUIX0L0cheUrugwk0kgvsVojtA5YaRDtZZZ+CHrXzukx5zxe8c2MGKntNgknk8bs8fsj3+KtuDhxP+HZDVU5ct/RhatYOXgGDbSVgLBIG7LGTykJW83z0dm7kjklbaneLnEnlwFjoL/YZzb93WwNYgjWDC6EEdkuC0cZEO7p3i/6RF1WutL8nxmnkxVx6UcUZJIy/LgP49622mO3/AA==</diagram></mxfile>"}'
app = dash.Dash(__name__)
app.layout = html.Div([
html.Div(className='mxgraph', style={"maxWidth": "100%"}, **{'data-mxgraph': unescape(mxgraph)}),
DeferScript(src='https://viewer.diagrams.net/js/viewer-static.min.js')
])
if __name__ == '__main__':
app.run_server()
Dash callback solution (no Javascript):
import dash
import dash_html_components as html
from dash.dependencies import Output, Input, State
navbar_base_class = "navbar-links"
app = dash.Dash(__name__)
app.layout = html.Nav(
[
html.Div("Covid-19 global data Dashboard", className="dashboard-title"),
html.A(
id="toggle-button",
children=[
html.Span(className="bar"),
html.Span(className="bar"),
html.Span(className="bar"),
],
href="#",
className="toggle-button",
),
html.Div(
id="navbar-links",
children=html.Ul(
children=[
html.Li(html.A("Linked-In", href="#")),
html.Li(html.A("Source Code", href="#")),
html.Li(html.A("CSV Data", href="#")),
],
),
className=navbar_base_class,
),
],
className="navbar",
)
#app.callback(
Output("navbar-links", "className"),
Input("toggle-button", "n_clicks"),
State("navbar-links", "className"),
prevent_initial_call=True,
)
def callback(n_clicks, current_classes):
if "active" in current_classes:
return navbar_base_class
return navbar_base_class + " active"
if __name__ == "__main__":
app.run_server(debug=True)
The idea of the code above is to take the toggle-button click as Input and the current value of navbar-links as State. We can use this state to determine if we should add the active class or remove it. The new className value is returned in the callback.
Javascript solution:
window.addEventListener("load", function () {
var toggleButton = document.getElementsByClassName("toggle-button")[0];
var navBarLinks = document.getElementsByClassName("navbar-links")[0];
function toggleFunction() {
navBarLinks.classList.toggle("active");
}
toggleButton.addEventListener("click", toggleFunction);
});
The load event is fired when the whole page has loaded, including all dependent resources such as stylesheets and images. This is in contrast to DOMContentLoaded, which is fired as soon as the page DOM has been loaded, without waiting for resources to finish loading.
https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event
DOMContentLoaded would be preferable to use, but it only works for me with load.
If you need a pure JS solution you need to use MutationObserver. I've wrote a little helper function we are currently using that did the trick. Another suggestion would be to change the mutation to an element on screen then fire an event to handle the rest
/**
*
* #param {string} id
* #param {*} event
* #param {(this: HTMLElement, ev: any) => any} callback
* #param {boolean | AddEventListenerOptions} options
*/
function attachEventToDash(id, event, callback, options) {
debugger;
var observer = new MutationObserver(function (_mutations, obs) {
var ele = document.getElementById(id);
if (ele) {
debugger;
ele.addEventListener(event, callback, options)
obs.disconnect();
}
});
window.addEventListener('DOMContentLoaded', function () {
observer.observe(document, {
childList: true,
subtree: true
});
})
}

Custom Element (Web Component) won't accept keyboard input when inserted by a CKEditor 5 plugin

I'm in the initial stages of developing a plugin that will allow the user to insert placeholder elements into HTML content that will be processed server-side and used to incorporate some simple logic into a generated PDF document. To this end, I'm attempting to insert a custom element that I've defined using the web components API.
class NSLoop extends HTMLElement {
constructor() {
super();
}
get source() {
return this.getAttribute('source');
}
get as() {
return this.getAttribute('as');
}
}
window.customElements.define('ns-loop', NSLoop);
The contents of loopediting.js:
import Plugin from "#ckeditor/ckeditor5-core/src/plugin";
import Widget from "#ckeditor/ckeditor5-widget/src/widget";
import {viewToModelPositionOutsideModelElement} from "#ckeditor/ckeditor5-widget/src/utils";
import LoopCommand from "./loopcommand";
export default class LoopEditing extends Plugin {
static get requires() {
return [Widget];
}
constructor(editor) {
super(editor);
}
init() {
this._defineSchema();
this._defineConverters();
this.editor.commands.add('loop', new LoopCommand(this.editor));
this.editor.editing.mapper.on('viewToModelPosition', viewToModelPositionOutsideModelElement(this.editor.model, viewElement => viewElement.is('element', 'ns-loop')));
}
_defineSchema() {
const schema = this.editor.model.schema;
schema.register('loop', {
isBlock: false,
isLimit: false,
isObject: false,
isInline: false,
isSelectable: false,
isContent: false,
allowWhere: '$block',
allowAttributes: ['for', 'as'],
});
schema.extend( '$text', {
allowIn: 'loop'
} );
schema.extend( '$block', {
allowIn: 'loop'
} );
}
_defineConverters() {
const conversion = this.editor.conversion;
conversion.for('upcast').elementToElement({
view: {
name: 'ns-loop',
},
model: (viewElement, {write: modelWriter}) => {
const source = viewElement.getAttribute('for');
const as = viewElement.getAttribute('as');
return modelWriter.createElement('loop', {source: source, as: as});
}
});
conversion.for('editingDowncast').elementToElement({
model: 'loop',
view: (modelItem, {writer: viewWriter}) => {
const widgetElement = createLoopView(modelItem, viewWriter);
return widgetElement;
}
});
function createLoopView(modelItem, viewWriter) {
const source = modelItem.getAttribute('source');
const as = modelItem.getAttribute('as');
const loopElement = viewWriter.createContainerElement('ns-loop', {'for': source, 'as': as});
return loopElement;
}
}
}
This code works, in the sense that an <ns-loop> element is successfully inserted into the editor content; however, I am not able to edit this element's content. Any keyboard input is inserted into a <p> before the <ns-loop> element, and any text selection disappears once the mouse stops moving. Additionally, it is only possible to place the cursor at the beginning of the element.
If I simply swap out 'ns-loop' as the tag name for 'div' or 'p', I am able to type within the element without issue, so I suspect that I am missing something in the schema definition to make CKEditor aware that this element is "allowed" to be typed in, however I have no idea what I may have missed -- as far as I'm aware, that's what I should be achieving with the schema.extend() calls.
I have tried innumerable variations of allowedIn, allowedWhere, inheritAllFrom, isBlock, isLimit, etc within the schema definition, with no apparent change in behaviour.
Can anyone provide any insight?
Edit: Some additional information I just noticed - when the cursor is within the <ns-loop> element, the Heading/Paragraph dropdown menu is empty. That may be relevant.
Edit 2: Aaand I found the culprit staring me in the face.
this.editor.editing.mapper.on('viewToModelPosition', viewToModelPositionOutsideModelElement(this.editor.model, viewElement => viewElement.is('element', 'ns-loop')));
I'm new to the CKE5 plugin space, and was using other plugins as a reference point, and I guess I copied that code from another plugin. Removing that code solves the problem.
As noted in the second edit, the culprit was the code,
this.editor.editing.mapper.on('viewToModelPosition', viewToModelPositionOutsideModelElement(this.editor.model, viewElement => viewElement.is('element', 'ns-loop')));
which I apparently copied from another plugin I was using for reference. Removing this code has solved the immediate problem.
I'll accept this answer and close the question once the 2-day timer is up.

Rails 5 javascript for Ajax controlled partial is acting strangely & it can't initialize bootstrap-star-rating inputs as it should

I am using this js library (bootstrap-stars-rating) to provide star ratings using jquery in my Rails 5 application
It works well except that if I am calling it inside a partial that is renerdered by Ajax helper, it will not initialize the javascript code necessary to render the stars
I tried to solve this by making a globally accessible function in my webpacker init file for that library like this:
import "bootstrap-star-rating";
import "bootstrap-star-rating/css/star-rating.css";
import "bootstrap-star-rating/js/star-rating.js";
import "bootstrap-star-rating/themes/krajee-fas/theme.min.css";
const rating_options = {
size: 'xs',
showCaption: false,
step: 0.5,
filledStar: '<i class="fas fa-star"></i>',
emptyStar: '<i class="far fa-star"></i>',
theme: 'krajee-fas',
hoverOnClear: false
}
// initialize the star rating across the application
const initStarRating = () => {
document.addEventListener("turbolinks:load", () => {
console.log('hey I was run');
$(".bootstrap-rating").rating(rating_options);
}, {passive: true});
}
// initialize specific star rating in a specific form for ajax
const initStarRatingForObj = (obj) => {
{
console.log(obj)
console.log(obj.find(".bootstrap-rating"))
obj.find(".bootstrap-rating").rating(rating_options);
}
}
// binding the function so I can access it globally
window.initStarRatingForObj = initStarRatingForObj
export { initStarRating }
The problem is that if I call this function above in the js.erb file that renders the partial, passing the correct object to the function initStarRatingForObj, it will break & give error that it can't find the rating() function ((that one called in the initStarRating))
This is very weird as the rating() function is already available & working in that file, so how does it work in initStarRating() but not in the initStarRatingForObj() ?
I have been looking into this for hours but it doesn't work, I hope someone can point out what the issue is.
Note:
The same partial is working well in the same page if it is not rendered by Ajax response

How to use autoComplete.js?

I'm trying to use autoComplete.js.
I have npm installed it:
npm i #tarekraafat/autocomplete.js
Then imported it in a js file:
import autoComplete from "#tarekraafat/autocomplete.js/dist/js/autoComplete";
Also added a div with an id of autoComplete:
<input id="autoComplete" tabindex="1"> <!-- Default "id" value = "autoComplete">`
In the file where I imported the library, I copied the code that is on the website in step 4 of the how to use part.
But, I get the error:
autocompletejs.js:43 Uncaught ReferenceError: resultsListID is not defined
What am I doing wrong? I followed the steps as in the documentation but I get this error...
Any ideas on what might be the issue?
This error is caused, by the fact that here
resultsList: { // Rendered results list object | (Optional)
render: true,
container: source => {
resultsListID = "food_List";
return resultsListID;
},
destination: document.querySelector("#autoComplete"),
position: "afterend",
element: "ul"
},
resultsListID variable have never been initiated. It is possible to fix this byt adding var at the start of where resultsListID is assigned making something like:
var resultsListID = "food_List";
resultsList: { // Rendered results list object | (Optional)
render: true,
container: source => {
var resultsListID = "food_List";
return resultsListID;
},
destination: document.querySelector("#autoComplete"),
position: "afterend",
element: "ul"
},
Noticed, that this part is optional, and if one chooses to keep it, as far as my understanding is going all results will be wrapped in the container, that has id provided in the resultsListID variable

Computed not running when updated from emitted event vue js

I'm kinda a noob at vue js, however I can't seem to understand why the allValid event isn't being emitted from the following code: (snippet here, full code in the js fiddle)
http://jsfiddle.net/sTX7y/674/
Vue.component('password-validator', {
template: `
<ul>
<regex-validation regex='.{6,}' v-on:valid='v => { isLongEnough = v }' :input='input'>
Is Long Enough
</regex-validation>
<regex-validation regex='[A-Z]' v-on:valid='v => { hasUppercase = v }' :input='input'>
Has Upper
</regex-validation>
</ul>
`,
props: [ 'input' ],
data: function(){
return {
isLongEnough: false,
hasUppercase: false,
}
},
computed:{
isValid: function(){
var valid = this.isLongEnough && this.hasUppercase;
this.$emit('allValid', valid);
return valid;
}
}
});
When viewing this using the vue chrome extension I can clearly see that isLongEnough and hasUppercase both flip from true to false, (and the validation is reflected on the output). It's just that the last isValid computed function just never seems to run...
Thanks for the help and if you see any other noob mistakes feel free to chime in on how I could do this better.
The computed function is defined correctly in the password-validator component. The only problem is you have referenced it ouside of the component scope. i.e. {{ isValid }} is in the html outside of the template. To correct this you can change the password-validator template thus:
template: `
<ul>
<regex-validation regex='.{6,}' v-on:valid='v => { isLongEnough = v }' :input='input'>
Is Long Enough
</regex-validation>
<regex-validation regex='[A-Z]' v-on:valid='v => { hasUppercase = v }' :input='input'>
Has Upper
</regex-validation>
Is Valid: {{ isValid }}
</ul>
now that the reference to the computed property isValid is inside the template, it should update accordingly.
Updated the fiddle here: jsfiddle

Categories

Resources