Loading spinner in Vue.js from FileInput - javascript

I have a problem that I can't get my head around.
I am using Three.js in my web app and I want to add a loading spinner when I upload a file.
For that I created a component with the loading spinner, and display it with v-if when the file gets loaded.
Strangely enough, this does not work, even though the value "loading" is true when logged from within the function (when I log it after the file is loaded it is false again without changing it).
The code:
<template>
<div class="button-draw-container">
<input id="file-input" name="button-upload" type="file" #click="importFile"/>
</div>
<div v-if="loading" id="wrapper-loader">
<Loader/>
</div>
</template>
<script>
export default {
components: {
Loader
},
data() {
return {
loading: false,
}
},
methods: {
importFile() {
var fileInput = document.querySelector("#file-input");
var cores = [];
var outline;
fileInput.addEventListener("change", function(event) {
//This is the part that's not working
this.loading = true;
console.log(this.loading)
const reader = new FileReader();
reader.addEventListener("load", function(event) {
const contents = event.target.result;
const loader = new Rhino3dmLoader();
loader.setLibraryPath( 'https://cdn.jsdelivr.net/npm/three#0.138.2/examples/jsm/libs/rhino3dm/' );
loader.parse( contents, function( object ) {
for (let i = 0; i < object.children.length; i++) {
if(object.children[i].userData.attributes.layerIndex == 1) {
cores.push(object.children[i].geometry);
}
else if(object.children[i].userData.attributes.layerIndex == 2) {
outline = object.children[i].geometry;
}
}
} );
});
reader.readAsArrayBuffer( this.files[0] );
})
},
}
</script>

The problem was that the scope of "this" is within the function block, meaning that this is no longer a reference to the Vue instance.
Using an arrow function which has no lexical scope and changing the last line to reader.readAsArrayBuffer( fileInput.files[0] ); did the job.

Related

Pop up removed completly from DOM

I have created the below function that uses bootstrapalert where a pop up comes up that holds random data from a database table. But after I trigger the event and close the window I need to refresh the page in order for it to work again. I don't seem to find the issue in the code that causes this problem. Any suggestions for improvement?
<script>
var alertList = document.querySelectorAll(".alert");
alertList.forEach(function (alert) {
new bootstrap.Alert(alert);
});
function randomName() {
if (
document.getElementById("randomNamePopUp").dataset.showned == "false"
) {
document.getElementById("randomNamePopUp").style.display = "block";
document.getElementById("randomNamePopUp").dataset.showned = "true";
} else {
//window.location.href=window.location.href+"?showRandom=true";
}
}
const urlParams1 = new URLSearchParams(window.location.search);
const myParam = urlParams1.get("showRandom");
if (myParam == "true") {
randomName();
}
</script>

How to use imported javascript library constructor in vue.js component?

I want to reuse a javascript library I did some time ago in a vue.js component.
The js library works like this:
simple reference on the main script with tag
css loading
The library provides a constructor, so what is needed is a element with an ID and to init the component in javascript I only need to:
var divelement = new pCalendar("#divelement", {
... various options
});
I'm trying to create a .vue component that is able to do the same (loading js library, init css, init component with the constructor and options), but I can't figure out what is the right way to do it.
This is what I'm working on, but in this situation I get an error because pCalendar is not recognized as constructor.
<template>
<div id="myelement"></div>
</template>
<script>
import perpetual_calendar from '../../../assets/js/perpetual-calendar/perpetual_calendar.js'
export default {
data () {
return {
myelement: '',
}
},
mounted(){
var myelement = new pCalendar("#myelement",{
... various options
});
} ,
}
</script>
<style lang="css">
#import '../../../assets/js/perpetual-calendar/pcalendar_style.css';
</style>
Edit 1 (answer to #Daniyal Lukmanov question):
perpetual_calendar.js looks like this:
var pCalendar = function (element, options) {
this.options = {};
this.initializeElement(element);
this.initializeOptions(options || {});
this._create();
}
...
pCalendar.prototype.initializeElement = function (element) {
var canCreate = false;
if (typeof HTMLElement !== "undefined" && element instanceof HTMLElement) {
this.element = element;
canCreate = true;
} else if (typeof element === "string") {
this.element = document.querySelector(element);
if (this.element) {
canCreate = true;
} else {
canCreate = false;
}
} else {
canCreate = false;
}
if (canCreate === true) {
if (document.getElementsByName(this.element.id+"_store").length!=0) {
canCreate = false;
}
}
return canCreate;
};
and so on ...
Edit 2: this is the initializeOptions function, that is throwing the TypeError: "this.element is null" error.
pCalendar.prototype.initializeOptions = function (options) {
// begin hardcoded options, don't touch!!!
this.options['objectId'] = this.element.id;
this.options['firstMonth'] = null;
(... various options)
// end hardcoded options
for (var key in this.defaultOptions) {
( ... loop to load options - default one or defined by user in the constructor)
}
};
In you perpetual_calendar.js file, you need to export the pCalendar in order to use it. At the bottom of the perpetual_calendar.js file, add:
export {
pCalendar
};
Now, you should be able to import and use it like so:
import { pCalendar } from './perpetual_calendar.js';
let calendar = new pCalendar({ /* parameters*/ });
EDIT After adding initializeElement method
There are a few things wrong in the code:
It seems that not all code paths of initializeElement set the this.element variable.
document.querySelector will not work in vue. You will need to pass the element via the this.$refs variable:
<template>
<div id="myelement" ref="myelement"></div>
</template>
<script>
import perpetual_calendar from '../../../assets/js/perpetual-calendar/perpetual_calendar.js'
export default {
data () {
return {
myelement: '',
}
},
mounted(){
var myelement = new pCalendar(this.$refs["myelement"], { /* various options */ });
}
}
</script>
<style lang="css">
#import '../../../assets/js/perpetual-calendar/pcalendar_style.css';
</style>
Now, you can pass the element to your perpetual_calendar as directly as an object instead of having to use document.querySelector:
pCalendar.prototype.initializeElement = function (element) {
this.element = element.
return true;
};

Event handler problem when feeding HTML chart via wix code

I'm feeding an HTML element (pie chart) on a wix page. I pull data from local storage for 7 variables and pass the information to the HTML element via Postmessage.
My code works fine when it's part of a button (export function). However I would like to trigger the event from the onReady function (i.e. when the page is loaded). I use the exact same code but it simply doesn't work with the OnReady function (i.e. I'm unable to trigger the event programatically).
Wix pagecode for Export Function with button (works fine):
export function button1_click(event) {
var data = [introdeo, intcalypso, intbalthazar, intluna, intkiara, intmistral, intsaya];
console.log(data);
var labels = ["Rodeo", "Calypso", "Balthazar", "Luna", "Kiara", "Mistral", "Saya"];
let info = {data:data, labels:labels};
$w("#html1").postMessage(info);
}
Wix pagecode for onReady function (doesn't work):
$w.onReady(function () {
var data = [introdeo, intcalypso, intbalthazar, intluna, intkiara, intmistral, intsaya];
var labels = ["Rodeo", "Calypso", "Balthazar", "Luna", "Kiara", "Mistral", "Saya"];
let info = {data:data, labels:labels};
$w("#html1").postMessage(info);
} );
HTML code (the chart code in the HTML element on wix page):
<!DOCTYPE html>
<html lang="en-US">
<body>
<canvas id="myChart"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.4.0/Chart.min.js"></script>
<script type="text/javascript">
var ctx = document.getElementById('myChart').getContext('2d');
var myPieChart = new Chart(ctx,{
type: 'pie',
data: {
labels:[],
datasets: [{
data: [],
backgroundColor: ["#f97a03", "#52aff0", "#35a11d", "#f052e4", "#853fc2", "#f0f712", "#092978"],
}]
},
options: {}
});
window.onmessage = function(event){
myPieChart.data.datasets[0].data = event.data.data;
myPieChart.data.labels = event.data.labels;
myPieChart.update();
};
</script>
</body>
</html>
With the button Export function, I get an updated pie chart on my web page. With the OnReady code, I get a blank space in the HTML element.
Seems like the html element might not be ready to receive that POST. Try wrapping $w("#html1").postMessage(info); in a setTimeout.
$w.onReady(function () {
var data = [introdeo, intcalypso, intbalthazar, intluna, intkiara, intmistral, intsaya];
var labels = ["Rodeo", "Calypso", "Balthazar", "Luna", "Kiara", "Mistral", "Saya"];
let info = {data:data, labels:labels};
setTimeout(function() {
$w("#html1").postMessage(info);
}, 1000)
});
I will suggest to use a promise
and resolve the promise when the HTMl component is ready to use
So, in this code we are sending the data for every 500ms to check if the HTML is ready
and if it's ready updating the global variable and resolving the promise
$w.onReady(()=>{
let isHTMLready = false;
async function sendHTMLData() {
await isHTMLReady(); // this line will wait until the HTML component is ready
// then your code here
}
$w('#htmlID').onmessage(e=>{
let {data} = e;
if(data.isReady) {
// html is ready
isHTMLReady = true; //updating the gloable variable
} else if(data.someOtherCondition) {
// do something
}
});
function isHTMLReady() {
return new Promise((res,rej)=>{
let i = 0;
let intervalID = setInterval(()=>{
$w('#html').postMessage({
isHTMLReady : true
});
if(isHTMLready) { // checking if the global variable is changed to true
// stop the time interval
clearInterval(intervalID);
// resolve the promise
res("ready");
}
i++;
if(i > 28) { // waiting until 14 second before rejecting the promise
// rejecting the promise
rej("no response from HTML");
}
},500);
});
}
});
on the HTML component add this following code
It will check if the data sent is "isHTMLReady" then (if the HTML component is ready) we will send it back to wix site
from there we will update the variable and stop the interval and resolve the promise
window.onmessage = e => {
let {data} = e;
if(data.isHTMLReady) {
messageWixSite({isHTMLReady: true});
}
else if(data.isGraphData) {
// write your code here
}
}
function messageWixSite(data) {
let msg = {
"isCropper" : true,
}
msg = {...msg, ...data};
// console.log("message : " , msg);
window.parent.postMessage(msg, "*");
}
This way we ensure that both the wix site and the HTML element is ready to use before sending the data

How to replace src of image while making list in react [duplicate]

This question already has answers here:
How to check for broken images in React JS
(8 answers)
Closed 5 years ago.
Could you please tell me how to replace src of image, while making list in react? I have one list in which there is an image and it's caption. I want to exchange src url in my img tag when image not present.
If image is present on server then it's fine, if not - I want to replace src url to this one http://punemirror.indiatimes.com/photo/55813567.cms.
Here is my code
https://codesandbox.io/s/KOrGKp3B8
I have tried like this: there is function which detect whether image present or not. But how I will use this function in react?
imageExists(url, callback) {
var img = new Image();
img.onload = function () {
callback(true);
};
img.onerror = function () {
callback(false);
};
img.src = url;
}
You can create a component which will handle this logic for you:
class SmartImage extends React.Component {
constructor() {
super();
this.state = { fallback: true };
}
componentDidMount() {
this.checkImage();
}
componentDidUpdate(prevProps) {
if (prevProps.image !== this.props.image) {
this.checkImage();
}
}
checkImage() {
let img = new Image(this.props.img);
img.onload = () => {
this.setState({ fallback: false });
};
img.onerror = () => {
this.setState({ fallback: true });
};
}
render() {
return (
<img src={this.state.fallback ? this.props.imgFallback : this.props.img} />
);
}
}
And instead of rendering img tags directly, you'd render SmartImage components like this:
<SmartImage img={url1} imgFallback={url2} />
It basically checks if the image is loaded successfully each time its changed, and on mount, and changes the state accordingly, which then renders accordingly.

AngularJS : How to run JavaScript from inside Directive after directive is compiled and linked

I have a responsive template that I am trying to use with my Angularjs app. This is also my first Angular app so I know I have many mistakes and re-factoring in my future.
I have read enough about angular that I know DOM manipulations are suppose to go inside a directive.
I have a javascript object responsible for template re-sizes the side menu and basically the outer shell of the template. I moved all of this code into a directive and named it responsive-theme.
First I added all the methods that are being used and then I defined the App object at the bottom. I removed the function bodies to shorten the code.
Basically the object at the bottom is a helper object to use with all the methods.
var directive = angular.module('bac.directive-manager');
directive.directive('responsiveTheme', function() {
return {
restrict: "A",
link: function($scope, element, attrs) {
// IE mode
var isRTL = false;
var isIE8 = false;
var isIE9 = false;
var isIE10 = false;
var sidebarWidth = 225;
var sidebarCollapsedWidth = 35;
var responsiveHandlers = [];
// theme layout color set
var layoutColorCodes = {
};
// last popep popover
var lastPopedPopover;
var handleInit = function() {
};
var handleDesktopTabletContents = function () {
};
var handleSidebarState = function () {
};
var runResponsiveHandlers = function () {
};
var handleResponsive = function () {
};
var handleResponsiveOnInit = function () {
};
var handleResponsiveOnResize = function () {
};
var handleSidebarAndContentHeight = function () {
};
var handleSidebarMenu = function () {
};
var _calculateFixedSidebarViewportHeight = function () {
};
var handleFixedSidebar = function () {
};
var handleFixedSidebarHoverable = function () {
};
var handleSidebarToggler = function () {
};
var handleHorizontalMenu = function () {
};
var handleGoTop = function () {
};
var handlePortletTools = function () {
};
var handleUniform = function () {
};
var handleAccordions = function () {
};
var handleTabs = function () {
};
var handleScrollers = function () {
};
var handleTooltips = function () {
};
var handleDropdowns = function () {
};
var handleModal = function () {
};
var handlePopovers = function () {
};
var handleChoosenSelect = function () {
};
var handleFancybox = function () {
};
var handleTheme = function () {
};
var handleFixInputPlaceholderForIE = function () {
};
var handleFullScreenMode = function() {
};
$scope.App = {
//main function to initiate template pages
init: function () {
//IMPORTANT!!!: Do not modify the core handlers call order.
//core handlers
handleInit();
handleResponsiveOnResize(); // set and handle responsive
handleUniform();
handleScrollers(); // handles slim scrolling contents
handleResponsiveOnInit(); // handler responsive elements on page load
//layout handlers
handleFixedSidebar(); // handles fixed sidebar menu
handleFixedSidebarHoverable(); // handles fixed sidebar on hover effect
handleSidebarMenu(); // handles main menu
handleHorizontalMenu(); // handles horizontal menu
handleSidebarToggler(); // handles sidebar hide/show
handleFixInputPlaceholderForIE(); // fixes/enables html5 placeholder attribute for IE9, IE8
handleGoTop(); //handles scroll to top functionality in the footer
handleTheme(); // handles style customer tool
//ui component handlers
handlePortletTools(); // handles portlet action bar functionality(refresh, configure, toggle, remove)
handleDropdowns(); // handle dropdowns
handleTabs(); // handle tabs
handleTooltips(); // handle bootstrap tooltips
handlePopovers(); // handles bootstrap popovers
handleAccordions(); //handles accordions
handleChoosenSelect(); // handles bootstrap chosen dropdowns
handleModal();
$scope.App.addResponsiveHandler(handleChoosenSelect); // reinitiate chosen dropdown on main content resize. disable this line if you don't really use chosen dropdowns.
handleFullScreenMode(); // handles full screen
},
fixContentHeight: function () {
handleSidebarAndContentHeight();
},
setLastPopedPopover: function (el) {
lastPopedPopover = el;
},
addResponsiveHandler: function (func) {
responsiveHandlers.push(func);
},
// useful function to make equal height for contacts stand side by side
setEqualHeight: function (els) {
var tallestEl = 0;
els = jQuery(els);
els.each(function () {
var currentHeight = $(this).height();
if (currentHeight > tallestEl) {
tallestColumn = currentHeight;
}
});
els.height(tallestEl);
},
// wrapper function to scroll to an element
scrollTo: function (el, offeset) {
pos = el ? el.offset().top : 0;
jQuery('html,body').animate({
scrollTop: pos + (offeset ? offeset : 0)
}, 'slow');
},
scrollTop: function () {
App.scrollTo();
},
// wrapper function to block element(indicate loading)
blockUI: function (ele, centerY) {
var el = jQuery(ele);
el.block({
message: '<img src="./assets/img/ajax-loading.gif" align="">',
centerY: centerY !== undefined ? centerY : true,
css: {
top: '10%',
border: 'none',
padding: '2px',
backgroundColor: 'none'
},
overlayCSS: {
backgroundColor: '#000',
opacity: 0.05,
cursor: 'wait'
}
});
},
// wrapper function to un-block element(finish loading)
unblockUI: function (el) {
jQuery(el).unblock({
onUnblock: function () {
jQuery(el).removeAttr("style");
}
});
},
// initializes uniform elements
initUniform: function (els) {
if (els) {
jQuery(els).each(function () {
if ($(this).parents(".checker").size() === 0) {
$(this).show();
$(this).uniform();
}
});
} else {
handleUniform();
}
},
updateUniform : function(els) {
$.uniform.update(els);
},
// initializes choosen dropdowns
initChosenSelect: function (els) {
$(els).chosen({
allow_single_deselect: true
});
},
initFancybox: function () {
handleFancybox();
},
getActualVal: function (ele) {
var el = jQuery(ele);
if (el.val() === el.attr("placeholder")) {
return "";
}
return el.val();
},
getURLParameter: function (paramName) {
var searchString = window.location.search.substring(1),
i, val, params = searchString.split("&");
for (i = 0; i < params.length; i++) {
val = params[i].split("=");
if (val[0] == paramName) {
return unescape(val[1]);
}
}
return null;
},
// check for device touch support
isTouchDevice: function () {
try {
document.createEvent("TouchEvent");
return true;
} catch (e) {
return false;
}
},
isIE8: function () {
return isIE8;
},
isRTL: function () {
return isRTL;
},
getLayoutColorCode: function (name) {
if (layoutColorCodes[name]) {
return layoutColorCodes[name];
} else {
return '';
}
}
};
}
};
});
Originally the App.init() object method would be called at the bottom of any regular html page, and I have others that do certain things also that would be used on specific pages like Login.init() for the login page and so forth.
I did read that stackoverflow post
"Thinking in AngularJS" if I have a jQuery background? and realize that I am trying to go backwards in a sense, but I want to use this template that I have so I need to retro fit this solution.
I am trying to use this directive on my body tag.
<body ui-view="dashboard-shell" responsive-theme>
<div class="page-container">
<div class="page-sidebar nav-collapse collapse" ng-controller="SidemenuController">
<sidemenu></sidemenu>
</div>
<div class="page-content" ui-view="dashboard">
</div>
</div>
</body>
So here is my problem. This kinda sorta works. I don't get any console errors but when I try to use my side menu which the javascript for it is in the directive it doesn't work until I go inside the console and type App.init(). After that all of the template javascript works. I want to know how to do responsive theme stuff in these directives. I have tried using it both in the compile and link sections. I have tried putting the code in compile and link and calling the $scope.App.init() from a controller and also at the bottom after defining everything. I also tried putting this in jsfiddle but can't show a true example without having the console to call App.init().
My end design would be having some way to switch the pages through ui-router and when a route gets switched it calls the appropriate methods or re-runs the directive or something. The only method that will run on every page is the App.init() method and everything else is really page specific. And technically since this is a single page app the App.init() only needs to run once for the application. I have it tied to a parent template inside ui-router and the pages that will switch all use this shell template. There are some objects that need to access other to call their methods.
Im sorry in advance for maybe a confusing post. I am struggling right now trying to put together some of the ways that you do things from an angular perspective. I will continue to edit the post as I get responses to give further examples.
You said I have read enough about angular that I know DOM manipulations are suppose to go inside a directive but it sounds like you missed the point of a directive. A directive should handle DOM manipulation, yes, but not one directive for the entire page. Each element (or segment) of the page should have its own directive (assuming DOM manip needs to be done on that element) and then the $controller should handle the interactions between those elements and your data (or model).
You've created one gigantic directive and are trying to have it do way too much. Thankfully, you've kinda sorta designed your code in such a way that it shouldn't be too hard to break it up into several directives. Basically, each of your handle functions should be its own directive.
So you'd have something like:
.directive('sidebarMenu', function(){
return {
template: 'path/to/sidebar/partial.html',
link: function(scope, elem, attrs){
// insert the code for your 'handleSidebarMenu()' function here
}
};
})
.directive('horizontalMenu', function(){
return {
template: 'path/to/horizontal/partial.html',
link: function(scope, elem, attrs){
// insert the code for your 'handleHorizontalMenu()' function here
}
};
})
and then your view would look something like:
<body ui-view="dashboard-shell" responsive-theme>
<div class="page-container">
<div class="page-sidebar nav-collapse collapse">
<horizontal-menu></horizontal-menu>
<sidebar-menu></sidebar-menu>
</div>
<div class="page-content" ui-view="dashboard">
</div>
</div>
</body>
And then you don't need a SidebarmenuController because your controller functions shouldn't be handling DOM elements like the sidebar. The controller should just handling the data that you're going to display in your view, and then the view (or .html file) will handle the displaying and manipulation of that data by its use of the directives you've written.
Does that make sense? Just try breaking that huge directive up into many smaller directives that handle specific elements or specific tasks in the DOM.

Categories

Resources