Rewrite the display of recent pins without the use of third-party js libs

This commit is contained in:
Isaac Bythewood
2013-02-20 07:59:45 +00:00
parent e467a83683
commit 8e8bed0efa
10 changed files with 2368 additions and 139 deletions

View File

@@ -1,15 +1,23 @@
from tastypie.resources import ModelResource from tastypie.resources import ModelResource
from tastypie import fields from tastypie import fields
from tastypie.authentication import BasicAuthentication
from tastypie.authorization import DjangoAuthorization
from django.contrib.auth.models import User from django.contrib.auth.models import User
from pinry.pins.models import Pin from pinry.pins.models import Pin
class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
resource_name = 'user'
excludes = ['email', 'password', 'is_superuser', 'first_name',
'last_name', 'is_active', 'is_staff', 'last_login', 'date_joined']
include_resource_uri = False
class PinResource(ModelResource): # pylint: disable-msg=R0904 class PinResource(ModelResource): # pylint: disable-msg=R0904
tags = fields.ListField() tags = fields.ListField()
submitter = fields.ForeignKey(UserResource, 'submitter', full=True)
class Meta: class Meta:
queryset = Pin.objects.all() queryset = Pin.objects.all()
@@ -17,6 +25,7 @@ class PinResource(ModelResource): # pylint: disable-msg=R0904
include_resource_uri = False include_resource_uri = False
filtering = { filtering = {
'published': ['gt'], 'published': ['gt'],
'submitter': ['exact']
} }
def build_filters(self, filters=None): def build_filters(self, filters=None):
@@ -37,13 +46,3 @@ class PinResource(ModelResource): # pylint: disable-msg=R0904
tags = bundle.data.get('tags', []) tags = bundle.data.get('tags', [])
bundle.obj.tags.set(*tags) bundle.obj.tags.set(*tags)
return super(PinResource, self).save_m2m(bundle) return super(PinResource, self).save_m2m(bundle)
class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
resource_name = 'auth/user'
excludes = ['email', 'password', 'is_superuser']
# Add it here.
authentication = BasicAuthentication()
authorization = DjangoAuthorization()

View File

@@ -1,14 +1,16 @@
from django.conf.urls import patterns, include, url from django.conf.urls import patterns, include, url
from tastypie.api import Api
from .api import PinResource from .api import PinResource
from .api import UserResource from .api import UserResource
pin_resource = PinResource() v1_api = Api(api_name='v1')
user_resource = UserResource() v1_api.register(PinResource())
v1_api.register(UserResource())
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'', include(pin_resource.urls)), url(r'^api/', include(v1_api.urls)),
url(r'', include(user_resource.urls)),
) )

View File

@@ -70,18 +70,14 @@ body {
#pins { #pins {
top: 70px; top: 70px;
position: absolute;
background: #eee;
z-index: 100;
} }
.pin { .pin {
background: #fff; background: #fff;
width: 200px; width: 200px;
float: left;
border: 1px solid #ccc; border: 1px solid #ccc;
padding: 20px; padding: 20px;
display: none; position: absolute;
} }
.pin img { .pin img {

View File

@@ -1,121 +1,85 @@
/** $(window).load(function() {
* Based on Wookmark's endless scroll. function tileLayout() {
*/ // Config
var apiURL = '/api/pin/?format=json&offset=' var blockMargin = 20;
var page = 0; var blockWidth = 240;
var handler = null; // End Config
var globalTag = null;
var isLoading = false;
/** var blockContainer = $('#pins');
* When scrolled all the way to the bottom, add more tiles. var blocks = blockContainer.children('.pin');
*/ var rowSize = Math.floor(blockContainer.width()/blockWidth);
function onScroll(event) { var blockWidth = (blockContainer.width()-blockMargin*(rowSize))/rowSize;
if(!isLoading) { var colHeights = []
var closeToBottom = ($(window).scrollTop() + $(window).height() > $(document).height() - 100);
if(closeToBottom) loadData(); for (var i=0; i < rowSize; i++) {
colHeights[i] = 0;
} }
};
function applyLayout() { for (var b=0; b < blocks.length; b++) {
$('#pins').imagesLoaded(function() { block = blocks.eq(b);
// Clear our previous layout handler.
if(handler) handler.wookmarkClear();
// Create a new layout handler. var col = -1;
handler = $('#pins .pin'); var colHeight = 0;
handler.wookmark({ for (var i=0; i < rowSize; i++) {
autoResize: true, if (col < 0) {
offset: 3, col = 0;
itemWidth: 242 colHeight = colHeights[col];
} else {
if (colHeight > colHeights[i]) {
col = i;
colHeight = colHeights[col];
}
}
}
block.css({
'margin-left': blockWidth*col+col*blockMargin
}); });
blockMarginTop = blockMargin;
block.css({
'margin-top': colHeight+blockMarginTop
}); });
}; colHeights[col] += block.height()+blockMarginTop;
/** block.css('display', 'block');
* Loads data from the API.
*/
function loadData(tag) {
isLoading = true;
$('#loader').show();
if (tag !== undefined) {
globalTag = tag;
window.history.pushState(tag, 'Pinry - Tag - '+tag, '/pins/tag/'+tag+'/');
} else if (url(2) == 'tag') {
tag = url(3);
globalTag = tag;
window.history.pushState(tag, 'Pinry - Tag - '+tag, '/pins/tag/'+tag+'/');
} }
if (tag !== undefined) { $('.spinner').css('display', 'none');
$('#pins').html(''); blockContainer.css('height', colHeights.sort().slice(-1)[0]);
page = 0;
if (tag != null)
$('.tags').html('<span class="label tag" onclick="loadData(null)">' + tag + ' x</span>');
else {
$('.tags').html('');
window.history.pushState(tag, 'Pinry - Recent Pins', '/pins/');
}
} }
var loadURL = apiURL+(page*30); var offset = 0;
if (globalTag !== null) loadURL += "&tag=" + tag;
$.ajax({ function loadPins() {
url: loadURL, $('.spinner').css('display', 'block');
success: onLoadData $.get('/api/v1/pin/?format=json&offset='+String(offset), function(pins) {
}); console.log(pins.objects[0])
}; var source = $('#pins-template').html();
var template = Handlebars.compile(source);
/** var context = {
* Receives data from the API, creates HTML for images and updates the layout pins: pins.objects
*/
function onLoadData(data) {
data = data.objects;
isLoading = false;
$('#loader').hide();
page++;
var html = '';
var i=0, length=data.length, image;
for(; i<length; i++) {
image = data[i];
html += '<div class="pin">';
html += '<div class="pin-options">';
html += '<a href="/pins/delete-pin/'+image.id+'">';
html += '<i class="icon-trash"></i>';
html += '</a>';
html += '</div>';
html += '<a class="fancybox" rel="pins" href="'+image.image+'">';
html += '<img src="'+image.thumbnail+'" width="200" >';
html += '</a>';
if (image.description) html += '<p>'+image.description+'</p>';
if (image.tags) {
html += '<p>';
for (tag in image.tags) {
html += '<span class="label tag" onclick="loadData(\'' + image.tags[tag] + '\')">' + image.tags[tag] + '</span> ';
} }
html += '</p>'; var html = template(context);
}
html += '</div>';
}
$('#pins').append(html); $('#pins').append(html);
applyLayout(); $('#pins').ajaxStop(function() {
}; tileLayout();
$(document).ready(new function() {
$(document).bind('scroll', onScroll);
loadData();
}); });
/** offset += 30;
* On clicking an image show fancybox original. });
*/ }
$('.fancybox').fancybox({
openEffect: 'none', loadPins();
closeEffect: 'none'
$(window).resize(function() {
tileLayout();
})
$(window).scroll(function() {
if($(window).scrollTop() + $(window).height() > $(document).height() - 100) {
loadPins();
}
});
}); });

View File

@@ -1,5 +1,6 @@
{% load new_pin %} {% load new_pin %}
{% load compress %} {% load compress %}
{% load verbatim %}
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
@@ -46,13 +47,35 @@
{% new_pin request %} {% new_pin request %}
{% verbatim %}
<script id="pins-template" type="text/x-handlebars-template">
{{#each pins}}
<div class="pin">
<img src="{{thumbnail}}">
{{#if description}}
<p>{{description}}</p>
{{/if}}
{{#if tags}}
<p>
{{#each tags}}
<span class="label">{{this}}</span>
{{/each}}
</p>
{{/if}}
</div>
{{/each}}
</script>
{% endverbatim %}
{% compress js %} {% compress js %}
<script src="/static/vendor/jquery/1.7.2/jquery.js"></script> <script src="/static/vendor/jquery/1.7.2/jquery.js"></script>
<script src="/static/vendor/bootstrap/2.0.3/js/bootstrap.js"></script> <script src="/static/vendor/bootstrap/2.0.3/js/bootstrap.js"></script>
<script src="/static/vendor/wookmark/0.5/jquery.wookmark.js"></script> <script src="/static/vendor/handlebars/handlebars.js"></script>
<script src="/static/vendor/fancybox/2.0.6/jquery.fancybox.js"></script>
<script src="/static/vendor/imagesloaded/2.0.1/jquery.imagesloaded.js"></script> <script>
<script src="/static/vendor/js-url/1.7.2/js-url.js"></script> var username = "{{ user.username }}";
var isSuperuser = {{ user.is_superuser|lower }};
</script>
<script src="/static/core/js/pinry.js"></script> <script src="/static/core/js/pinry.js"></script>
<script src="/static/core/js/messages.js"></script> <script src="/static/core/js/messages.js"></script>

View File

@@ -0,0 +1,44 @@
"""
jQuery templates use constructs like:
{{if condition}} print something{{/if}}
This, of course, completely screws up Django templates,
because Django thinks {{ and }} mean something.
Wrap {% verbatim %} and {% endverbatim %} around those
blocks of jQuery templates and this will try its best
to output the contents with no changes.
"""
from django import template
register = template.Library()
class VerbatimNode(template.Node):
def __init__(self, text):
self.text = text
def render(self, context):
return self.text
@register.tag
def verbatim(parser, token):
text = []
while 1:
token = parser.tokens.pop(0)
if token.contents == 'endverbatim':
break
if token.token_type == template.TOKEN_VAR:
text.append('{{')
elif token.token_type == template.TOKEN_BLOCK:
text.append('{%')
text.append(token.contents)
if token.token_type == template.TOKEN_VAR:
text.append('}}')
elif token.token_type == template.TOKEN_BLOCK:
text.append('%}')
return VerbatimNode(''.join(text))

View File

@@ -4,7 +4,7 @@
{% block yield %} {% block yield %}
<div id="pins"></div> <div id="pins"></div>
<div id="loader" class="container"> <div class="container spinner">
<div class="row"> <div class="row">
<div class="span2 offset5"> <div class="span2 offset5">
<img src="/static/core/img/loader.gif" alt="Loader"> <img src="/static/core/img/loader.gif" alt="Loader">

View File

@@ -4,7 +4,7 @@ from django.conf import settings
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^api/', include('pinry.api.urls', namespace='api')),
url(r'^pins/', include('pinry.pins.urls', namespace='pins')), url(r'^pins/', include('pinry.pins.urls', namespace='pins')),
url(r'', include('pinry.api.urls', namespace='api')),
url(r'', include('pinry.core.urls', namespace='core')), url(r'', include('pinry.core.urls', namespace='core')),
) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) ) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
Django==1.4.4 Django==1.4.4
South==0.7.4 South==0.7.4
Pillow==1.7.7 Pillow==1.7.7
django-tastypie==0.9.11 django-tastypie==0.9.12
django_compressor==1.2 django_compressor==1.2
cssmin==0.1.4 cssmin==0.1.4
jsmin==2.0.2 jsmin==2.0.2