How to change css while scrolling in Vue - javascript

i decided to learn Vue.js on a project and i can't get how i can track window scroll and dynamicly change CSS after some distance similar, like i did it with JQuery:
$(window).scroll(function() {
if($(window).scrollTop() >= 100) {
$('header').addClass('fixed');
} else {
$('header').removeClass('fixed');
}
});

You can use a Custom Directive and bind it to any component or element in your Vue instance.
So say you have a <div> and name your directive on-scroll you would update your div: <div on-scroll="handleScroll"> where handleScroll is the method in your Vue instance that is going to, well, handle the scroll event.
Directive:
Vue.directive('on-scroll', {
inserted: function (el, binding) {
let f = function (evt) {
if (binding.value(evt, el)) {
window.removeEventListener('scroll', f)
}
}
window.addEventListener('scroll', f)
}
})
Vue Instance:
new Vue({
el: '#el',
methods: {
handleScroll: (event, el) => {
if ( window.scrollY >= 300 ) {
el.classList.add('fixed');
} else {
el.classList.remove('fixed');
}
}
}
});

Related

Change position fixed of div if scroll

I tried to fix a div after scroll on vuejs; My code like this :
...
async created() {
window.addEventListener('scroll', this.calendarScroll);
}
methods: {
calendarScroll() {
console.log('Scroll');
const dateHeader = document.querySelector('.dates-header');
if (dateHeader) {
if (window.scrollTop() >= 200) {
dateHeader.style.top = 0;
dateHeader.style.position = 'fixed';
}
}
},
}
the error is : Calendar.vue?404a:681 Uncaught TypeError: window.scrollTop is not a function
You can add scrollTo to the Element prototype before you mount:
Element.prototype.scrollTo = () => {}
All Elements created after you add this code will have the scrollTo method.
For more detail, you can check this: https://github.com/vuejs/vue-test-utils/issues/319#issuecomment-354667621

Vue.js Computed/Watch Not Reacting to Viewport Width Data Change

Can someone explain to me in simple terms why the following example doesn't work?
I'm trying to run a function that captures the viewport/window width and then runs code based on how wide the viewport or window is (responsive design).
I'm a beginner so it's entirely possible I'm misunderstanding how Watch and Computed works... but it's my understanding that both Watch and Computed monitors a data property and if my data changes, watch and computed should react and trigger their code right?
So if I have a value called viewportWidth in my data, and I run an onresize to continually update it, I am updating my data which should trigger my watcher right? Shouldn't the continually updating value also trigger my computed property since it also relies on changing data?
So far I'm not seeing either of them react to my data changing.. if I'm misunderstanding please ELI5 and show me the better way to approach this and why..
(quick sidenote: I understand I can just run my handler inside of my onresize listener, but I assumed it would be smarter to instead setup a watcher or computed so that my method since they cache(?) and not trigger too often when it doesn't need to and only update conditions when it needs to.. is that right?)
Thank you!
<template>
<main>
<section>
<h2>viewport width: {{viewportWidth}}px</h2>
<h2>computed: {{rackClass}}</h2>
<h2>Does it work? {{doesItWork}}</h2>
</section>
</main>
</template>
<script>
export default {
data() {
return {
viewportWidth: window.innerWidth,
doesItWork: 'no it does not'
}
},
mounted() {
window.onresize = function(e) {
this.viewportWidth = window.innerWidth;
console.log(window.innerWidth)
}
},
watch: {
viewportWidth: function() {
console.log('>> value changed')
this.handleViewPortChange();
}
},
computed: {
rackClass: function(){
let theValue = "greater";
if(this.viewportWidth > 1000) theValue = "less than"
console.log('>> viewportWidth changed = ',this.viewportWidth)
return theValue
},
methods:{
handleViewportChange: function() {
this.doesItWork = 'it works!';
}
}
}
}
</script>
https://codepen.io/cmaxster/pen/rNyZLXG
Well aren't you in a pickle!
You are putting your curly braces and the commas in all the wrong places!
I updated the code so that it can be added as a snippet here. I have also put comments where you had messed up.
const app = new Vue({
el: "#app",
data() {
return {
viewportWidth: window.innerWidth,
doesItWork: 'no it does not'
}
},
mounted() {
const self = this;
window.onresize = (e) => {
this.viewportWidth = window.innerWidth;
//console.log(window.innerWidth)
}
},
watch: {
viewportWidth: function() {
console.log('>> value changed')
this.handleViewportChange(); // you were calling the wrong method! spellings and case was messed up
}
},
computed: {
rackClass: function(){
let theValue = "greater";
if(this.viewportWidth > 1000) theValue = "less than"
console.log('>> viewportWidth changed = ',this.viewportWidth)
return theValue
}
},
// you had your methods inside computed!
methods:{
handleViewportChange() {
this.doesItWork = 'it works!';
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.12/dist/vue.js"></script>
<main id="app">
<section>
<h2>viewport width: {{viewportWidth}}px</h2>
<h2>computed: {{rackClass}}</h2>
<h2>Does it work? {{doesItWork}}</h2>
</section>
</main>
Have you tried transforming your watch into an arrow function?

Vue acting weird with condition relating to window width

So I am just trying to get my basic Vue navbar functions working such as changing class on scroll (which works fine) and changing class on resize, which I'm having a bit more trouble with.
Here is the content of my <template> tags:
<nav class="navbar is-fixed-top navbar-max">
{{windowWidth}}
</nav>
...and the relevant content of my <script> tags:
export default {
name: "Navbar",
data () {
return {
windowWidth: window.innerWidth
}
},
created () {
window.addEventListener('resize', this.onResize);
},
mounted () {
this.windowWidth = window.innerWidth
},
beforeDestroy () {
window.removeEventListener('resize', this.onResize);
},
methods: {
onResize() {
let navbar = document.querySelector(".navbar");
if (this.windowWidth > 768) {
console.log(this.windowWidth),
navbar.classList.remove("nav-mobile"),
navbar.classList.add("nav-desktop")
}
else {
console.log(this.windowWidth),
navbar.classList.remove("nav-desktop"),
navbar.classList.add("nav-mobile")
}
}
}
}
My issue is really odd - all my console.log()'s output the correct width, and so does {{windowWidth}} in the navbar, and even the adding and removing classes works! It's just that the changed classes don't seem to have any effect until windowWidth = 1024 and I have no idea why...
Any help?
Cheers :)
You never set this.windowWidth after mount.
Simply add the following:
onResize() {
this.windowWidth = window.innerWidth // add this line
...
}
I would also like to point out that what it looks like you're trying to achieve (different nav-bar-styling on mobile and desktop) is very doable without any vue-/js-magic using CSS #media-queries.
And if you still wish to do it with vue, you should do it the vue way:
Make a computed method like so:
computed: {
isMobile() {
return this.windowWidth <= 768
}
}
and then update the class directly on the nav-tag using class-binding.

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.

Backbone events not firing after on demand loading element

I'm using backbone and lazy loading views in a single page application as I need them. However, it appears doing this seems to be confusing the way backbone knows what my 'el' is when setting up events. Using the view definition below, I'm trying to get the code that fires on the submit button click or the input fields changing but right now, neither appear to work.
$(document).ready(function () {
editaddressView = Backbone.View.extend({
elementReady: false,
initialize: function () {
this.model = window.AccountData;
this.listenTo(this.model, "change", this.render);
if ($('#section-editaddress').length == 0) {
// Load UI
$('#ajax-sections').prepend('<div class="section" id="section-editaddress" style="display: none;"></div>');
}
this.el = $('#section-editaddress');
},
events: {
"click #edit-address-submit": "beginSaving",
"change input": "updateModel",
"change select": "updateModel"
},
render: function () {
$(this.el).find("[name=address]").val(this.model.get('owner_address1'));
// ...
return this;
},
switchTo: function () {
// Set menu state
$('.js-NavItem').removeClass('active');
$('#sN-li-personal').addClass('active');
if (this.options.isPreLoaded)
this.elementReady = true;
if (this.elementReady) {
this.renderSwitch();
}
else {
var model = this;
$('#section-editaddress').load('/ajax/ui/editaddress', function (response, status, xhr) {
if (status == "error") {
$('#page-progress-container').fadeOut('fast', function () {
$('#page-load-error').fadeIn('fast');
});
} else {
$('#section-editaddress').find('.routedLink').click(function (e) {
window.Router.navigate($(this).attr('href'), true);
return false;
});
model.delegateEvents();
model.elementReady = true;
model.render(); // First render
model.renderSwitch();
}
});
}
},
renderSwitch: function () {
// Abort showing loading progress if possible
if (window.firstRunComplete) {
clearTimeout(window.pageHide);
// Change screen - Fade progress if needed
$('#page-progress-container').fadeOut('fast', function () {
$('#page-load-error').fadeOut('fast');
var sections = $(".section");
var numSections = sections.length;
var i = 0;
sections.hide('drop', { easing: 'easeInCubic', direction: 'left' }, 350, function () {
i++;
if (i == numSections) {
$('#section-editaddress').show('drop', { easing: 'easeInExpo', direction: 'right' }, 350).removeClass('hidden');
$.scrollTo($('#contentRegion'), 250, { margin: true });
}
});
});
}
// Switch complete
window.changingPage = false;
},
updateModel: function () {
var changedItems = {};
if (this.model.get('csrf') != $(this.el).find("[name=csrf]").val())
changedItems.csrf = $(this.el).find("[name=csrf]").val();
// ...
},
beginSaving: function () {
alert('test');
}
});
});
Can anyone see what I've missed?
Whenever you need to change or modify the DOM element of a BackboneJS view manually, you should use setElement rather than setting the property directly. It moves all of the event handlers to the newly attached DOM element and also sets the $el property. In addition, the function also detaches any existing event handlers.
So, in the code you pasted, you'd just change it to:
this.setElement($('#section-editaddress'));

Categories

Resources