I have a Vue component and the template has some div elements, a few images and a User object passed over from Laravel 6.0 Everything seems to work just fine until I try to use scrollIntoView() with the mounted() function. From what I can tell the page is trying to scrollIntoView() before it is completely loaded. If I limit the user object to a small size (just 2 users) everything works fine. But if there is more data it does not scroll to view like it should. It does work if I use a set timeout function but it looks hackey and not professional.
I just want to scroll to a div (the header) after view is mounted/loaded. The div is the first div on the page and has no images (if that even matters). Can anyone help me to solve this? I have seen that the mounted function only applies to the virtual DOM? If so what can we use in Vue that is more like JQuery $(document).ready() but for the specific component and applies to the actual DOM?
mounted() {
var elmnt = document.getElementById("move-to-header");
elmnt.scrollIntoView(true);
}
}
I got it working by using the callback function of my axios request. I did however need to wrap it in a setTimeout function with no specified number of milliseconds but overall it is working without errors.
get_user(the_name) {
axios
.post("/get_users", { the_name: the_name})
.then(response => {
this.display_user(response.data);
setTimeout(() => {
var elmnt = document.getElementById("move-to-header");
elmnt.scrollIntoView();
})
I have a problem when navigate between routes with angular 7, for example... i have this code in my app-component.html
<app-navbar *ngIf="currentUser"></app-navbar>
<router-outlet></router-outlet>
<app-footer *ngIf="currentUser"></app-footer>
Everything in the page works, the <router-outlet></router-outlet> have the main component for the page, this component have inside another component, the <app-leftmenu></app-leftmenu> this is the sidebar; originally this was a html-bootstrap 3 template that i segmented for use in angular cli.
Ok the reason for this question is that the SIDEBAR uses jquery for the basic actions like collapse and expand using the next code:
//
// Sidebar categories
//
// Hide if collapsed by default
$('.category-collapsed').children('.category-content').hide();
// Rotate icon if collapsed by default
$('.category-collapsed').find('[data-action=collapse]').addClass('rotate-180');
// Collapse on click
$('.category-title [data-action=collapse]').click(function (e) {
e.preventDefault();
var $categoryCollapse = $(this).parent().parent().parent().nextAll();
$(this).parents('.category-title').toggleClass('category-collapsed');
$(this).toggleClass('rotate-180');
containerHeight(); // adjust page height
$categoryCollapse.slideToggle(150);
});
But not work in angular, only when i reload once time the component if i navigate to other route the sidebar ignore the jquery, but refreshing the page works... how i resolve this... with no refreshing? Thanks for the help!
Presumably because at the time your jQuery code is executed $('.category-collapsed'), there is no such element.
Try jQuery.on(), which allows for later binding: https://api.jquery.com/on/
So this
$('.category-title [data-action=collapse]').click(fn)
becomes (untested)
$(document).on('click', '.category-title [data-action=collapse]', fn)
I would strongly suggest using Angular-Bootstrap instead of a vanilla javascript/jquery solution.
https://ng-bootstrap.github.io/#/home
This library provides a variety of standard UI components - tabbed interfaces, dropdowns, accordion menus etc. all as native Angular components with full TypeScript support.
I want to include Gumroad's Embed Widgets in my Angular application.
What I've tried:
I added this to my index.html:
<script src="https://gumroad.com/js/gumroad-embed.js"></script>
I added this to one of my components' template:
<div class="gumroad-product-embed" data-gumroad-product-id="demo">Loading...</div>
The problem:
Let's say the component on page /buy. If I open /buy, the widget appears. However, if I've navigated to /buy through some routerLink in the app, the widget doesn't appear, it just keeps showing "Loading...".
I also confirmed this by changing routerLink to href and it worked. I.e., the problem is probably related to removal and re-adding of the div.
Anyway, my question: how to use Gumroad's Embed Widget inside an Angular application correctly?
Notes:
1- It could be useful to look at gumroad-embed.js. It has stuff like MutationObserver but I couldn't follow along.
2- The Angular application is running on electronjs, in case that'll make a difference (due to MutationObserver or anything else).
Till someone comes up with a better answer, here's a solution that works specifically with the current gumroad-embed.js. In the future, it could change and the answer would be obsolete. Anyway, here it is:
Solution
1) Add a file edited-gumroad-embed.js under your /assets containing the following code:
function createGumroadEmbed(){window.GumroadEmbed||(window.GumroadEmbed=new GumroadEmbedManager)}function receiveMessage(t){var e={};if(t.data)try{e=JSON.parse(t.data)}catch(r){}if("GumroadEmbedMessage"===e.type&&GumroadEmbed){var i=GumroadEmbed.findEmbed(e.args.id)||GumroadEmbed.findEmbed(e.args.unique_id);i&&("setHeight"===e.action?i.setHeight(e.args.height):"scrollToTop"===e.action&&i.scrollToTop())}}!function(){var n=!1,a=/xyz/.test(function(){})?/\b_super\b/:/.*/;this._GumroadClass=function(){},_GumroadClass.extend=function(t){function e(){!n&&this.init&&this.init.apply(this,arguments)}var o=this.prototype;n=!0;var i=new this;for(var r in n=!1,t)i[r]="function"==typeof t[r]&&"function"==typeof o[r]&&a.test(t[r])?function(i,r){return function(){var t=this._super;this._super=o[i];var e=r.apply(this,arguments);return this._super=t,e}}(r,t[r]):t[r];return e.prototype=i,(e.prototype.constructor=e).extend=arguments.callee,e}}();var GumroadClass=_GumroadClass.extend({setEnvironment:function(){this.environment="production",this.domain="https://gumroad.com",this.isMobile=navigator.userAgent.match(/Mobile[\/; ]/i)||navigator.userAgent.match(/Opera (Mini|Mobi)/i)||navigator.userAgent.match(/IEMobile/i),this[this.environment]=!0,this.origin=window.location.protocol+"//"+window.location.hostname+(window.location.port?":"+window.location.port:"")},startNodeAdditionObserver:function(){MutationObserver&&(this.nodeAdditionObserver=new MutationObserver(function(t){for(var e=0;e<t.length;e++)for(var i=0;i<t[e].addedNodes.length;i++)this.nodeAdditionCallback&&this.nodeAdditionCallback(t[e].addedNodes[i])}.bind(this)),this.nodeAdditionObserver.observe(document.body,{childList:!0,subtree:!0}))}}),GumroadEmbedElement=GumroadClass.extend({init:function(t,e){this.manager=e;var i=t.getAttribute("data-gumroad-product-id");i&&(this.div=t,this.id=i,this.opts={as_embed:"true",referrer:document.referrer,origin:this.manager.origin},this.manager.embeds.push(this),this.show())},buildUrl:function(){var t=(this.manager.domain||"")+"/l/"+this.id+"?";for(var e in this.outboundEmbed&&(this.opts.outbound_embed="true"),this.opts)this.opts.hasOwnProperty(e)&&(t+="&"+e+"="+this.opts[e]);return t},createIframe:function(){this.iframe=document.createElement("iframe"),this.iframe.allowtransparency=!0,this.iframe.setAttribute("allowFullScreen","allowfullscreen"),this.iframe.className="gumroad-embed-iframe",this.iframe.scrolling="no",this.iframe.width="100%",this.iframe.height=0,this.iframe.id="gumroad-embed-iframe-"+this.id,this.iframe.setAttribute("style","display: block !important; border: none !important; margin: 0 auto !important; padding: 0 !important; max-width: 676px !important;"),this.div.parentNode.insertBefore(this.iframe,this.div)},scrollToTop:function(){this.iframe&&this.manager.isMobile&&window.scrollTo(0,this.iframe.offsetTop)},setHeight:function(t){this.div.style.display="none",this.iframe.setAttribute("height",t)},show:function(){this.iframe||this.createIframe();this.id=this.div.getAttribute("data-gumroad-product-id"),this.outboundEmbed=!!this.div.getAttribute("data-outbound-embed"),this.iframe.setAttribute("src",this.buildUrl())}}),GumroadEmbedManager=GumroadClass.extend({init:function(){this.setEnvironment(),this.createEmbeds()},createEmbeds:function(){this.embeds=[];for(var t=document.getElementsByClassName("gumroad-product-embed"),e=0;e<t.length;e++)new GumroadEmbedElement(t[e],this)},findEmbed:function(t){for(var e=0;e<this.embeds.length;e++)if(this.embeds[e].id==t)return this.embeds[e];return!1},gotMessage:function(t){var e={};try{e=JSON.parse(t.data)}catch(i){}this[e.action]&&this[e.action](e.args)},reload:function(){for(var t=0;t<this.embeds.length;t++){var e=this.embeds[t].iframe;e&&e.parentNode&&(e.parentNode.removeChild(e),this.embeds[t].div.style.display="")}this.createEmbeds()},scrollToTop:function(t){var e=this.findEmbed(t);e&&e.scrollToTop()},setHeight:function(t,e){var i=this.findEmbed(t);i&&i.setHeight(e)}});window.addEventListener?(window.addEventListener("message",receiveMessage,!1)/*,window.addEventListener("load",createGumroadEmbed)*/):window.attachEvent&&(window.attachEvent("onmessage",receiveMessage,!1)/*,window.attachEvent("onload",createGumroadEmbed)*/);createGumroadEmbed();
2) In the component whose template contains <div class="gumroad-product-embed" data-gumroad-product-id="demo">Loading...</div>, add the following code:
import {AfterContentInit, Component, OnDestroy} from '#angular/core';
#Component({
selector: 'app-buy',
templateUrl: './buy.component.html'
})
export class BuyComponent implements OnDestroy, AfterContentInit {
readonly scriptNode: HTMLScriptElement;
constructor() {
this.scriptNode = document.createElement('script')
this.scriptNode.setAttribute('src','/assets/edited-gumroad-embed.js')
}
ngAfterContentInit() {
document.getElementsByTagName('head')[0].appendChild(this.scriptNode)
}
ngOnDestroy() {
this.scriptNode.remove()
delete window['GumroadEmbed']
}
}
3) Success!
Explanation
By looking into gumroad-embed.js, it seems, as of Nov. 2018, to be just including another file. This other file's code is the base on which edited-gumroad-embed.js is based. edited-gumroad-embed.js is basically this file with 2 edits:
It calls createGumroadEmbed() instead of calling it on load event directly, because load seems to be firing once only at initial load of document.
It comments the event listener for load that used to call createGumroadEmbed().
For the component ts file, it basically tries to simulate the loading of the script as much as possible as if it was first load. By taking a non-thorough look into gumroad's code, it seems like for actions to happen window.GumroadEmbed needs to be undefined; that's why it deletes window['GumroadEmbed'].
Open questions and caveats
There's are enough open questions to pass a camel through.
Is all of that even needed or should have original Gumroad's code succeeded had it been run on Chrome instead of electronjs?
I didn't take enough look into gumroad's code to know if what I'm doing has no side effects. For example, are there leaks? are there event listeners that should be removed? What about the MutationObserver(s)?
I'm not sure why I'm calling the code in ngAfterContentInit. I'm trying to make it run after the div has been added to the DOM as much as possible.
If the component is reused (for example, in routing), does one need to re-create the scriptNode? I currently have routing reuse disabled anyway for other reasons.
That said, I'm probably not intending to use it after all, but the reason is unrelated to the original question. The reason is that I found many requests in the Network tab to many websites (e.g., Facebook) and I don't know the effect of that on my customers (e.g., privacy-wise). Again: I do not know, I'm not familiar with iframes. Also, I'm a bit afraid that my solution might have any leaks.
I'll just add a hyperlink.
I had the same problem in React, and came up with this simple solution:
index.html:
<script src="https://gumroad.com/js/gumroad-embed.js"></script>
<div style="display:none;" id="gumroad-product-embed" class="gumroad-container">
<div class="gumroad-product-embed" data-gumroad-product-id="gwIwi">
Loading...
</div>
</div>
<script defer>
const embed = document.getElementById('gumroad-product-embed')
window.GUMROAD = embed
</script>
<gumroadcomponent>.js:
const gumroad = window.GUMROAD
const container = React.createRef()
export class BuyNUNISYNTH extends React.Component {
componentDidMount() {
gumroad.style.display = "block"
container.current.appendChild(gumroad)
}
render() {
return <div>
<MyNav no_buy_button/>
<div ref={container}></div>
</div>
}
}
This keeps the Gumroad embed outside of React so that it only needs to load once. It was the re-rendering of React that was causing the problem.
I'm trying to create a React component where I can call a function like showMessage(message, bgColor) and an alert box will pop up that closes automatically after 5 seconds.
I've created many React components in my application but this one is causing me trouble because of having to call alert(), it not always being present, and having a required timer functionality. I just can't think of a "React"ive way of designing the component.
Here is the current jQuery code that I'm using which works but I'm trying to get away from:
$("#alertBox").css('background-color', color);
$("#alertBox").html(message);
$("#alertBox").alert();
$("#alertBox").fadeIn(500, "linear").fadeOut(5000, "linear", function() {
$("#alertBox").alert('close');
});
The problem is React is fundamentally declarative and this code just seems so imperative. In certain event handlers in my codebase I want to be able to just call a function to momentarily display this alert box. If it wasn't for the call to the alert function and fading in/out it wouldn't be so bad (could just do conditional redner). This is the last place in my code I'm still using jQuery which I'm trying to completely neuter from my application.
I'd also rather not use react-bootstrap and react-motion because I just found out about them a couple days ago and this is the last React component I need for my application and rather not rewrite everything now to use those libraries.
How about something like;
<showMessage message={message} bgcolor={bgcolor} hide={hideMessage}/>
then in showMessage render;
render() {
if (this.props.hide) return null;
if (this.state.timerDone) return null;
return (<div id="messageDive" style={whatever}></div>);
}
And you probably need something in componentReceivedProps to reset timerDone and set your timer. Then of course timerDone method to setState({timerDone: true}).
Our GWT application has various fragments and each of them are very big in size(1+ MB). What we would like to do is to show a progress bar when GWT is downloading the fragment. We are using GWTP based code splitting.
I could not find anything related to fragment loading event in GWT source code. Does anyone have any idea about how Javascript on page can be notified about which fragment is going to be downloaded next?
There is not any direct way, but you can workaround that basically in two ways
1.- Extending the CrossSiteIframeLinker and overriding the method getJsInstallScript() so as you can return a customised javascript content which could alert somehow to the user about the permutation being loaded or simply show a spinner. Note that your script should contain a code similar to installScriptEarlyDownload.js or installScriptDirect.js
// Your linker class
public class MyXSILinker extends CrossSiteIframeLinker {
#Override
protected String getJsInstallScript(LinkerContext context) {
return "/your_namespace/your_script.js";
}
}
// your "/your_namespace/your_script.js" script
function installScript(filename) {
}
// your module file
<define-linker name="mylinker" class="...MyXSILinker" />
<add-linker name="mylinker" />
2.- Using the window.__gwtStatsEvent function. You can define a function in your index.html to start a loading spinner when the script loading the app is going to be added to your document, then use your gwt code to remove that.
// In your index.html
<head>
<script>
window.__gwtStatsEvent = function(r) {
if (r.type == 'scriptTagAdded') {
d = document.createElement('div')
d.id = 'loading'
d.innerHTML = 'Loading...'
document.body.appendChild(d);
}
}
</head>
</script>
// In your EntryPoint class
Document.get().getElementById("loading").removeFromParent();
To show an accurate progress-bar is quite difficult because <script> tag does not support progress events. So the best approach should be a linker loading the permutation using ajax, so as you can use html5 progress events in the xhr object.
I would use the #2 approach showing a nice spinner animated with css.