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 import fields
from tastypie.authentication import BasicAuthentication
from tastypie.authorization import DjangoAuthorization
from django.contrib.auth.models import User
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
tags = fields.ListField()
submitter = fields.ForeignKey(UserResource, 'submitter', full=True)
class Meta:
queryset = Pin.objects.all()
@@ -17,6 +25,7 @@ class PinResource(ModelResource): # pylint: disable-msg=R0904
include_resource_uri = False
filtering = {
'published': ['gt'],
'submitter': ['exact']
}
def build_filters(self, filters=None):
@@ -37,13 +46,3 @@ class PinResource(ModelResource): # pylint: disable-msg=R0904
tags = bundle.data.get('tags', [])
bundle.obj.tags.set(*tags)
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 tastypie.api import Api
from .api import PinResource
from .api import UserResource
pin_resource = PinResource()
user_resource = UserResource()
v1_api = Api(api_name='v1')
v1_api.register(PinResource())
v1_api.register(UserResource())
urlpatterns = patterns('',
url(r'', include(pin_resource.urls)),
url(r'', include(user_resource.urls)),
url(r'^api/', include(v1_api.urls)),
)

View File

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

View File

@@ -1,121 +1,85 @@
/**
* Based on Wookmark's endless scroll.
*/
var apiURL = '/api/pin/?format=json&offset='
var page = 0;
var handler = null;
var globalTag = null;
var isLoading = false;
$(window).load(function() {
function tileLayout() {
// Config
var blockMargin = 20;
var blockWidth = 240;
// End Config
/**
* 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();
}
};
var blockContainer = $('#pins');
var blocks = blockContainer.children('.pin');
var rowSize = Math.floor(blockContainer.width()/blockWidth);
var blockWidth = (blockContainer.width()-blockMargin*(rowSize))/rowSize;
var colHeights = []
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(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) {
$('#pins').html('');
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/');
for (var i=0; i < rowSize; i++) {
colHeights[i] = 0;
}
for (var b=0; b < blocks.length; b++) {
block = blocks.eq(b);
var col = -1;
var colHeight = 0;
for (var i=0; i < rowSize; i++) {
if (col < 0) {
col = 0;
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');
}
$('.spinner').css('display', 'none');
blockContainer.css('height', colHeights.sort().slice(-1)[0]);
}
var loadURL = apiURL+(page*30);
if (globalTag !== null) loadURL += "&tag=" + tag;
$.ajax({
url: loadURL,
success: onLoadData
});
};
var offset = 0;
/**
* Receives data from the API, creates HTML for images and updates the layout
*/
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>';
}
html += '</div>';
function loadPins() {
$('.spinner').css('display', 'block');
$.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 = {
pins: pins.objects
}
var html = template(context);
$('#pins').append(html);
$('#pins').ajaxStop(function() {
tileLayout();
});
offset += 30;
});
}
$('#pins').append(html);
applyLayout();
};
$(document).ready(new function() {
$(document).bind('scroll', onScroll);
loadData();
});
loadPins();
/**
* On clicking an image show fancybox original.
*/
$('.fancybox').fancybox({
openEffect: 'none',
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 compress %}
{% load verbatim %}
<!DOCTYPE html>
<html>
@@ -46,13 +47,35 @@
{% 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 %}
<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/wookmark/0.5/jquery.wookmark.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 src="/static/vendor/js-url/1.7.2/js-url.js"></script>
<script src="/static/vendor/handlebars/handlebars.js"></script>
<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/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 %}
<div id="pins"></div>
<div id="loader" class="container">
<div class="container spinner">
<div class="row">
<div class="span2 offset5">
<img src="/static/core/img/loader.gif" alt="Loader">

View File

@@ -4,7 +4,7 @@ from django.conf import settings
urlpatterns = patterns('',
url(r'^api/', include('pinry.api.urls', namespace='api')),
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')),
) + 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
South==0.7.4
Pillow==1.7.7
django-tastypie==0.9.11
django-tastypie==0.9.12
django_compressor==1.2
cssmin==0.1.4
jsmin==2.0.2