The default behaviour of the discuss module in Odoo 9 is:
Last message at the bottom
Scrollbar at the bottom
I need to change this behaviour to:
Last message on top
Scrollbar on top
I think all this can be changed in odoo/addons/mail/static/src/js/thread.js
The order of messages is changed with:
init: function (parent, options) {
this._super.apply(this, arguments);
this.options = _.defaults(options || {}, {
//display_order: ORDER.ASC,
display_order: ORDER.DESC,
display_needactions: true,
display_stars: true,
display_document_link: true,
display_avatar: true,
shorten_messages: true,
squash_close_messages: true,
display_reply_icon: false,
});
this.expanded_msg_ids = [];
this.selected_id = null;
},
But then the user always has to scroll up so I also need to modify the behaviour of the scrollbar.
In this thread: Set scroll position I found it could be done with:
window.scrollTo(0, 0); // values are x,y-offset
or
var el = document.getElementById("myel"); // Or whatever method to get the element
// To set the scroll
el.scrollTop = 0;
el.scrollLeft = 0;
This is some code I found in odoo/addons/mail/static/src/js/thread.js:
/**
* Scrolls the thread to a given message or offset if any, to bottom otherwise
* #param {int} [options.id] optional: the id of the message to scroll to
* #param {int} [options.offset] optional: the number of pixels to scroll
*/
scroll_to: function (options) {
window.alert("In function scroll_to");
options = options || {};
if (options.id !== undefined) {
var $target = this.$('.o_thread_message[data-message-id=' + options.id + ']');
if (options.only_if_necessary) {
var delta = $target.parent().height() - $target.height();
var offset = delta < 0 ? 0 : delta - ($target.offset().top - $target.offsetParent().offset().top);
offset = - Math.min(offset, 0);
this.$el.scrollTo("+=" + offset + "px", options);
} else if ($target.length) {
this.$el.scrollTo($target);
}
} else if (options.offset !== undefined) {
this.$el.scrollTop(options.offset);
} else {
window.alert("55555555555");
window.alert("55555555555" + this.el.scrollHeight);
//this.$el.scrollTop(this.el.scrollHeight);
this.$el.scrollTop(20);
}
},
get_scrolltop: function () {
return this.$el.scrollTop();
},
is_at_bottom: function () {
window.alert("777777777777777");
return this.el.scrollHeight - this.$el.scrollTop() - this.$el.outerHeight() < 5;
},
unselect: function () {
this.$('.o_thread_message').removeClass('o_thread_selected_message');
this.selected_id = null;
},
});
I've put some alertboxes to see what happens when the page is loaded.
It seems it goes straight to the part with "5555555".
I've tried some changes there to adjust the scrollbar but nothing happens.
Does anyone have an idea on how to get the scrollbar on top as default?
Hi: Last message on top you can see this page:
Change message order in discuss odoo 9
I have test it at Odoo10 And it run well:
display_order set as DESC
init: function (parent, options) {
this._super.apply(this, arguments);
this.options = _.defaults(options || {}, {
display_order: ORDER.DESC,
},
through an friend's Help,Now I'm OK
share the code :
scroll_to: function (options) {
options = options || {};
if (options.id !== undefined) {
var $target = this.$('.o_thread_message[data-message-id="' + options.id + '"]');
if (options.only_if_necessary) {
var delta = $target.parent().height() - $target.height();
var offset = delta < 0 ? 0 : delta - ($target.offset().top - $target.offsetParent().offset().top);
offset = - Math.min(offset, 0);
this.$el.scrollTo("+=" + offset + "px", options);
} else if ($target.length) {
this.$el.scrollTo($target);
}
} else if (options.offset !== undefined) {
this.$el.scrollTop(options.offset);
} else {
// this.$el.scrollTop(this.el.scrollHeight);
this.$el.scrollTop();
console.log("flag 4");
}
},
Related
I'm having some problems with the access of this methods and variables, and then i just got return undefined.
Im trying for example:
var slider = animateSlider();
slider.init();
slider.current;
I just want to have the access of all this function's value to manipulate the plugin with his callbacks.
(function($,window,document,undefined)
{
var Arrow = function (pages) {
this.pages = pages;
if(this.pages === 1) {
$('.prev').css({"opacity": "0", "z-index": "-1"});
} else {
$('.prev').css({"opacity": "1", "z-index": "1"});
}
if(this.pages < $('.anim-slider > .anim-slide').length) {
$('.next').css({"opacity": "1", "z-index": "1"});
} else {
$('.next').css({"opacity": "0", "z-index": "-1"});
}
};
/**
* [Create the contructor of animateSlider Plugin]
* #param {object} element [the element the plugin is chain to]
* #param {object} options [plugin's configuration object]
*/
var animateSlider = function(element,options)
{
this.element = element;
this.$element = $(element);
this.options = options;
};
animateSlider.prototype =
{
/**
* [Initialize the plugin]
*/
init : function()
{
//Use Modernizr
this.cssAnimations = Modernizr.cssanimations;
this.cssTransitions = Modernizr.csstransitions;
if (!this.cssAnimations || !this.cssTransitions)
{
throw new Error("Your broswer does not support CSS3 Animations or Transitions");
}
this.config = $.extend({},this.defaults,this.options);
this.slides = this.$element.children(".anim-slide");
this.slidesCount = this.slides.length;
this.interval = [];//Ovveride config.interval
this.current = 0; //first slide
$('.contador').html(parseInt(this.current + 1) + ' de ' + this.slidesCount);
var $dots = $("<div class=\"anim-dots\"></div>");
var temp = this.slidesCount;
while ( temp --)
{
$dots.append("<span></span>");
}
$dots.appendTo(this.$element);
this.slides.eq(this.current).addClass("anim-slide-this");
this.$dots = this.$element.find(".anim-dots>span");
this.$navNext = $(".anim-arrows-next");
this.$navPrev = $(".anim-arrows-prev");
this.loadEvents();
this.navigate(this.current);
this.updateDots();
this.autoplay();
},
/**
* [Go to current slide and set the proper classes to animate the elements]
* #param {number} page [current slide]
*/
navigate : function(page)
{
//Classes created from animate.css, you can add your own here.
var classes = 'bounce flash pulse rubberBand shake swing tada wobble bounceIn bounceInDown bounceInRight bounceInUp bounceOut bounceOutDown bounceOutLeft bounceOutRight bounceOutUp fadeIn fadeInDown fadeInDownBig fadeInLeft fadeInLeftBig fadeInRight fadeInRightBig fadeInUp fadeInUpBig fadeOut fadeOutDown fadeOutDownBig fadeOutLeft fadeOutLeftBig fadeOutRight fadeOutRightBig fadeOutUp fadeOutUpBig flipInX flipInY flipOutX flipOutY lightSpeedIn lightSpeedOut rotateIn rotateInDownLeft rotateInDownRight rotateInUpLeft rotateInUpRight rotateOut rotateOutDownLeft rotateOutDownRight rotateOutUpLeft rotateOutUpRight slideInDown slideInLeft slideInRight slideOutLeft slideOutRight slideOutUp slideInUp slideOutDown hinge rollIn rollOut fadeInUpLarge fadeInDownLarge fadeInLeftLarge fadeInRightLarge fadeInUpLeft fadeInUpLeftBig fadeInUpLeftLarge fadeInUpRight fadeInUpRightBig fadeInUpRightLarge fadeInDownLeft fadeInDownLeftBig fadeInDownLeftLarge fadeInDownRight fadeInDownRightBig fadeInDownRightLarge fadeOutUpLarge fadeOutDownLarge fadeOutLeftLarge fadeOutRightLarge fadeOutUpLeft fadeOutUpLeftBig fadeOutUpLeftLarge fadeOutUpRight fadeOutUpRightBig fadeOutUpRightLarge fadeOutDownLeft fadeOutDownLeftBig fadeOutDownLeftLarge fadeOutDownRight fadeOutDownRightBig fadeOutDownRightLarge bounceInBig bounceInLarge bounceInUpBig bounceInUpLarge bounceInDownBig bounceInDownLarge bounceInLeft bounceInLeftBig bounceInLeftLarge bounceInRightBig bounceInRightLarge bounceInUpLeft bounceInUpLeftBig bounceInUpLeftLarge bounceInUpRight bounceInUpRightBig bounceInUpRightLarge bounceInDownLeft bounceInDownLeftBig bounceInDownLeftLarge bounceInDownRight bounceInDownRightBig bounceInDownRightLarge bounceOutBig bounceOutLarge bounceOutUpBig bounceOutUpLarge bounceOutDownBig bounceOutDownLarge bounceOutLeftBig bounceOutLeftLarge bounceOutRightBig bounceOutRightLarge bounceOutUpLeft bounceOutUpLeftBig bounceOutUpLeftLarge bounceOutUpRight bounceOutUpRightBig bounceOutUpRightLarge bounceOutDownLeft bounceOutDownLeftBig bounceOutDownLeftLarge bounceOutDownRight bounceOutDownRightBig bounceOutDownRightLarge zoomIn zoomInUp zoomInUpBig zoomInUpLarge zoomInDown zoomInDownBig zoomInDownLarge zoomInLeft zoomInLeftBig zoomInLeftLarge zoomInRight zoomInRightBig zoomInRightLarge zoomInUpLeft zoomInUpLeftBig zoomInUpLeftLarge zoomInUpRight zoomInUpRightBig zoomInUpRightLarge zoomInDownLeft zoomInDownLeftBig zoomInDownLeftLarge zoomInDownRight zoomInDownRightBig zoomInDownRightLarge zoomOut zoomOutUp zoomOutUpBig zoomOutUpLarge zoomOutDown zoomOutDownBig zoomOutDownLarge zoomOutLeft zoomOutLeftBig zoomOutLeftLarge zoomOutRight zoomOutRightBig zoomOutRightLarge zoomOutUpLeft zoomOutUpLeftBig zoomOutUpLeftLarge zoomOutUpRight zoomOutUpRightBig zoomOutUpRightLarge zoomOutDownLeft zoomOutDownLeftBig zoomOutDownLeftLarge zoomOutDownRight zoomOutDownRightBig zoomOutDownRightLarge flipInTopFront flipInTopBack flipInBottomFront flipInBottomBack flipInLeftFront flipInLeftBack flipInRightFront flipInRightBack flipOutTopFront flipOutTopBack flipOutBottomFront flipOutBottomback flipOutLeftFront flipOutLeftBack flipOutRightFront flipOutRightBack strobe shakeX shakeY spin spinReverse slingshot slingshotReverse pulsate heartbeat panic';
var classShow,classHide,delayShow,$next,$current,currentAnimate,nextAnimate;
$current = this.slides.eq(this.current);
currentAnimate = this.elemAnimate(this.current,this.config);
this.current = page;
$next = this.slides.eq(this.current);
nextAnimate = this.elemAnimate(this.current,this.config);
/*=========================================*/
$current.removeClass(" anim-slide-this "+classes);
$current.find("*").removeClass(classes);
//Iterate through a javascript plain object of current and next Slide
$.each(currentAnimate,function(index)
{
if ( index == $current.prop("tagName").toLowerCase() )
{
classHide = $current.data("classHide");
delayShow = $current.data("delayShow");
$current.removeClass(delayShow);
$current.addClass(classHide+" animated");
return false;
}
else
{
classHide = $current.find(index).data("classHide");
delayShow = $current.find(index).data("delayShow");
$current.find(index).removeClass(delayShow);
$current.find(index).addClass(classHide+" animated");
}
});
$.each(nextAnimate,function(index)
{
if ( index == $current.prop("tagName").toLowerCase() )
{
classShow = $next.data("classShow") ;
delayShow = $next.data("delayShow");
$next.removeClass(classes);
$next.addClass(classShow+" "+delayShow+" animated");
return false;
}
else
{
classShow = $next.find(index).data("classShow");
delayShow = $next.find(index).data("delayShow");
$next.find(index).removeClass(classes);
$next.find(index).addClass(classShow+" "+delayShow+" animated ");
}
});
$next.addClass(" anim-slide-this");
/*=========================================*/
this.updateDots();
},
/**
* [Update the dots to the current slide]
*/
updateDots : function()
{
this.$dots.removeClass("anim-dots-this");
this.$dots.eq(this.current).addClass("anim-dots-this");
},
/**
* [If the dots are clicked the autoplay procedure stops
* and you navigate to the current slide]
* #param {number} page [current slide]
*/
dots : function(page)
{
if ( page >= this.slidesCount || page < 0)
{
return false;
}
if (this.config.autoplay)
{
clearTimeout(this.autoplay);
this.config.autoplay = false;
}
this.navigate(page);
},
/**
* [Get the configuration object for each slide element and attach it to elements with $.data]
* #param {number} page [current slide]
* #param {object} config [configuration object]
*/
elemAnimate : function(page,config)
{
if ( typeof config.animations == "object" )
{
if ( this.slidesCount !== Object.keys(config.animations).length )
{
throw new SyntaxError("Slides length and animation Object length must be equal.");
}
//Get the selected Slide configuration object
var animations = config.animations[page];
var $current = this.slides.eq(page);
return $.each(animations,function(index,value)
{
if ( index == $current.prop("tagName").toLowerCase() )
{
if ( $current.data("classShow") == null )
{
if ( typeof value.show === "string" ) { $current.data("classShow",value.show); } else { $current.data("classShow",""); }
if ( typeof value.hide === "string" ) { $current.data("classHide",value.hide); } else { $current.data("classHide",""); }
if ( typeof value.delayShow === "string" ) { $current.data("delayShow",value.delayShow); } else { $current.data("delayShow"," "); }
}
return false;
}
else
{
if ( !$current.find(index)[0] )
{
throw new TypeError("The element \'"+index+"\' does not exist.");
}
if ( $current.find(index).data("classShow") == null )
{
if( typeof value.show === "string" ) { $current.find(index).data("classShow",value.show); } else { $current.find(index).data("classShow"," "); }
if( typeof value.hide === "string" ) { $current.find(index).data("classHide",value.hide); } else { $current.find(index).data("classHide"," "); }
if( typeof value.delayShow === "string" ) { $current.find(index).data("delayShow",value.delayShow); } else { $current.find(index).data("delayShow"," "); }
}
}
});
}
},
/**
* [Call the animDuration for each slide and if the animation time of current slide is bigger than
* config.interval replace it with this.inteval, else leave config.interval with the default value]
*/
autoplay : function()
{
if (this.config.autoplay)
{
var page = this.current;
var that = this;
var loop = function()
{
page = ( page >= that.slidesCount -1 || page < 0 ) ? 0 : page + 1;
that.navigate(page);
that.autoplay();
};
if ( this.interval.length === this.slidesCount )
{
this.autoplayTime = setTimeout(loop,this.interval[page]);
return;
}
this.animDuration(page).done(function(animationTime)
{
if( animationTime >= that.config.interval )
{
that.interval[page] = animationTime;
that.autoplayTime = setTimeout(loop,0);
}
else if( animationTime < that.config.interval )
{
that.interval[page] = that.config.interval;
that.autoplayTime = setTimeout(loop,that.config.interval-animationTime);
}
});
}
},
/**
* [Find the total animation time for the current slide]
* #param {number} page [current slide]
* #return {object} promise [jQuery's Promises to make asynchronous call]
*/
animDuration: function(page)
{
var $slideAnimations = this.slides.eq(page);
var slideAnimationsCount = $slideAnimations.children("*.animated").length;
var animationStart = +new Date();
var promise = new $.Deferred();
var animationTime,count = 0;
$slideAnimations.on("animationend webkitAnimationEnd oanimationend MSAnimationEnd",function()
{
var animationEnd = +new Date();
animationTime = Math.ceil((animationEnd -animationStart)/1000)*1000;
count++;
if (count == slideAnimationsCount)
{
promise.resolve(animationTime);
}
});
return promise;
},
/**
* [Attach events handlers to specific tasks]
*/
loadEvents : function()
{
var that = this;
this.$navNext.on("click",function(event)
{
if(parseInt(that.current + 1) < that.slidesCount) {
if (that.config.autoplay)
{
clearTimeout(that.autoplay);
that.config.autoplay = false;
}
var page = (that.current >= that.slidesCount - 1 ) ? 0 : that.current + 1 ;
that.navigate(page,"next");
Arrow(parseInt(page + 1));
// contador de paginas
$('.contador').html(parseInt(page + 1) + ' de ' + that.slidesCount);
}
event.preventDefault();
});
this.$navPrev.on("click",function(event)
{
if(parseInt(that.current + 1) !== 1) {
if (that.config.autoplay)
{
clearTimeout(that.autoplay);
that.config.autoplay = false;
}
var page = ( that.current === 0 )? that.slidesCount - 1 : that.current - 1;
that.navigate(page,"prev");
Arrow(parseInt(page + 1));
// contador de paginas
$('.contador').html(parseInt(page + 1) + ' de ' + that.slidesCount);
}
event.preventDefault();
});
this.$dots.on("click.slide",function(event)
{
var page = $(this).index();
that.dots(page);
event.preventDefault();
});
},
defaults :
{
autoplay : true,
interval : 5000
}
};
/**
* [Attach the plugin to jQuery's prototype]
* #param {object} options [plugin's configuration object]
* #return {object} this [the jQuery wrapper]
*/
$.fn.animateSlider = function(options)
{
return this.each(function()
{
var instance = $.data(this,"animateSlider");
if (!instance)
{
$.data(this,"animateSlider",new animateSlider(this,options).init());
}
});
};
})(jQuery);
The plugin you are trying to use is this.
README file clearly mentions how to use the plugin.
$(<jquerySelector>).animateSlider();
You cannot just pick and choose the functions you want to access from an IIFE unless it puts variables onto the global scope in case of jquery plugins they usually attach themselves onto the $ scope via $.fn in this case $.fn.animateSlider which means you can only access them via the above syntax.
But if you adamant on accessing variables inside an IIFE.
I will give the steps to access Arrow variable:
Change var Arrow = function (pages) { to Arrow = function (pages).
In main js file. Create an Instance var obj_name = new Arrow(1);
Though I cant think of why you would want to do that and if this is the only way then your design is wrong.
someone have a an idea about lazy loading. It doesn't seems to work on my page.
I did something wrong ?
here is my page -> http://500milligrammes.com/fmzz/final/test.html
here is my html part:
<img class="lazy img-responsive" src="...jpg" data-original="...jpg" alt=""/>
here is my js part:
$(function() {
$("img.lazy").lazyload({
effect : "fadeIn"
});
here is my jquery.lazyload.js page:
/*!
* Lazy Load - jQuery plugin for lazy loading images
*
* Copyright (c) 2007-2015 Mika Tuupola
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/mit-license.php
*
* Project home:
* http://www.appelsiini.net/projects/lazyload
*
* Version: 1.9.5
*
*/
(function($, window, document, undefined) {
var $window = $(window);
$.fn.lazyload = function(options) {
var elements = this;
var $container;
var settings = {
threshold : 0,
failure_limit : 0,
event : "scroll",
effect : "show",
container : window,
data_attribute : "original",
skip_invisible : false,
appear : null,
load : null,
placeholder : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC"
};
function update() {
var counter = 0;
elements.each(function() {
var $this = $(this);
if (settings.skip_invisible && !$this.is(":visible")) {
return;
}
if ($.abovethetop(this, settings) ||
$.leftofbegin(this, settings)) {
/* Nothing. */
} else if (!$.belowthefold(this, settings) &&
!$.rightoffold(this, settings)) {
$this.trigger("appear");
/* if we found an image we'll load, reset the counter */
counter = 0;
} else {
if (++counter > settings.failure_limit) {
return false;
}
}
});
}
if(options) {
/* Maintain BC for a couple of versions. */
if (undefined !== options.failurelimit) {
options.failure_limit = options.failurelimit;
delete options.failurelimit;
}
if (undefined !== options.effectspeed) {
options.effect_speed = options.effectspeed;
delete options.effectspeed;
}
$.extend(settings, options);
}
/* Cache container as jQuery as object. */
$container = (settings.container === undefined ||
settings.container === window) ? $window : $(settings.container);
/* Fire one scroll event per scroll. Not one scroll event per image. */
if (0 === settings.event.indexOf("scroll")) {
$container.bind(settings.event, function() {
return update();
});
}
this.each(function() {
var self = this;
var $self = $(self);
self.loaded = false;
/* If no src attribute given use data:uri. */
if ($self.attr("src") === undefined || $self.attr("src") === false) {
if ($self.is("img")) {
$self.attr("src", settings.placeholder);
}
}
/* When appear is triggered load original image. */
$self.one("appear", function() {
if (!this.loaded) {
if (settings.appear) {
var elements_left = elements.length;
settings.appear.call(self, elements_left, settings);
}
$("<img />")
.bind("load", function() {
var original = $self.attr("data-" + settings.data_attribute);
$self.hide();
if ($self.is("img")) {
$self.attr("src", original);
} else {
$self.css("background-image", "url('" + original + "')");
}
$self[settings.effect](settings.effect_speed);
self.loaded = true;
/* Remove image from array so it is not looped next time. */
var temp = $.grep(elements, function(element) {
return !element.loaded;
});
elements = $(temp);
if (settings.load) {
var elements_left = elements.length;
settings.load.call(self, elements_left, settings);
}
})
.attr("src", $self.attr("data-" + settings.data_attribute));
}
});
/* When wanted event is triggered load original image */
/* by triggering appear. */
if (0 !== settings.event.indexOf("scroll")) {
$self.bind(settings.event, function() {
if (!self.loaded) {
$self.trigger("appear");
}
});
}
});
/* Check if something appears when window is resized. */
$window.bind("resize", function() {
update();
});
/* With IOS5 force loading images when navigating with back button. */
/* Non optimal workaround. */
if ((/(?:iphone|ipod|ipad).*os 5/gi).test(navigator.appVersion)) {
$window.bind("pageshow", function(event) {
if (event.originalEvent && event.originalEvent.persisted) {
elements.each(function() {
$(this).trigger("appear");
});
}
});
}
/* Force initial check if images should appear. */
$(document).ready(function() {
update();
});
return this;
};
/* Convenience methods in jQuery namespace. */
/* Use as $.belowthefold(element, {threshold : 100, container : window}) */
$.belowthefold = function(element, settings) {
var fold;
if (settings.container === undefined || settings.container === window) {
fold = (window.innerHeight ? window.innerHeight : $window.height()) + $window.scrollTop();
} else {
fold = $(settings.container).offset().top + $(settings.container).height();
}
return fold <= $(element).offset().top - settings.threshold;
};
$.rightoffold = function(element, settings) {
var fold;
if (settings.container === undefined || settings.container === window) {
fold = $window.width() + $window.scrollLeft();
} else {
fold = $(settings.container).offset().left + $(settings.container).width();
}
return fold <= $(element).offset().left - settings.threshold;
};
$.abovethetop = function(element, settings) {
var fold;
if (settings.container === undefined || settings.container === window) {
fold = $window.scrollTop();
} else {
fold = $(settings.container).offset().top;
}
return fold >= $(element).offset().top + settings.threshold + $(element).height();
};
$.leftofbegin = function(element, settings) {
var fold;
if (settings.container === undefined || settings.container === window) {
fold = $window.scrollLeft();
} else {
fold = $(settings.container).offset().left;
}
return fold >= $(element).offset().left + settings.threshold + $(element).width();
};
$.inviewport = function(element, settings) {
return !$.rightoffold(element, settings) && !$.leftofbegin(element, settings) &&
!$.belowthefold(element, settings) && !$.abovethetop(element, settings);
};
/* Custom selectors for your convenience. */
/* Use as $("img:below-the-fold").something() or */
/* $("img").filter(":below-the-fold").something() which is faster */
$.extend($.expr[":"], {
"below-the-fold" : function(a) { return $.belowthefold(a, {threshold : 0}); },
"above-the-top" : function(a) { return !$.belowthefold(a, {threshold : 0}); },
"right-of-screen": function(a) { return $.rightoffold(a, {threshold : 0}); },
"left-of-screen" : function(a) { return !$.rightoffold(a, {threshold : 0}); },
"in-viewport" : function(a) { return $.inviewport(a, {threshold : 0}); },
/* Maintain BC for couple of versions. */
"above-the-fold" : function(a) { return !$.belowthefold(a, {threshold : 0}); },
"right-of-fold" : function(a) { return $.rightoffold(a, {threshold : 0}); },
"left-of-fold" : function(a) { return !$.rightoffold(a, {threshold : 0}); }
});
})(jQuery, window, document);
First Issue:
You need to close down the ready function.
$(function() {
$("img.lazy").lazyload({
effect : "fadeIn"
});
});
Second Issue:
Here is the usage on how to implement the lazy load on the site:
<img class="lazy" data-original="img/example.jpg" width="640" height="480">
Now with this you have to store it within a data attribute (the image itself). What you are doing is calling the image within the src and also within the data attribute. Now when the page loads, it actually starts the process of loading the images with src and then the lazy load happens.
I would recommend not to use an img tag as placeholder. The src attribute is mandatory and so you generate invalid HTML.
And I also wouldn't use jQuery for such a tiny functionality.
The following justlazy plugin provides valid HTML without jQuery.
1. Define a placeholder
<span data-src="default/image" data-alt="some alt text"
class="justlazy-placeholder">
</span>
2. Register lazy loading
$(function() {
Justlazy.registerLazyLoadByClass("justlazy-placeholder");
});
I'm trying to use the jQuery appear plugin. I'm having trouble making it work. I tried to attach it to the (window).scroll event but it makes the page slow. If I don't use the scroll, it only fires once. I need it to work again whenever the element becomes visible. Can you give me some tips on how to make it work.
Here's my code:
jQuery('.home-section-1').appear(function(){
jQuery('.page-scroll-indicator .fa.fa-circle').removeClass('active-ind');
jQuery('.page-scroll-indicator .section-1').addClass('active-ind');
});
As ɴ-ᴀ-ᴛ-ʜ said in his comment, you need to be using .on to listen for the appear event.
jQuery('.home-section-1').on('appear', function(){
jQuery('.page-scroll-indicator .fa.fa-circle').removeClass('active-ind');
jQuery('.page-scroll-indicator .section-1').addClass('active-ind');
});
Here's a code snippit showing it working, you'll notice that your method (Method 1) doesn't fire, while the method above (Method 2) does:
/*
* jQuery appear plugin
*
* Copyright (c) 2012 Andrey Sidorov
* licensed under MIT license.
*
* https://github.com/morr/jquery.appear/
*
* Version: 0.3.4
*/
(function($) {
var selectors = [];
var check_binded = false;
var check_lock = false;
var defaults = {
interval: 250,
force_process: false
}
var $window = $(window);
var $prior_appeared;
function process() {
check_lock = false;
for (var index = 0, selectorsLength = selectors.length; index < selectorsLength; index++) {
var $appeared = $(selectors[index]).filter(function() {
return $(this).is(':appeared');
});
$appeared.trigger('appear', [$appeared]);
if ($prior_appeared) {
var $disappeared = $prior_appeared.not($appeared);
$disappeared.trigger('disappear', [$disappeared]);
}
$prior_appeared = $appeared;
}
}
// "appeared" custom filter
$.expr[':']['appeared'] = function(element) {
var $element = $(element);
if (!$element.is(':visible')) {
return false;
}
var window_left = $window.scrollLeft();
var window_top = $window.scrollTop();
var offset = $element.offset();
var left = offset.left;
var top = offset.top;
if (top + $element.height() >= window_top &&
top - ($element.data('appear-top-offset') || 0) <= window_top + $window.height() &&
left + $element.width() >= window_left &&
left - ($element.data('appear-left-offset') || 0) <= window_left + $window.width()) {
return true;
} else {
return false;
}
}
$.fn.extend({
// watching for element's appearance in browser viewport
appear: function(options) {
var opts = $.extend({}, defaults, options || {});
var selector = this.selector || this;
if (!check_binded) {
var on_check = function() {
if (check_lock) {
return;
}
check_lock = true;
setTimeout(process, opts.interval);
};
$(window).scroll(on_check).resize(on_check);
check_binded = true;
}
if (opts.force_process) {
setTimeout(process, opts.interval);
}
selectors.push(selector);
return $(selector);
}
});
$.extend({
// force elements's appearance check
force_appear: function() {
if (check_binded) {
process();
return true;
};
return false;
}
});
})(jQuery);
// Your method
jQuery('.home-section-1').appear(function(){
alert('Method 1');
});
// Using .on
jQuery('.home-section-1').on('appear', function(){
alert('Method 2');
});
.home-section-1 {
margin-top: 2000px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="home-section-1">Hello World</div>
I got a pretty annoying javascript error. The world famous:
uncaught TypeError: Cannot read property "top" of null
Here is the code:
$(function() {
var setTitle = function(title, href) {
title = 'Derp: ' + title;
href = href || '';
history.pushState({id: href}, title, href.replace('#', '/'));
document.title = title;
},
scroll = function(url, speed) {
var href = typeof url == 'string' ? url : $(this).attr('href'),
target = $(href),
offset = target.offset(),
title = target.find('h1').text();
if(typeof url == 'number') {
target = [{id:''}];
offset = {top: url};
}
// And move the element
if(offset.top) {
// Set the new URL and title
setTitle(title, href);
// Make sure we're not moving the contact panel
if(target[0].id != 'contact') {
$('html, body').animate({scrollTop: offset.top}, speed);
}
}
return false;
};
// Handle existing URL fragments on load
if(location.pathname.length > 1) {
scroll(location.pathname.replace('/', '#'), 0);
}
$('a#logo').click(function() {
$('html,body').animate({scrollTop: 0});
return false;
});
// Handle internal link clicks
$('a[href^=#]:not(#logo)').click(scroll);
// Close the "Get In Touch" box
var box = $('#contact'),
moveBox = function() {
var closing = $(this).attr('class') == 'close',
amount = closing ? -(box.height() + 20) : 0,
cb = closing ? '' : function() { box.animate({marginTop: -10}, 150); };
box.animate({marginTop: amount}, cb);
};
box.css('margin-top', -(box.height() + 20));
$('#contact a.close, #get-in-touch').click(moveBox);
// Nasty little fix for vertical centering
$('.vertical').each(function() {
$(this).css('margin-top', -($(this).height() / 2));
});
// Work panels
var parent = $('#work'),
panels = parent.children('div');
panels.each(function() {
$(this).css('width', 100 / panels.length + '%');
})
parent.css('width', (panels.length * 100) + '%');
// Bind the keyboards
$(document).keyup(function(e) {
var actions = {
// Left
37: function() {
var prev = panels.filter('.active').prev().not('small');
if(prev.length > 0) {
prev.siblings().removeClass('active');
setTitle(prev.find('h1').text(), prev[0].id);
setTimeout(function() {
prev.addClass('active');
}, 250);
parent.animate({left: '+=100%'}).css('background-color', '#' + prev.attr('data-background'));
}
},
// Right
39: function() {
var next = panels.filter('.active').next();
if(next.length > 0) {
next.siblings().removeClass('active');
setTitle(next.find('h1').text(), next[0].id);
setTimeout(function() {
next.addClass('active');
}, 250);
parent.animate({left: '-=100%'}).css('background-color', '#' + next.attr('data-background'));
}
},
// Down
40: function() {
var w = $(window),
height = w.height() * panels.children('div').length,
h = w.height() + w.scrollTop();
if(h < height) {
scroll(h);
}
},
// Up
38: function() {
var w = $(window);
$('html,body').animate({scrollTop: w.scrollTop() - w.height()});
}
};
// Call a function based on keycode
if(actions[e.which]) {
actions[e.which]();
}
e.preventDefault();
return false;
});
// Fix crazy resize bugs
$(window).resize(function() {
var m = $(this),
h = m.height(),
s = m.scrollTop();
if((h - s) < (h / 2)) {
m.scrollTop(h);
}
//$('html,body').animate({scrollTop: s});
});
// slideshow
var woof = function() {
var slides = $('#molly li'),
active = slides.filter('.active');
if(!active.length) {
active = slides.last();
}
active.addClass('active');
var next = active.next().length ? active.next() : slides.first();
next.css('opacity', 0).addClass('active').animate({opacity: 1}, function() {
active.removeClass('active last-active');
});
};
setInterval(woof, 3000);
// easing
$.easing.swing = function(v,i,s,u,a,l) {
if((i /= a / 2) < 1) {
return u / 2 * (Math.pow(i, 3)) + s;
}
return u / 2 * ((i -= 2) * i * i + 2) + s;
};
// Change the default .animate() time: http://forr.st/~PG0
$.fx.speeds._default = 600;
});
try{Typekit.load()}catch(e){}
Sorry for this long monster but I thought it could be useful for you to see the whole thing. The Error warning shows up in this part:
// And move the element
if(offset.top) {
Uncaught TypeError: Cannot read property 'top' of null
It's line 23 in the code.
That's it. Could you give me a hint on how to solve this problem?
Thank you!
var href = typeof url == 'string' ? url : $(this).attr('href'),
target = $(href), //line 2
offset = target.offset(), //line 3
I believe this must have something to do with line 2, target should be null when error occurs
According to jQuery source, jQuery.fn.offset only returns null if:
the first element in the set doesn't exist (empty set) or
its ownerDocument is falsy (I don't know when that would happen, sorry).
The first option seems more likely, so you should check if target.length > 0 before calling target.offset() and handle the alternative.
Can somebody recommend an easy to use Lightbox plug-in for ExtJS 4?
The best I can do at the momemnt is http://dev.sencha.com/playpen/ext-core-latest/examples/lightbox/. This relates to ExtJS 3 and I cannot find an equivalent in ExtJS 4.
I found adapted version for ExtJS4 here .
/*!
* Based on the lightbox implementation in Ext Core Library 3.0
* http://www.lyquidity.com/
* Copyright(c) 2012, Lyquidity Solutions Limited.
*
* MIT Licensed - http://extjs.com/license/mit.txt
*/
Ext.define('Ext.ux.Lightbox', {
extend: 'Ext.Component',
alias: 'widget.lightbox',
id: 'ux-lightbox',
/**
* #cfg {int} activeImage The index of the current image.
*/
activeImage: undefined,
/**
* #cfg {boolean} animate True if the image change transition is to be animated.
*/
animate: true,
/**
* #cfg {int} borderSize The size of the image border.
*/
borderSize: 10,
/**
* #cfg {string} labelImage The term for 'image'.
*/
labelImage: "Image",
/**
* #cfg {string} labelOf The term for 'of' as in '3 of 7'.
*/
labelOf: "of",
/**
* #cfg {int} overlayOpacity The relativ opacity of the overlay (background). Default: 0.85.
*/
overlayOpacity: 0.85,
/**
* #cfg {HtmlElement} renderTo The element to which the lightbox will be rendered. Defaults to the body.
*/
renderTo: Ext.getBody(),
/**
* #cfg {int} resizeSpeed Time in milliseconds for the lightbox to resize to a new image. Default: 600ms.
*/
resizeSpeed: 600,
/**
* #cfg {string} containerTag The tag which contains the <img> tag. Default: 'a'.
*/
containerTag: 'a',
/**
* #cfg {string} missingImage The image to use to represent a missing image. Default: missing.png.
*/
missingImage: 'missing.png',
childEls: ['outerImageContainer', 'imageContainer', 'image', 'hoverNav', 'navPrev', 'navNext',
'outerDataContainer', 'dataContainer', 'data', 'details', 'caption', 'imageNumber', 'bottomNav', 'navClose'],
initComponent: function()
{
var me = this;
// This weirdness is here because I'm simulating what happens in the Designer
// which has a bug because renderTpl is set to null by ExtJS so Ext.applyIf()
// has no effect. Setting renderTpl to undefined solves the problem.
// You can remove this by using Ext.apply() instead or by moving the renderTpl
// out of the initComponent function.
this.renderTpl = undefined;
Ext.applyIf(me, {
renderTpl: [
'<div id="ux-lightbox-outerImageContainer">',
' <div id="ux-lightbox-imageContainer">',
' <img id="ux-lightbox-image">',
' <div id="ux-lightbox-hoverNav">',
' ',
' ',
' </div>',
' </div>',
'</div>',
'<div id="ux-lightbox-outerDataContainer">',
' <div id="ux-lightbox-dataContainer">',
' <div id="ux-lightbox-data">',
' <div id="ux-lightbox-details">',
' <span id="ux-lightbox-caption"></span>',
' <span id="ux-lightbox-imageNumber"></span>',
' </div>',
' <div id="ux-lightbox-bottomNav">',
' ',
' </div>',
' </div>',
' </div>',
'</div>'
],
images: [],
selectors: [],
listeners: {
beforerender: {
fn: me.onComponentBeforeRender,
scope: me
},
afterrender: {
fn: me.onComponentAfterRender,
scope: me
}
}
});
this.resizeDuration = this.animate ? this.resizeSpeed : 0;
this.overlayDuration = this.animate ? 200 : 0;
this.addEvents('open', 'close');
this.callParent(arguments);
},
onComponentBeforeRender: function () {
var me = this;
this.shim = Ext.DomHelper.append( this.renderTo,
{
tag: 'iframe',
id: 'ux-lightbox-shim'
}, true);
this.overlay = Ext.DomHelper.append(this.renderTo, {
id: 'ux-lightbox-overlay'
}, true);
},
onComponentAfterRender: function (container, position) {
var me = this;
Ext.each([this.overlay, this.shim, this.el], function(el){
el.setVisibilityMode(Ext.Element.DISPLAY).hide();
});
this.setVisible(false).setPosition(0, 20);
var size = (this.animate ? 250 : 1);
this.outerImageContainer.setSize(size,size);
this.outerDataContainer.setWidth(size).hide();
// Initialize events
var close = function(ev)
{
ev.preventDefault();
this.close();
};
this.overlay.on('click', close, this);
this.navClose.on('click', close, this);
this.el.on('click', function(ev)
{
if(ev.getTarget().id == this.getId())
this.close();
}, this);
this.navPrev.on('click', function(ev)
{
ev.preventDefault();
this.setImage(this.activeImage - 1);
}, this);
this.navNext.on('click', function(ev)
{
ev.preventDefault();
this.setImage(this.activeImage + 1);
}, this);
},
/**
* Called by user to register the images to include in the lightbox
* #property register
* #type Function
* #param {String} The selector to find the images in 'container' to register
* #param {boolean} True if the images are a group
* #param {String} The container which holds the images to use. Default: body
*/
register: function(sel, group, container)
{
this.thumbnailContainer = container
? container
: Ext.getBody();
if (this.renderTo != Ext.getBody())
{
this.renderTo.setStyle({position:'relative'});
}
var selector = {sel: sel, group: group};
var length = Ext.Array.filter(this.selectors, function(item) { return item.sel === sel; }, this).length;
if(length === 0)
{
this.selectors.push(selector);
// This ability to use functions like 'on' against the result of Ext.fly() or Ext.select() is good
Ext.select(sel, true, this.thumbnailContainer.dom).on('click', this.click, this);
}
},
/**
* Private function to handle the image click event
* #property click
* #type Function
* #param {HTMLElement} ev The contained item pressed
*/
click: function(ev)
{
try
{
// Get the parent of the clicked element which is the container
var up = Ext.get(ev.target).up(this.containerTag);
var target = undefined;
var selector = undefined;
Ext.each(this.selectors, function(item)
{
var els = Ext.select(item.sel, true);
var i = els.indexOf(up);
if (i === -1) return;
target = ev.getTarget(item.sel);
selector = item;
}, true );
if (target)
{
ev.preventDefault();
this.open(target, selector.sel, selector.group);
}
}
catch (e)
{
console.log(e.message);
}
},
remove: function()
{
this.clear();
Ext.enableNestedListenerRemoval = true;
Ext.each([this.overlay, this.shim], function(node)
{
if (!node) return;
Ext.removeNode(node.dom);
delete node;
});
this.destroy();
},
/**
* Clears the click events on all registered images
* #property clear
* #type Function
*/
clear: function()
{
this.close();
if (!this.selectors) return;
Ext.each(this.selectors, function(item)
{
Ext.select(item.sel, true, this.thumbnailContainer.dom).un('click', this.click, this);
},this);
this.selectors = [];
this.images = [];
},
/**
* Called automatically when the page has completely loaded. This is an empty function that should be
* overridden by each application that needs to take action on page load
* #property launch
* #type Function
* #param {HTMLElement} image The element containing the item to display
* #param {String} sel The selector for the image
* #param {Boolean} group True if this image is part of a group
*/
open: function(image, sel, group)
{
// Close the image if on is already open
if (this.isVisible())
{
Ext.callback( this.close, this, [this.open,image, sel, group] );
return;
}
group = group || false;
this.setViewSize();
this.overlay.setTop(0);
this.overlay.setLeft(0);
this.overlay.show();
this.overlay.setOpacity(0);
this.overlay.fadeIn({
duration: this.overlayDuration,
opacity: this.overlayOpacity,
callback: function() {
this.images = [];
var index = 0;
if(!group) {
this.images.push([image.href, image.title]);
}
else {
var setItems = Ext.query(sel,this.thumbnailContainer);
Ext.each(setItems, function(item){
var href = item.href;
href = item.getAttribute('href');
if(href) {
this.images.push([href, item.title]);
}
}, this);
while (index < this.images.length && this.images[index][0] != image.getAttribute('href')) {
index++;
}
}
// calculate top and left offset for the lightbox
var pageScroll = Ext.fly(this.renderTo.dom).getScroll();
var size = this.getViewSize();
var lightboxLeft = pageScroll.left;
this.setHeight(size.height);
this.setWidth(size.width);
this.setVisible(true);
this.setImage(index);
this.fireEvent('open', index == this.images.length ? undefined : this.images[index]);
},
scope: this
});
},
setViewSize: function()
{
var viewSize = this.getViewSize();
this.overlay.setStyle({
width: viewSize.width + 'px',
height: viewSize.height + 'px'
});
this.shim.setStyle({
width: viewSize.width + 'px',
height: viewSize.height + 'px'
}).show();
},
setImage: function(index)
{
this.activeImage = index;
this.disableKeyNav();
if (this.animate)
this.showMask();
this.image.fadeOut({
duration: this.resizeDuration,
scope: this,
callback: function()
{
this.image.hide();
delete this.lastImage;
this.hoverNav.hide();
this.navPrev.hide();
this.navNext.hide();
this.dataContainer.setOpacity(0.0001);
this.imageNumber.hide();
var imageSrc = (this.activeImage >= this.images.length)
? this.missingImage
: this.images[this.activeImage][0];
this.preview = Ext.create('Ext.Img',
{
src: imageSrc,
hidden: true,
renderTo: Ext.getBody(),
listeners : {
load : {
element : 'el', //the rendered img element
fn : function()
{
this.image.dom.src = this.preview.src;
this.resizeImage(this.preview.el.dom.width, this.preview.el.dom.height);
Ext.removeNode(this.preview.el.dom);
delete this.preview;
},
scope: this
}
}
});
}
});
},
resizeImage: function(w, h)
{
this.hideMask();
var wCur = this.outerImageContainer.getWidth();
var hCur = this.outerImageContainer.getHeight();
// The w and h could exceed the displayable area so this
// should be checked and the w/h changed accordingly.
var guesstimateDetailsSize = 60;
var size = this.getViewSize();
while ((w+this.borderSize * 2 > size.width) || (h+this.borderSize * 2 + guesstimateDetailsSize > size.height))
{
if (w+this.borderSize * 2 > size.width)
{
// Compute the new width
var wtemp = size.width - this.borderSize * 2;
// Compute the corresponding height so the aspect ratio remains the same
var htemp = Math.abs(wtemp/w*h);
w = wtemp;
h = htemp;
}
if (h+this.borderSize * 2 + guesstimateDetailsSize > size.height)
{
// Compute the new height
var htemp = size.height - guesstimateDetailsSize - this.borderSize * 2;
// Compute the corresponding width so the aspect ratio remains the same
var wtemp = Math.abs(htemp/h*w);
w = wtemp;
h = htemp;
}
}
var wNew = (w + this.borderSize * 2);
var hNew = (h + this.borderSize * 2);
var wDiff = wCur - wNew;
var hDiff = hCur - hNew;
var afterResize = function(){
this.hoverNav.setWidth(this.imageContainer.getWidth() + 'px');
this.navPrev.setHeight(h + 'px');
this.navNext.setHeight(h + 'px');
this.outerDataContainer.setWidth(wNew + 'px');
this.outerDataContainer.show();
this.showImage(w,h);
};
if (hDiff != 0 || wDiff != 0)
{
this.outerImageContainer.animate(
{
height: hNew,
width: wNew,
duration: this.resizeDuration,
scope: this,
callback: afterResize,
delay: 50
});
}
else
{
afterResize.call(this);
}
},
hideMask: function()
{
if (Ext.getVersion().major === 4 && Ext.getVersion().minor === 0)
{
if (!this.mask) return
this.mask.hide();
}
else
this.imageContainer.unmask();
},
showMask: function()
{
if (Ext.getVersion().major === 4 && Ext.getVersion().minor === 0)
{
if (!this.mask)
{
this.mask = new Ext.LoadMask(this.imageContainer, {});
}
this.mask.show();
}
else
this.imageContainer.mask('Loading image...');
},
showImage: function(w,h)
{
// Is it already displayed?
if (this.lastImage && this.lastImage == this.image.dom.src)
return;
this.lastImage = this.image.dom.src;
this.image.setWidth(w);
this.image.setHeight(h);
this.image.setOpacity(0);
this.image.show({
duration: this.resizeDuration,
scope: this,
callback: this.updateDetails()
});
this.preloadImages();
},
updateDetails: function()
{
var detailsWidth = this.data.getWidth(true) - this.navClose.getWidth() - 10;
this.details.setWidth((detailsWidth > 0 ? detailsWidth : 0) + 'px');
this.caption.update((this.activeImage >= this.images.length) ? '' : this.images[this.activeImage][1]);
this.caption.show();
if (this.images.length > 1) {
this.imageNumber.update(this.labelImage + ' ' + (this.activeImage + 1) + ' ' + this.labelOf + ' ' + this.images.length);
this.imageNumber.show();
}
this.dataContainer.fadeIn({
duration: this.resizeDuration/2,
scope: this,
callback: function() {
var viewSize = this.getViewSize();
this.overlay.setHeight(viewSize.height + 'px');
var total = this.outerDataContainer.getHeight() + this.outerImageContainer.getHeight();
this.setHeight(total);
var position = this.getPosition(true);
var distance = Math.abs((this.getViewSize().height - total)/2) - position[1];
this.el.move("b", distance, {
duration: this.resizeDuration,
scope: this,
callback: this.updateNav()
});
}
});
},
updateNav: function()
{
this.enableKeyNav();
this.hoverNav.show();
// if not first image in set, display prev image button
if (this.activeImage > 0)
this.navPrev.show();
// if not last image in set, display next image button
if (this.activeImage < (this.images.length - 1))
this.navNext.show();
},
enableKeyNav: function()
{
Ext.fly(this.renderTo.dom).on('keydown', this.keyNavAction, this);
},
disableKeyNav: function()
{
Ext.fly(this.renderTo.dom).un('keydown', this.keyNavAction, this);
},
keyNavAction: function(ev)
{
var keyCode = ev.getKey();
if (
keyCode == 88 || // x
keyCode == 67 || // c
keyCode == 27
) {
this.close();
}
else if (keyCode == 80 || keyCode == 37){ // display previous image
if (this.activeImage != 0){
this.setImage(this.activeImage - 1);
}
}
else if (keyCode == 78 || keyCode == 39){ // display next image
if (this.activeImage != (this.images.length - 1)){
this.setImage(this.activeImage + 1);
}
}
},
preloadImages: function()
{
var next, prev;
if (this.images.length > this.activeImage + 1)
{
next = new Image();
next.src = this.images[this.activeImage + 1][0];
}
if (this.activeImage > 0)
{
prev = new Image();
prev.src = this.images[this.activeImage - 1][0];
}
},
close: function(callback,image, sel, group)
{
if (!this.isVisible()) return;
this.hideMask();
this.disableKeyNav();
this.setVisible(false);
this.shim.hide();
this.overlay.fadeOut({
duration: this.overlayDuration,
stopAnimation: true,
scope: this,
callback: function()
{
if (this.overlay)
this.overlay.hide();
if (callback)
Ext.callback(callback, this, [image, sel, group], 1);
}
});
this.fireEvent('close', this.activeImage);
},
getViewSize: function()
{
return {width: this.renderTo.getWidth(), height: this.renderTo.getHeight()};
}
});
I'm not able to leave a comment, but some people on the Sencha forums have already tried to build off the example you linked to: http://www.sencha.com/forum/showthread.php?138296-Ext.ux.Lightbox
I think your best bet is to build off their existing work to adapt it for Ext 4.