From f88a06aefa0a1327cf7e9e28de66dbcf24c8aa17 Mon Sep 17 00:00:00 2001 From: Isaac Bythewood Date: Fri, 27 Apr 2012 19:59:55 +0000 Subject: [PATCH] Started the creation of an API and implemented infinite scroll. Closes issue #7. --- pinry/api/__init__.py | 0 pinry/api/urls.py | 6 + pinry/api/views.py | 21 ++++ pinry/core/static/core/css/pinry.css | 6 + pinry/core/static/core/img/loader.gif | Bin 0 -> 2608 bytes pinry/core/static/core/js/pinry.js | 84 ++++++++++++- pinry/core/templates/core/base.html | 2 + pinry/pins/templates/pins/recent_pins.html | 18 +-- pinry/pins/views.py | 5 +- pinry/settings/__init__.py | 1 + pinry/urls.py | 1 + .../imagesloaded/2.0.1/jquery.imagesloaded.js | 112 ++++++++++++++++++ .../2.0.1/jquery.imagesloaded.min.js | 2 + 13 files changed, 237 insertions(+), 21 deletions(-) create mode 100644 pinry/api/__init__.py create mode 100644 pinry/api/urls.py create mode 100644 pinry/api/views.py create mode 100644 pinry/core/static/core/img/loader.gif create mode 100644 pinry/vendor/static/vendor/imagesloaded/2.0.1/jquery.imagesloaded.js create mode 100644 pinry/vendor/static/vendor/imagesloaded/2.0.1/jquery.imagesloaded.min.js diff --git a/pinry/api/__init__.py b/pinry/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pinry/api/urls.py b/pinry/api/urls.py new file mode 100644 index 0000000..65c6980 --- /dev/null +++ b/pinry/api/urls.py @@ -0,0 +1,6 @@ +from django.conf.urls import patterns, include, url + + +urlpatterns = patterns('', + url(r'^pins/recent/(?P\d*)/$', 'pinry.api.views.pins_recent', name='pins-recent'), +) diff --git a/pinry/api/views.py b/pinry/api/views.py new file mode 100644 index 0000000..3220d10 --- /dev/null +++ b/pinry/api/views.py @@ -0,0 +1,21 @@ +from django.utils import simplejson +from django.http import HttpResponse + +from pinry.pins.models import Pin + + +def pins_recent(request, page=1): + start_pin = abs(int(page) - 1) * 25 + end_pin = int(page) * 25 + + pins = Pin.objects.order_by('-id')[start_pin:end_pin] + recent_pins = [] + for pin in pins: + recent_pins.append({ + 'id': pin.id, + 'thumbnail': pin.image.url_200x1000, + 'original': pin.image.url, + 'description': pin.description, + }) + + return HttpResponse(simplejson.dumps(recent_pins), mimetype="application/json") diff --git a/pinry/core/static/core/css/pinry.css b/pinry/core/static/core/css/pinry.css index 6d9b494..81d3f9b 100644 --- a/pinry/core/static/core/css/pinry.css +++ b/pinry/core/static/core/css/pinry.css @@ -29,6 +29,11 @@ body { text-decoration: underline; } +#loader { + margin-top: 70px; + text-align: center; +} + #pins { top: 70px; position: absolute; @@ -41,6 +46,7 @@ body { float: left; border: 1px solid #ccc; padding: 20px; + display: none; } .pin img { diff --git a/pinry/core/static/core/img/loader.gif b/pinry/core/static/core/img/loader.gif new file mode 100644 index 0000000000000000000000000000000000000000..c69e937232b24ea30f01c68bbd2ebc798dcecfcb GIT binary patch literal 2608 zcmdVcdr(tX9tZGC9yiG~=H_*Q-0%n(kWqP*D#hw{AQu8;1%gl-Hrf&{2?48KX;hHy z3Ze*zEz4t3XdUFyLbNPUYlA`|B}P=N1fqtL1*}S;87#|-W9v<#G;ul(e%d3)N(^9c$d2Dz{7}?ErjNd;{EMKkCsk21~b9Gvg zDo<7L=3Z5HNbVlZUcm1eg#o#CZCJU`3IYHwM->zCd?uYrF3vKFeM}v?f+%s?E>ly|3W25ry9#NNbTx-}0ON58dTrs^ix{_1O0Wh~SVSBlH)Ajn zPn^Gbjz}PCtN@#keR&hK&Dhl-b$kZ8^S)x#dh0{7X=X%CCJk7P1PSO>T&S8I4{#Lg zb5#)o=;!ZP*1nM{cI4@(x7o27*SA()NHmrn67aN@Pmi~(i_SnrjYnwh36aG%!@i0d zqbvfa44f|?OG4ntP|nbjhEl1)Yp6ZN@yjy zy4==QmLy%t;ps3R?~f2KfTTI|2?q8dFd6^z5GF+Xa&Y)sjG)hxit80pPcOP zJ z*LW{SyGHD%hUotV+W%I}fBLAIx!8|7#}$;clKQ+{&FjDqGQ2ZNx(lYM3*%~}ILnao zM`aui55~ZFJlu^!5rdA9Q_7H68H_;##u{x(Yn-vSfIRCb^Nqsg zGRS!Egm>h+o<}LeV4&CLReo9FrDjDvs}8?JwC)#Qs|ie=r?~xUh)&*d`Fx>FG}%X# zNdtDHBKhLPC0wpooFDAQKL%*6T|ULH$=wX!NhcasgD3d;-d$I6yRK3yN+E~C1335_iLOt+*9uvSZ`>*KA}vm}08wRq=>5l|t*Na&jR z-C1&C`nkEk#sB|@yyt-#fXngP04My zm7u$Q%EJbHp`>~`5W&L{W!6`y&}LMS;jfUpgO~7TLVMRZ9IC)IZp0A${`yp0{&wco z#1nx@XMkhqeK%7?RE7JdLr1^nwFfaJ0Q&Lv?WNJ%9}VSJsNY2+UYs2%EU0J~ayFXv zi*?7KCXQHkD)O6!0Q%4N+HTODHxJ{kQSuQX$l-rSwkwh(zMkdfzxyGwl@yHC)C4p< z&n2%8#M?)Q@mgHL1ot8`SFdSEj9ye|jHy+U8#@HoUExG=@AVkRAe_qYm4EpzK6L*& zh`)26?V#f4#_h^P9G^%>h2-H3)$QP zQovu6J9qDvsxqweDdNNa!Lb?L4_UF{tLX_nN7r0U_vF14YKcGR-*Gl} zx3oG)bzf|65dBxD-;2ZCp??K;+TuQ9onnK?==5hzbkb^r_g>z4#D8mcv8(+XdoszA zCx-qhdgxMNMotj}SiL_6V(tLcsK7(M(r(%u<}QrVfOvyK6_;~NOTlPGfX@M7S5YQF z&*$(ylJMHJt^_aQeu{C6NaTE$G3HNN@_SnN8YcaKn%`)F@~L1x+ah7-gEJPpc6w%3 zyX}r+Qk$4RHZzfH){e~F*qJ{d*L8a6n4;U?+{de0-t)mal#TVxe)3F}^UBh+zd T)6_**#cgp_+?JL9(ew3BlNF>u literal 0 HcmV?d00001 diff --git a/pinry/core/static/core/js/pinry.js b/pinry/core/static/core/js/pinry.js index f671bb8..b7037bd 100644 --- a/pinry/core/static/core/js/pinry.js +++ b/pinry/core/static/core/js/pinry.js @@ -1,10 +1,84 @@ -$(window).bind('load', function() { - $('.pin').wookmark({ - offset: 3, - itemWidth: 242, - autoResize: true +/** + * Based on Wookmark's endless scroll. + */ +$(window).ready(function () { + var apiURL = '/api/pins/recent/' + var page = 1; + var handler = null; + var isLoading = false; + + /** + * When scrolled all the way to the bottom, add more tiles. + */ + function onScroll(event) { + if(!isLoading) { + var closeToBottom = ($(window).scrollTop() + $(window).height() > $(document).height() - 100); + if(closeToBottom) loadData(); + } + }; + + function applyLayout() { + $('#pins').imagesLoaded(function() { + // Clear our previous layout handler. + if(handler) handler.wookmarkClear(); + + // Create a new layout handler. + handler = $('#pins .pin'); + handler.wookmark({ + autoResize: true, + offset: 3, + itemWidth: 242 + }); + }); + }; + + /** + * Loads data from the API. + */ + function loadData() { + isLoading = true; + $('#loader').show(); + + $.ajax({ + url: apiURL+page, + success: onLoadData + }); + }; + + /** + * Receives data from the API, creates HTML for images and updates the layout + */ + function onLoadData(data) { + isLoading = false; + $('#loader').hide(); + + page++; + + var html = ''; + var i=0, length=data.length, image; + for(; i'; + html += ''; + html += ''; + html += '

'+image.description+'

'; + html += ''; + } + + $('#pins').append(html); + + applyLayout(); + }; + + $(document).ready(new function() { + $(document).bind('scroll', onScroll); + loadData(); }); + /** + * On clicking an image show fancybox original. + */ $('.fancybox').fancybox({ openEffect: 'none', closeEffect: 'none' diff --git a/pinry/core/templates/core/base.html b/pinry/core/templates/core/base.html index 7e8a442..841be8a 100644 --- a/pinry/core/templates/core/base.html +++ b/pinry/core/templates/core/base.html @@ -39,11 +39,13 @@ + {% else %} + {% endif %} diff --git a/pinry/pins/templates/pins/recent_pins.html b/pinry/pins/templates/pins/recent_pins.html index e93ea31..f79ca90 100644 --- a/pinry/pins/templates/pins/recent_pins.html +++ b/pinry/pins/templates/pins/recent_pins.html @@ -3,18 +3,12 @@ {% block title %}Recent Pins{% endblock %} {% block yield %} -
- {% for pin in pins %} -
- {% if pin.image %} - - {{ pin.description }} - - {% endif %} - {% if pin.description %} -

Description: {{ pin.description }}

- {% endif %} +
+
+
+
+ Loader
- {% endfor %} +
{% endblock %} diff --git a/pinry/pins/views.py b/pinry/pins/views.py index ec1a17a..41a7689 100644 --- a/pinry/pins/views.py +++ b/pinry/pins/views.py @@ -7,10 +7,7 @@ from .forms import PinForm def recent_pins(request): - context = { - 'pins': Pin.objects.order_by('-id')[:50], - } - return TemplateResponse(request, 'pins/recent_pins.html', context) + return TemplateResponse(request, 'pins/recent_pins.html', None) def new_pin(request): diff --git a/pinry/settings/__init__.py b/pinry/settings/__init__.py index 534956b..4bf8925 100644 --- a/pinry/settings/__init__.py +++ b/pinry/settings/__init__.py @@ -53,4 +53,5 @@ INSTALLED_APPS = ( 'pinry.vendor', 'pinry.core', 'pinry.pins', + 'pinry.api', ) diff --git a/pinry/urls.py b/pinry/urls.py index eee2e64..02d2c8e 100644 --- a/pinry/urls.py +++ b/pinry/urls.py @@ -6,4 +6,5 @@ from django.conf import settings urlpatterns = patterns('', url(r'', include('pinry.core.urls', namespace='core')), url(r'^pins/', include('pinry.pins.urls', namespace='pins')), + url(r'^api/', include('pinry.api.urls', namespace='api')), ) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/pinry/vendor/static/vendor/imagesloaded/2.0.1/jquery.imagesloaded.js b/pinry/vendor/static/vendor/imagesloaded/2.0.1/jquery.imagesloaded.js new file mode 100644 index 0000000..a488974 --- /dev/null +++ b/pinry/vendor/static/vendor/imagesloaded/2.0.1/jquery.imagesloaded.js @@ -0,0 +1,112 @@ +/*! + * jQuery imagesLoaded plugin v2.0.1 + * http://github.com/desandro/imagesloaded + * + * MIT License. by Paul Irish et al. + */ + +/*jshint curly: true, eqeqeq: true, noempty: true, strict: true, undef: true, browser: true */ +/*global jQuery: false */ + +;(function($, undefined) { +'use strict'; + +// blank image data-uri bypasses webkit log warning (thx doug jones) +var BLANK = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='; + +$.fn.imagesLoaded = function( callback ) { + var $this = this, + deferred = $.isFunction($.Deferred) ? $.Deferred() : 0, + hasNotify = $.isFunction(deferred.notify), + $images = $this.find('img').add( $this.filter('img') ), + loaded = [], + proper = [], + broken = []; + + function doneLoading() { + var $proper = $(proper), + $broken = $(broken); + + if ( deferred ) { + if ( broken.length ) { + deferred.reject( $images, $proper, $broken ); + } else { + deferred.resolve( $images ); + } + } + + if ( $.isFunction( callback ) ) { + callback.call( $this, $images, $proper, $broken ); + } + } + + function imgLoaded( img, isBroken ) { + // don't proceed if BLANK image, or image is already loaded + if ( img.src === BLANK || $.inArray( img, loaded ) !== -1 ) { + return; + } + + // store element in loaded images array + loaded.push( img ); + + // keep track of broken and properly loaded images + if ( isBroken ) { + broken.push( img ); + } else { + proper.push( img ); + } + + // cache image and its state for future calls + $.data( img, 'imagesLoaded', { isBroken: isBroken, src: img.src } ); + + // trigger deferred progress method if present + if ( hasNotify ) { + deferred.notifyWith( $(img), [ isBroken, $images, $(proper), $(broken) ] ); + } + + // call doneLoading and clean listeners if all images are loaded + if ( $images.length === loaded.length ){ + setTimeout( doneLoading ); + $images.unbind( '.imagesLoaded' ); + } + } + + // if no images, trigger immediately + if ( !$images.length ) { + doneLoading(); + } else { + $images.bind( 'load.imagesLoaded error.imagesLoaded', function( event ){ + // trigger imgLoaded + imgLoaded( event.target, event.type === 'error' ); + }).each( function( i, el ) { + var src = el.src; + + // find out if this image has been already checked for status + // if it was, and src has not changed, call imgLoaded on it + var cached = $.data( el, 'imagesLoaded' ); + if ( cached && cached.src === src ) { + imgLoaded( el, cached.isBroken ); + return; + } + + // if complete is true and browser supports natural sizes, try + // to check for image status manually + if ( el.complete && el.naturalWidth !== undefined ) { + imgLoaded( el, el.naturalWidth === 0 || el.naturalHeight === 0 ); + return; + } + + // cached images don't fire load sometimes, so we reset src, but only when + // dealing with IE, or image is complete (loaded) and failed manual check + // webkit hack from http://groups.google.com/group/jquery-dev/browse_thread/thread/eee6ab7b2da50e1f + if ( el.readyState || el.complete ) { + el.src = BLANK; + el.src = src; + } + }); + } + + return deferred ? deferred.promise( $this ) : $this; +}; + +})(jQuery); \ No newline at end of file diff --git a/pinry/vendor/static/vendor/imagesloaded/2.0.1/jquery.imagesloaded.min.js b/pinry/vendor/static/vendor/imagesloaded/2.0.1/jquery.imagesloaded.min.js new file mode 100644 index 0000000..a5fd97b --- /dev/null +++ b/pinry/vendor/static/vendor/imagesloaded/2.0.1/jquery.imagesloaded.min.js @@ -0,0 +1,2 @@ +(function(c,n){var k="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";c.fn.imagesLoaded=function(l){function m(){var b=c(h),a=c(g);d&&(g.length?d.reject(e,b,a):d.resolve(e));c.isFunction(l)&&l.call(f,e,b,a)}function i(b,a){b.src===k||-1!==c.inArray(b,j)||(j.push(b),a?g.push(b):h.push(b),c.data(b,"imagesLoaded",{isBroken:a,src:b.src}),o&&d.notifyWith(c(b),[a,e,c(h),c(g)]),e.length===j.length&&(setTimeout(m),e.unbind(".imagesLoaded")))}var f=this,d=c.isFunction(c.Deferred)?c.Deferred(): +0,o=c.isFunction(d.notify),e=f.find("img").add(f.filter("img")),j=[],h=[],g=[];e.length?e.bind("load.imagesLoaded error.imagesLoaded",function(b){i(b.target,"error"===b.type)}).each(function(b,a){var e=a.src,d=c.data(a,"imagesLoaded");if(d&&d.src===e)i(a,d.isBroken);else if(a.complete&&a.naturalWidth!==n)i(a,0===a.naturalWidth||0===a.naturalHeight);else if(a.readyState||a.complete)a.src=k,a.src=e}):m();return d?d.promise(f):f}})(jQuery);