How to scroll to top of page on Button Click using Vue js? - vue.js

I have a component with a section tag with an id and ref of map. The id is used to load a Google Map, don't worry about this.
<section
id="map"
ref="map"
>
</section>
I have a function called showUserLocationOnTheMap() with a button called Map that's invoked when a User clicks on it. This function shows a marker on the map based on an address, that is stored in the db from a previous step.
showUserLocationOnTheMap(latitude, longitude) {
let map = new google.maps.Map(document.getElementById("map"), {
zoom:15,
center: new google.maps.LatLng(latitude, longitude),
mapTypeId:google.maps.MapTypeId.ROADMAP
});
const marker = new google.maps.Marker({
position: new google.maps.LatLng(latitude, longitude),
map: map
});
google.maps.event.addListener(marker, "click", () => {
const infoWindow = new google.maps.InfoWindow();
infoWindow.setContent(
`<div class="ui header">
<h6>company name</h6>
</div>
`
);
infoWindow.open(map, marker);
});
this.$refs.map.scrollToTop();
},
<button
#click="showUserLocationOnTheMap(item.profile.latitude,item.profile.longitude)"
class="apply-job-btn btn btn-radius btn-primary apply-it"
>
Map
</button>
What I'm trying to do is, I have a bunch of search results, so when someone is scrolling far down the page and they click the map button from the record, it will do two things.
It shows a marker on the map (This is working)
It scrolls back up to the top of the page so the User can see the Map with the marker on it.
WHAT I TRIED:
I created this function below:
scrollToTop() {
this.$el.scrollTop = 0;
}
I added a ref attribute called map to my Section tag and then at the end of my showUserLocationOnTheMap() function I called this line (as you can also see above).
this.$refs.map.scrollToTop();
The problem is I'm getting this error:
Cannot read property 'scrollToTop' of undefined"

Related

Element is undefined because it is not rendered yet. How to wait for it?

In my Vue app I have an image element with a bound src. this code is in a stepper. In one step the user selects a picture to upload an in the next step the image gets shown (which works as intended). However I am trying to initiate a library on the element in the next step (the Taggd library) but the element seems to be undefined.
I tried the nextTick() to wait for the image to load but without luck.
the image element:
<div class="orientation-landscape">
<div class="justify-center q-px-lg">
<img v-if="photo.url" :src="photo.url" id="workingPhoto"
style="width: 80vw;" ref="workingPhoto"/>
</div>
</div>
The functions. I first use uploadFile to set the url of the image element and go to the next step where the image will be loaded. then I call initTaggd() on the nextTick however that fails because the element is undefined.
uploadFile(file) {
const self = this;
self.photo.file = file;
self.setPhotoUrl(file);
self.$refs.stepper2.next();
console.log('We should be at next step now. doing the init');
self.nextTick(self.initTaggd());
},
setPhotoUrl(file) {
const self = this;
self.photo.url = URL.createObjectURL(file);
console.log('ping1');
},
initTaggd() {
const self = this;
const image = document.getElementById('workingPhoto');
console.log('image:', image); //THIS RETURNS UNDEFINED
console.log('Trying ANOTHER element by refs:', self.$refs.stepper2); // This returns the element
console.log('trying the real element by reference', self.$refs.workingPhoto); // Returns undefined again
const taggd = new Taggd(image); //All this here fails because image is undefined.
console.log('ping2.5');
taggd.setTags([
self.createTag(),
self.createTag(),
self.createTag(),
]);
console.log('ping3');
},
I think I am looking for a way to wait for the image to fully load before calling initTaggd() but I am lost on how to achieve this.
You could listen for the load event:
<img
v-if="photo.url"
id="workingPhoto"
ref="workingPhoto"
:src="photo.url"
style="width: 80vw;"
#load="initTaggd"
/>

vue-router view caching / not resetting data

Im working on a car sales website and using vue-router.
I have an index page with a list of all cars for sale, and then when clicked they link to the single view of that specific vehicle.
I have a large 'header' image on the single view page and have it inside a container with a fixed height so that when the page loads there is not jumping in page height.
When going to this single view, I do an API call to get the vehicle data and then wish to fade in the heading image.
To do this:
<div class="singleVehicle__mainImage">
<span :style="styles" :class="{'imageLoaded' : mainImageLoaded }" v-if="vehicle"></span>
</div>
export default {
data() {
return {
vehicle: null,
styles: {
backgroundImage: null
},
mainImageLoaded: null
}
},
created() {
this.getVehicle().then(() => {
this.mainImageBackground();
});
},
methods: {
mainImageBackground() {
var source = "IMAGE SOURCE URL";
this.styles.backgroundImage = "url("+source+")";
var $this = this;
var img = new Image();
img.onload = function() {
$this.mainImageLoaded = true;
}
img.src = source;
if (img.complete) img.onload();
}
}
}
By default, the span inside the image wrapper has a 0 opacity and then that is transitioned to 1 when the .imageLoaded class is added.
This works fine, but only the first time each vehicle loaded. The image waits till it loads and then fades in. Everyother time afterwards the image simply pops in when it loads almost like the imageLoaded class is not being reset / the data is not being reset when leaving the view.
When clearing browser cache, it works again but once for each vehicle view.
This is probably due to your v-if="vehicle". The vehicle call is possibly taking longer and so the span is not showing until after the class is added or some timing issue related to that.

Dijit multiple widgets not parsed when row deleted with dGrid (dgrid.io)

I use dGrid 0.4.1-dev (dgrid.io) with a custom formatter (I have tried with custom renderer) to return raw HTMLthat contains Dijit widgets , during the page load, all widgets has been parsed correctly but it's totally broken when sort, paginate or scroll (OnDemandGrid).
In my example, I use Tooltip widget on icons (declarative mode, see bottom "Returned HTML for the cell").
On page load:
After scroll,sort, paginate...:
Returned HTML for the cell (by the formatter):
<div data-dojo-type="dijit/Tooltip" id="comxDynElement_101_dijit_1" data-dojo-props="connectId:'comxDynElement_101',position:['above']">Dupliquer cette facture</div><i class="fa fa-clone"></i>
<div data-dojo-type="dijit/Tooltip" id="comxDynElement_102_dijit_1" data-dojo-props="connectId:'comxDynElement_102',position:['above']">Télécharger cette facture</div><a id="comxDynElement_102" href="..."><i class="fa fa-file-pdf-o"></i></a>
<div data-dojo-type="dijit/Tooltip" id="comxDynElement_103_dijit_1" data-dojo-props="connectId:'comxDynElement_103',position:['above']">Voir cette facture</div><a id="comxDynElement_103" href="..."><i class="fa fa-eye"></i></a>
My formatter:
function (item) {
return item;
}
The widgets are being build and started on page load by the Dojo Parser. When new declarative html is added to your page after the page is already loaded, these widgets are not automatically parsed. You need to do that manually.
For example, it should be possible to call the parser manually in a renderRow function of your grid. An example can be found here.
So the following code could work:
require([
'dgrid/OnDemandList',
'dojo/parser'
], function (OnDemandList,parser) {
var list = new OnDemandList({
collection: myStore, // a dstore collection
renderRow: function (object, options) {
var div = document.createElement('div');
div.appendChild(document.createTextNode(object.myField));
// only 'parse' new row, not whole document again.
parser.parse(div);
return div;
}
});
});

JQuery UI Tabs: nested Tabs, AJAX loading and Back button

I'm loading JQuery UI tabs using AJAX.
I have 3 levels of nested UI tabs:
vertical
horizontal
horizontal
Most of the stuff is only 2 levels deep but some are 3. The issue is the 3rd level. I followed this:
http://benalman.com/code/projects/jquery-bbq/examples/fragment-jquery-ui-tabs/
But it does not cover nesting of tabs.
The layout of the page is like this:
<script type="text/javascript">
var tabs;
var tab_a_selector;
var tab_a_vertical_selector;
$(function() {
$("#menuTabs").tabs({
ajaxOptions: {
cache: false
}
}).addClass("ui-tabs-vertical ui-helper-clearfix");
$("#menuTabs li").removeClass('ui-corner-top').addClass('ui-corner-left');
$(".ui-tabs-vertical .ui-tabs-nav").removeClass("ui-tabs-nav").addClass("ui-tabs-nav-vert")
$("#menuItem0").tabs();
$("#menuItem1").tabs();
//...
/* -- enables Back button for nested tabs -- */
// The "tab widgets" to handle.
tabs = $('.tabs');
// This selector will be reused when selecting actual tab widget A elements.
tab_a_selector = 'ul.ui-tabs-nav a';
tab_a_vertical_selector = 'ul.ui-tabs-nav-vert a';
// Enable tabs on all tab widgets. The `event` property must be overridden so
// that the tabs aren't changed on click, and any custom event name can be
// specified. Note that if you define a callback for the 'select' event, it
// will be executed for the selected tab whenever the hash changes.
tabs.tabs({ event: 'change' });
// Define our own click handler for the tabs, overriding the default.
tabs.find(tab_a_selector).click(function(){
var state = {};
// Get the id of this tab widget.
id = $(this).closest( '.tabs' ).attr( 'id' );
// Get the index of this tab.
idx = $(this).parent().prevAll().length;
// Set the new state
// This is done as below to remove any state from deeper levels of nested tabs.
state ['menuTabs'] = $.bbq.getState('menuTabs');
state[ id ] = idx;
$.bbq.pushState( state, 2 );
});
tabs.find(tab_a_vertical_selector).click(function(){
var state = {};
// Get the id of this tab widget.
id = $(this).closest( '.tabs' ).attr( 'id' );
// Get the index of this tab.
idx = $(this).parent().prevAll().length;
// Set the state!
state[ id ] = idx;
// 2 -> replaces old state with new state. meaning indexes of nested tabs are removed
$.bbq.pushState( state, 2 );
});
// Bind an event to window.onhashchange that, when the history state changes,
// iterates over all tab widgets, changing the current tab as necessary.
$(window).bind( 'hashchange', function(e) {
// Iterate over all tab widgets.
tabs.each(function(){
// Get the index for this tab widget from the hash, based on the
// appropriate id property. In jQuery 1.4, you should use e.getState()
// instead of $.bbq.getState(). The second, 'true' argument coerces the
// string value to a number.
var idx = $.bbq.getState( this.id, true ) || 0;
// Select the appropriate tab for this tab widget by triggering the custom
// event specified in the .tabs() init above (you could keep track of what
// tab each widget is on using .data, and only select a tab if it has
// changed).
$(this).find( tab_a_selector).eq( idx ).triggerHandler( 'change' );
$(this).find( tab_a_vertical_selector ).eq( idx ).triggerHandler( 'change' );
});
})
// Since the event is only triggered when the hash changes, we need to trigger
// the event now, to handle the hash the page may have loaded with.
$(window).trigger( 'hashchange' );
/* -- END enables Back button for nested tabs -- */
});
</script>
<div id="menuTabs" class="tabs">
<ul>
<li>menuItem0</li>
<li>menuItem1</li>
<li>menuItem2</li>
</ul>
<div id="menuItem0" class="tabs">
<ul>
<li>Intro</li>
</ul>
</div>
<div id="menuItem1" class="tabs">
<ul>
<li>Introduction</li>
<li>Guide</li>
<li>abc</li>
</ul>
</div>
<!--...-->
</div>
The 3rd level of tabs is in the above html pages, as example in abc.html:
<script type="text/javascript">
var rNumberTabs = $("#rNumber").tabs();
rNumberTabs.tabs({ event: 'change' });
rNumberTabs.find(tab_a_selector).click(function(){
var state = {};
// Get the id of this tab widget.
id = $(this).closest( '.tabs' ).attr( 'id' );
// Get the index of this tab.
idx = $(this).parent().prevAll().length;
// Set the state!
state[ id ] = idx;
$.bbq.pushState( state );
});
tabs = tabs.add(grNumberTabs);
// If this is triggered it leads to an infinte loop,
// if not, I can't even browse to any other tab than the first
// one on he third level, eg. it automatically jumps back
// to first one.
$(window).trigger( 'hashchange' );
</script>
<div id="rNumber" class="tabs">
<ul>
<li>Layout</li>
<li>Prefix</li>
</ul>
<div id="layout">
<!-- Content here -->
</div>
<div id="prefix">
<!-- Content here -->
</div>
</div>
Any ideas how I can solve?
So here my current solution. The issue of endless looping seems to be caused by the fact that when using AJAX loading of tabs, the tab is loaded again for every click on it. Since the tabs that contain a 3rd level of tabs also contain JavaScript (see question) re-loading such a tab leads to issues.
The solution is to cache the tabs:
$("#menuTabs").tabs({
cache: true,
ajaxOptions: {
cache: false
}
})
Note: You must set Ajax cache to false:
http://docs.jquery.com/UI/Tabs#option-cache
Same for the 3rd level of tabs. And here also remove the call to hashchange event.
var rNumberTabs = $("#rNumber").tabs({
cache: true,
ajaxOptions: {
cache: false
}
});
//snipped..
//$(window).trigger( 'hashchange' ); remove this line

Backbone: Pattern for rendering and re-rendering subscriber views in a correct order

So basically I've been toying with this pattern of pubsub with subviews re-rendering when called by parent 'controller' view (sorry if that's confusing). If the subviews are based on a fetch of a collection or model (not shown below), sometimes they aren't rendered in the correct order I need them too, ie if SubView1 is a small nav for Subview2, I don't want it below SubView2.
I figure there has to be a pattern for such a common problem. Lemme know if this doesn't make sense and I will clarify. But the basic situation I'm dealing with is below.
markup:
<div id="main-container">
<div class="inner-container"></div>
</div>
js:
var ToggleNavView = Backbone.View.extend({
//let's say this template has two links
template: Handbars.compile(linkstmpl),
el: $('#main-container'),
events: {
"click a": "toggleViews"
}
initialize: function(){
_.bindAll(this, 'render', 'toggleViews');
// whoa, nice looking event aggregator http://bit.ly/p3nTe6
this.vent = _.extend({}, Backbone.Events);
this.render();
},
render: function(){
$(this.el).append(this.tmpl);
// suppose subviews below are declared as modules above with, say, requirejs
var sub1 = new SubView1({ vent: this.vent }),
sub2 = new SubView2({ vent: this.vent });
},
toggleViews: function(e){
e.preventDefault();
// get name of section or view you're toggling to
var section = $(e.currentTarget).data('section');
// publish events to subscribers
this.vent.trigger('toggleInboxViews', this, section);
},
});
var SubView1 = Backbone.View.Extend({
template: Handlebars.compile(tmpl1),
initialize: function(ops){
_.bindAll(this, 'render', 'removeOrRerender');
this.vent = ops.vent || null;
this.render()
},
render: function(){
$(this.el).append(this.template()).appendTo($(".inner-container"))
},
removeOrRerender: function(obj, section){
if( section == 'my-section'){
this.render();
} else if( section == 'other-section' ) {
$(this.el).fadeOut();
}
},
})
// another subview with same functionality etc...
// init view
new ToggleNavView();
If you need your sub-views to show up in specific places then the parent view should define that structure and the sub-views should be told where to render themselves. Then it won't matter what order things are draw in as the overall structure doesn't change.
For example, if you want one sub-view to appear at the top and the other below it, the main view should look like this:
<div id="main-view">
<div id="sub1"></div>
<div id="sub2"></div>
</div>
then the main view would render the sub-views with something like this:
var sub1 = new SubView1({ el: this.$el.find('#sub1'), vent: this.vent }),
var sub2 = new SubView2({ el: this.$el.find('#sub2'), vent: this.vent });
By specifying the el for the sub-views, their location on the page is no longer their problem and they won't shift positions if they're rendered in a different order. A happy side effect of this structure is that the sub-views are only concerned with themselves and are nicely self-contained; the parent view just needs to put the pieces in the right place by structuring its template properly and everything just works.
Here's a simple demo that might clarify the structure: http://jsfiddle.net/ambiguous/9u3S5/