I have a SPA that I upgraded to 2 which I had some initial issues with but all working now. However I have noticed that the nice transition when switching views no longer works and would like to retain it.
Shell.html:
<div class="loader" data-bind="css: { active: router.isNavigating }">
<img src="/scale/images/379.gif" />
</div>
<div id="pageHost" class="page-host">
<!--ko compose: {
model: router.activeItem,
compositionComplete: router.compositionComplete,
attached: router.attached,
cacheViews:false,
transition: 'entrance'} -->
<!--/ko-->
</div>
As you can see the transition is defined as expected and all the views work and the animated gif displays when loading. Is there anything I've missed? Let me know if you need to see main.js or other code.
EDIT:
It also appears to be the case that views are still cached despite the setting above. It's almost like the above settings are ignored.
EDIT 2
Changed to below as per upgrade info in docs:
<div id="pageHost" class="page-host" data-bind="css: { active: router.isNavigating }">
<!--ko router: { transition:'entrance', cacheViews:false }--><!--/ko-->
</div>
Everything still seems to be working but still no transitions and I'm sure views are still cached.
You can try writing your own transition like this one (depends on animate.css, jquery and Q)
define(function(require) {
var
$ = require('jquery'),
Q = require('q');
$.fn.animationDuration = function (sec) {
$(this).css({
'-webkit-animation-duration': sec + 's',
'-moz-animation-duration': sec + 's',
'-ms-animation-duration': sec + 's',
'animation-duration': sec + 's'
});
return this;
};
return function (context) {
console.log('transitioning views', context);
var afterAnimateActiveView = function (view) {
view && $(view).hide();
},
duration = context.duration || {},
durationIn = duration.in || 0.5,
durationOut = duration.out || 0.5;
return animateView(context.activeView, durationOut, 'fadeOut', afterAnimateActiveView)
.then(function (activeView) {
//hide active view after animation to prevent flickering
activeView && $(activeView).hide();
return animateView(context.child, durationIn, 'fadeIn');
})
.then(function () {
console.log('transition complete');
});
};
function animateView(view, duration, animation, cb) {
var dfd = Q.defer();
if (view) {
console.log('transitioning view', view);
$(view)
.animationDuration(duration)
.addClass('animated')
.addClass(animation)
.show()
.one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function () {
$(view)
.removeClass('animated')
.removeClass(animation);
//need to use callback here to do syncronous manipulations
cb && cb(view);
dfd.resolve();
});
} else {
dfd.resolve(true);
}
return dfd.promise;
}
});
It turns out that when used Nuget to update Durandal to 2 it didn't update the animate.css file with the classes the entrance transition was using. Adding these classes resolved it straight away.
Related
This is driving my crazy, the first angular-slick is not working but the second is just fine, any idea what is going on?
I created a plunkr (in case someone is looking for an example in the future), but my problem is very odd because in my code/realproject is not working so I don't know what the hell is going on, anyway! here is the plunkr: http://plnkr.co/edit/URIbhoVpm1OcLSQqISPs?p=preview
I think the problem is related to the DOM because maybe angular needs to create the html before the carousel is render, I don't know... :(
This is the outcome:
https://db.tt/noc0VgGU
Router:
(function() {
'use strict';
angular
.module('mgxApp.landing')
.config(configFunction);
configFunction.$inject = ['$routeProvider'];
function configFunction($routeProvider) {
$routeProvider.when('/', {
templateUrl: 'app/landing/landing.html',
controller: 'homeCtrl',
controllerAs: 'hC'
});
}
})();
Controller:
(function() {
'use strict';
angular
.module('mgxApp.landing')
.controller('homeCtrl', homeCtrl);
homeCtrl.$inject = ['modalFactory', 'channelFactory'];
function homeCtrl(modalFactory, channelFactory) {
var hC = this;
hC.openAuthModal = modalFactory.openAuthModal;
hC.activeChannels;
channelFactory.allActiveChannels().then(function(activechannels){
console.log(activechannels);
hC.activeChannels = activechannels;
});
hC.w = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
hC.breakpoints = [
{
breakpoint: 768,
settings: {
slidesToShow: 2,
slidesToScroll: 2
}
}, {
breakpoint: 480,
settings: {
slidesToShow: 1,
slidesToScroll: 1
}
}
];
}
})();
HTML VIEW:
// NOT WORKING
<slick class="slider single-item" responsive="hC.breakpoints" slides-to-show=3 slides-to-scroll=3>
<div ng-repeat="channel in hC.activeChannels">
{{channel.get("username")}}
</div>
</slick>
// Working fine
<slick class="slider single-item" current-index="index" responsive="hC.breakpoints" slides-to-show=3 slides-to-scroll=3>
<div ng-repeat="i in hC.w">
<h3>{{ i }}</h3>
</div>
</slick>
Factory and Promise:
(function () {
'use strict';
angular
.module('mgxApp.channel')
.factory('channelFactory', channelFactory);
channelFactory.$inject = ['$rootScope', '$q'];
function channelFactory($rootScope, $q) {
var service = {
allActiveChannels : allActiveChannels
};
return service;
function allActiveChannels() {
var deferral = $q.defer();
var User = Parse.Object.extend("_User");
var query = new Parse.Query(User).limit(10);
query.find({
success: function(users) {
console.log(users);
/*for (var i = 0; i < users.length; i++) {
console.log(users[i].get("username"));
}*/
deferral.resolve(users);
},
error: function(error) {
console.warn(error);
deferral.reject();
}
});
return deferral.promise;
}
}
})();
My working code
<div tmob-slick-slider sliderData="" dynamicDataChange="true" class="utilHeightImg marqueeContainer">
<slick id="productCarousel" class="slider" settings="vm.slickAccessoriesConfig" data-slick='{"autoplay ": true, "autoplaySpeed": 4000}'>
<!-- repeat='image' -->
<div ng-repeat="slideContent in vm.slides track by $index" >
<div bind-unsafe-html="slideContent" ></div>
</div>
<!-- end repeat -->
</slick>
</div>
you have to write a directive to reinitialize the slider
angular.module('tmobileApp')
.directive('tmobSlickSlider',['$compile',function ($compile) {
return {
restrict: 'EA',
scope: true,
link: function (scope, element, attrs) {
scope.$on('MarqueesliderDataChangeEvent', function (event, data) {
$compile(element.contents())(scope);
});
}
};
}]);
Write this in your controller
hc.selectView=false; // make this hc.selectView=true when your promise get resolve
$scope.$watch('hc.selectView', function(newValue, oldValue) {
$scope.$broadcast('MarqueesliderDataChangeEvent');
});
I ended up using this solution:
Angular-slick ng-repeat $http get
I'd suggest you to use ng-if on slick element. That will only load slick directive only when data is present just by checking length of data.
Markup
<slick ng-if="ctrl.products.length">
<div ng-repeat="product in ctrl.products">
<img ng-src="{{product.image}}" alt="{{product.title}}"/>
</div>
</slick>
this is my simple test code for lazy load
http://codepen.io/kevkev/pen/bVVGdE
it works so far .. but the thing is that hidden images in an onclick function for buttons etc. doesnt work!
(watch through my code and scroll to end and push the button)
you can see in the network feedback that it already had load the images.
i could figure out that the problem is "display:none"
.pop {
display:none;
z-index:99;
position:absolute;
width:100%;
height:auto;
background:inherit;
}
Because display: none; elements are unknown in position. And the lazyloader doesn't know, when and if you change this. Therefore it decides to eager load it. If you want a lazyloader that automatically detects this use https://github.com/aFarkas/lazysizes/.
As alternative I would recommend justlazy, because it's more lightweight and don't uses jQuery.
1. Define placeholder (similar to that what you have done):
<span data-src="path/to/image" data-alt="alt" data-title="title"
class="placeholder">
</span>
2. Initialize lazy loading after your button click:
$(document).ready(function () {
$("#art").click(function () {
$("#art_pop").fadeIn(300);
Justlazy.registerLazyLoadByClass("placeholder", {
// image will be loaded if it is 300 pixels
// below the lower display border
threshold: 300
});
});
// other code ..
});
thanks guys! but I also got a working solution on this:
http://codepen.io/kevkev/full/meebpQ/
$(document).ready(function () {
$("#art").click(function () {
$("#art_pop").fadeIn(300);
});
$(".pop > span, .pop").click(function () {
$(".pop").fadeOut(600);
});
});
;(function($) {
$.fn.unveil = function(threshold, callback) {
var $w = $(window),
th = threshold || 0,
retina = window.devicePixelRatio > 1,
attrib = retina? "data-src-retina" : "data-src",
images = this,
loaded;
this.one("unveil", function() {
var source = this.getAttribute(attrib);
source = source || this.getAttribute("data-src");
if (source) {
this.setAttribute("src", source);
if (typeof callback === "function") callback.call(this);
}
});
function unveil() {
var inview = images.filter(function() {
var $e = $(this);
if ($e.is(":hidden")) return;
var wt = $w.scrollTop(),
wb = wt + $w.height(),
et = $e.offset().top,
eb = et + $e.height();
return eb >= wt - th && et <= wb + th;
});
loaded = inview.trigger("unveil");
images = images.not(loaded);
}
$w.on("scroll.unveil resize.unveil lookup.unveil", unveil);
unveil();
return this;
};
})(window.jQuery || window.Zepto);
/* OWN JAVASCRIPT */
$(document).ready(function() {
$("img").unveil(200, function() {
$(this).load(function() {
this.style.opacity = 1;
});
});
});
I want to access and view public Youtube videos (simple read only) from any Youtube channel without resorting to Oauth, just with plain API key. I haven't found a decent layman example on how to go about with API v3 ;-(
I have this to juggle with which I cannot get to work. Basically, a Select menu contains options whose values are existing channel IDs. When an option containing a channel ID is selected, it should trigger requestUserUploadsPlaylistId(). Then, when NEXTbutton or PREVIOUSbutton are activated, function requestVideoPlaylist() would kick in. Is there a better way to do this? I get the following error messages in Firebug:
TypeError: response.result is undefined (When I choose an option from SELECTmenu).
TypeError: response.result is undefined (After I click on buttons).
Here is what I am struggling with (am new to API v3 and kinda used to API v2 (sigh)):
<HTML is here>
script>
$('#NEXTbutton').prop('disabled', true).addClass('disabled');
</script>
<script type="text/javascript" src="https://apis.google.com
/js/client.js?onload=onJSClientLoad"></script>
<script>
var dd, playlistId, nextPageToken, prevPageToken;
function onJSClientLoad() {
gapi.client.setApiKey('YOUR-API-KEY');
gapi.client.load('youtube', 'v3', function(){
$('#NEXTbutton').prop('disabled', false).removeClass('disabled');
});
}
// Calling the following function via selected option value of select menu
// I am using "mine: false," since it's an unauthenticated request ??
function requestUserUploadsPlaylistId() {
var dd = $("#SELECTmenu option:selected").val();
var request = gapi.client.youtube.channels.list({
mine: false, // is this legit?
channelId: dd, // Variable is preset chosen value of SELECTmenu options
part: 'contentDetails,id'
});
request.execute(function(response) {
playlistId = response.result.items[0].contentDetails.relatedPlaylists.uploads;
channelId = response.result.items[0].id;
});
}
function requestVideoPlaylist(playlistId, pageToken) {
var requestOptions = {
playlistId: playlistId,
part: 'snippet,id',
maxResults: 5
};
if (pageToken) {
requestOptions.pageToken = pageToken;
}
var request = gapi.client.youtube.playlistItems.list(requestOptions);
request.execute(function(response) {
// Only show the page buttons if there's a next or previous page.
nextPageToken = response.result.nextPageToken;
var nextVis = nextPageToken ? 'visible' : 'hidden';
$('#NEXTbutton').css('visibility', nextVis);
prevPageToken = response.result.prevPageToken
var prevVis = prevPageToken ? 'visible' : 'hidden';
$('#PREVIOUSbutton').css('visibility', prevVis);
var playlistItems = response.result.items;
if (playlistItems) {
$.each(playlistItems, function(index, item) {
displayResult(item.snippet);
});
} else {
$('#CONTAINER').html('Sorry, no uploaded videos available');
}
});
}
function displayResult(videoSnippet) {
for(var i=0;i<response.items.length;i++) {
var channelTitle = response.items[i].snippet.channelTitle
var videoTitle = response.items[i].snippet.title;
var Thumbnail = response.items[i].snippet.thumbnails.medium.url;
var results = '<li><div class="video-result"><img src="'+Thumbnail+'" /></div>
<div class="chantitle">'+channelTitle+'</div>
<div class="vidtitle">'+videoTitle+'</div></li>';
$('#CONTAINER').append(results);
}
}
function nextPage() {
requestVideoPlaylist(playlistId, nextPageToken);
}
function previousPage() {
requestVideoPlaylist(playlistId, prevPageToken);
}
$('#NEXTbutton').on('click', function() { // Display next 5 results
nextPage();
});
$('#PREVIOUSbutton').on('click', function() { // Display previous 5 results
previousPage();
});
$("#SELECTmenu").on("change", function() {
$('#CONTAINER').empty();
if ($("#SELECTmenu option:selected").val().length === 24) { //Channel ID length
requestUserUploadsPlaylistId();
} else {
return false;
}
});
I'm surely missing something here, any pointers will be greatly appreciated.
FINAL UPDATE
A few updates later and I've finally answered my question after playing with the awesome Google APIs Explorer tool. Here is a sample working code allowing access to Youtube channel video-related data from a Select menu for read-only without using OAUTH, just an API key. The Select menu, based on a selected option's value (which contains a channel id), posts a video thumbnail, the thumbnail's channel origin; and the video's title. Should be easy to make the thumbnail clickable so as to load video in iframe embed or redirect to Youtube page. Enjoy!
// Change values and titles accordingly
<select id="SELECTmenu">
<option value="selchan">Select channel ...</option>
<option value="-YOUR-24digit-ChannelID-">Put-channel-title-here</option>
<option value="-YOUR-24digit-ChannelID-">Put-channel-title-here</option>
</select>
<button id="NEXTbutton">NEXT</button>
<button id="PREVIOUSbutton">PREV</button>
<ol id="CONTAINER"></ol> // Loads video data response
<script type="text/javascript"
src="https://apis.google.com/js/client.js?onload=onJSClientLoad">
</script>
var playlistId, nextPageToken, prevPageToken;
function onJSClientLoad() {
gapi.client.setApiKey('INSERT-YOUR-API-KEY'); // Insert your API key
gapi.client.load('youtube', 'v3', function(){
//Add function here if some action required immediately after the API loads
});
}
function requestUserUploadsPlaylistId(pageToken) {
// https://developers.google.com/youtube/v3/docs/channels/list
var selchan = $("#SELECTmenu option:selected").val();
var request = gapi.client.youtube.channels.list({
id: selchan,
part: 'snippet,contentDetails',
filter: 'uploads'
});
request.execute(function(response) {
playlistId = response.result.items[0].contentDetails.relatedPlaylists.uploads;
channelId = response.result.items[0].id;
requestVideoPlaylist(playlistId, pageToken);
});
}
function requestVideoPlaylist(playlistId, pageToken) {
$('#CONTAINER').empty();
var requestOptions = {
playlistId: playlistId,
part: 'snippet,id',
maxResults: 5 // can be changed
};
if (pageToken) {
requestOptions.pageToken = pageToken;
}
var request = gapi.client.youtube.playlistItems.list(requestOptions);
request.execute(function(response) {
// Only show the page buttons if there's a next or previous page.
nextPageToken = response.result.nextPageToken;
var nextVis = nextPageToken ? 'visible' : 'hidden';
$('#NEXTbutton').css('visibility', nextVis);
prevPageToken = response.result.prevPageToken
var prevVis = prevPageToken ? 'visible' : 'hidden';
$('#PREVIOUSbutton').css('visibility', prevVis);
var playlistItems = response.result.items;
if (playlistItems) {
displayResult(playlistItems);
} else {
$('#CONTAINER').html('Sorry, no uploaded videos.');
}
});
}
function displayResult(playlistItems) {
for(var i=0;i<playlistItems.length;i++) {
var channelTitle = playlistItems[i].snippet.channelTitle
var videoTitle = playlistItems[i].snippet.title;
var videoThumbnail = playlistItems[i].snippet.thumbnails.medium.url;
var results = '<li>
<div>'+channelTitle+'</div>
<div><img src="'+videoThumbnail+'" /></div>
<div>'+videoTitle+'</div>
</li>';
$('#CONTAINER').append(results);
}
}
function nextPage() {
$('#CONTAINER').empty(); // This needed here
requestVideoPlaylist(playlistId, nextPageToken);
}
function previousPage() {
$('#CONTAINER').empty(); // This needed here
requestVideoPlaylist(playlistId, prevPageToken);
}
$('#NEXTbutton').on('click', function() { // Display next maxResults
nextPage();
});
$('#PREVIOUSbutton').on('click', function() { // Display previous maxResults
previousPage();
});
// Using as filtering example Select option values which contain channel
// ID length of 24 alphanumerics/symbols to trigger functions just in case
// there are other option values in the menu that do not refer to channel IDs.
$("#SELECTmenu").on("change", function() {
$('#CONTAINER').empty();
if ($("#SELECTmenu option:selected").val().length === 24) {
requestUserUploadsPlaylistId();
return false;
} else {
return false;
}
});
NOTE:
Remember, code sample above is built based on what API v3 provided at the time of this posting.
TIP: It's better to make sure that the buttons be disabled during API call and re-enabled after API has posted the expected results. If you press those buttons while processing, you may get compounded and/or unexpected results. ~ Koolness
I got the following:
http://jsfiddle.net/GsL8Z/
I want to toggle the size of an image. After each toggle, I want to replace the scaled image with its instance in right size.
This actually works pretty well, but only the first time. After the third click, the wrong class gets allocated.
Any help would be greatly appreciated!
HTML
<div id="projekt_1" class="projekt">
<ul class="bilder">
<li><img class="imgKlein" src="images/mainworks_th.jpg" alt="Mainworks"/></li>
</ul>
</div>
CSS
.imgGross{
height: 450px;
}
.imgKlein{
height: 215px;
}
JS
var status = true,
obj = $('.projekt'),
projekte = $.makeArray(obj),
obj = $('.bilder'),
projekte_li = $.makeArray(obj),
obj = $('.projekt li img'),
projekte_li_img = $.makeArray(obj);
var images = new Array (2);
images[0] = $('<img class="imgKlein"/>').attr({src: 'images/mainworks_th.jpg'});
images[1] = $('<img class="imgGross"/>').attr({src:'images/mainworks_pre.jpg'});
$('#projekt_1').click(function() {
if (status == true) {
$("img", this).switchClass( "imgKlein", "imgGross", 1000, "easeInOutQuad" );
setTimeout(function(){
$(projekte_li[0]).html(images[1]);
}, 2000);
status = false;
}
else {
$("img", this).switchClass( "imgGross", "imgKlein", 1000, "easeInOutQuad" );
setTimeout(function(){
$(projekte_li[0]).html(images[0]);
}, 2000);
status = true;
}
return false;
});
Somehow the switchClass is having problems with you replacing the whole html for the img. As a matter of fact you can just change the src.
Also, you are better off using .toggle() in jQuery to handle things changing back and forward on each click.
By the way, also the setTimeout can give problems. .switchClass() has a complete handler that runs after the animation is complete and you should use that.
So, the solution could be:
$('#projekt_1').toggle(
function(e) {
$("img", this).switchClass("imgKlein", "imgGross", 1000, "easeInOutQuad",
function() {
$(this).attr({ src: 'images/mainworks_pre.jpg', alt: "Mainworks_pre" });
});
return false;
},
function (e) {
$("img", this).switchClass("imgGross", "imgKlein", 1000, "easeInOutQuad",
function(){
$(this).attr({ src: 'images/mainworks_th.jpg', alt: "Mainworks_TH" });
});
return false;
}
);
Fiddle: http://jsfiddle.net/GsL8Z/2/
i testet something with the example from jquery-ui and it works
the code is little bit shorter than yours:
$(function() {
$( "#projekt_1" ).click(function(){
$(".imgKlein").switchClass( "imgKlein", "imgGross", 1000, "easeInOutQuad", function()
{
$("img").attr("src", "http://www.spielwiki.de/images/e/e9/Kleines_M%C3%A4dchen%2C_zu_gro%C3%9Fer_Luftballon.png");
});
$(".imgGross").switchClass( "imgGross", "imgKlein", 1000, "easeInOutQuad", function()
{
$("img").attr("src", "http://images.all-free-download.com/images/graphiclarge/small_house_329.jpg");
});
return false;
});
});
the link to the example: http://jsfiddle.net/DWrC6/24/
I have a piece of code for showing a picture that slides up from a div when the mouse enters the div, the code works exactly how i want except it bugs when the mouse hovers in and out too quickly and the animation doesn't have time to complete, I've already changed from mouseover and mouseout, to mouseenter and mouseleave and this hasn't seemed to help, any suggestions would be great
<script type="text/javascript">
document.observe("dom:loaded", function() {
var effectInExecution=null;
$('mid_about_us').observe('mouseenter', function() {
if(effectInExecution) effectInExecution.cancel();
effectInExecution=new Effect.SlideDown('about_us_mo',{style:'height:140px;', duration: 1.0 });
});
$('mid_about_us').observe('mouseleave', function() {
if(effectInExecution) effectInExecution.cancel();
effectInExecution=new Effect.SlideUp('about_us_mo',{style:'height:0px;', duration: 1.0 });
});
});
I wrote a Prototype class a while back to solve this problem, the issue can be fixed by supplying a scope parameter to the effect options. anyway here is the class i wrote:
var DivSlider = Class.create();
Object.extend(DivSlider, {
toggle: function(selector, element, options) {
element = $(element);
this.options = Object.extend({
duration: 0.5,
fps: 35,
scope: 'DivSlider',
forceOpen: false
}, options || {});
var toggle = element.visible();
if (toggle && this.options.forceOpen) {
//already open, leave.. still call callback
(this.options.after || Prototype.emptyFunction)
.bind(this, element)();
return;
}
var effects = new Array();
if (toggle) {
effects.push(new Effect.SlideUp(element, {
sync: true
}));
} else {
$$(selector).each(function(el) {
if ((element !== el) && el.visible()) {
effects.push(new Effect.SlideUp(el, {
sync: true
}));
}
});
effects.push(new Effect.SlideDown(element, {
sync: true
}));
}
new Effect.Parallel(effects, {
duration: this.options.duration,
fps: this.options.fps,
queue: {
position: 'end',
scope: this.options.scope
},
beforeStart: function() {
(this.options.before || Prototype.emptyFunction)
.bind(this, element)();
}.bind(this),
afterFinish: function() {
(this.options.after || Prototype.emptyFunction)
.bind(this, element)();
}.bind(this)
});
}
});
and to use it in your case you would simply use:
DivSlider.toggle('div.your_class', your_id);
in your enter/leave code, it can handle multiple div's of the same class also, allowing only one div per class to be open at any single time. If this does not fit your needs you can easily deconstruct the class to get the code you actually need.