Skip to content

Instantly share code, notes, and snippets.

@marcinjackowiak
Last active May 11, 2016 05:45
Show Gist options
  • Save marcinjackowiak/06201ef84a912eed38f786278091735b to your computer and use it in GitHub Desktop.
Save marcinjackowiak/06201ef84a912eed38f786278091735b to your computer and use it in GitHub Desktop.
Basic "read more" functionality for a Backbone Collection rendered in an html table (read-only). Using Bootstrap to make it look nice.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<!-- External dependencies -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootswatch/3.3.6/flatly/bootstrap.min.css">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
<!-- This is the template used in the home view -->
<script type="text/template" id="home">
<table class="table table-bordered">
<thead>
<tr>
<th class="bg-primary">Id</th>
<th class="bg-primary">Name</th>
<th class="bg-primary">Description</th>
</tr>
</thead>
<tbody>
<% _.each(collection, function(model) { %>
<tr>
<td><%= model.get('id') %></td>
<td><%= model.get('name') %></td>
<td style="width: 75%;"><%= model.get('desc') %></td>
</tr>
<% }) %>
</tbody>
</table>
</script>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-xs-12">
<div id="main" style="padding-top: 40px;">
</div>
</div>
</div>
<script>
//
// The readMore plugin code
//
// A few words:
// This is just one way of doing it. One of the drawbacks is that each time you click the link
// the entire view is rendered again. Also the changes are done to the model/collection directly so
// use for read-only collections.
//
$.readMore = function (params){
// Defaults
var params = _.extend({
view: null, // The view that will capture the events.
collection: null, // Collection to apply the logic to.
field: null, // The field used in the template for display.
excerptField: null, // The excerpt field. When not provided the fullField is used instead to create the excerpt.
fullField: null, // The field holding the full value.
excerptLength: 80 // The length the full value will be trimmed to when no excerptField is provided.
}, params);
var view = params.view;
var collection = params.collection;
var field = params.field;
var excerptField = params.excerptField;
var fullField = params.fullField;
var excerptLength = params.excerptLength;
// Click handler for the "More" and "Less" links. The method is created on the "View" class.
view.readMoreClick = function(e) {
e.preventDefault();
var $target = $(e.currentTarget);
if($target.hasClass('show-more-link')) {
var cid = $target.attr('data-cid');
var field = $target.attr('data-field');
var model = collection.get(cid);
var full = model.readMoreFields[field].full;
full += ' <a href="#" class="read-more-link show-less-link" data-cid="'+cid+'" data-field="'+field+'"><small>[Less]</small></a>';
full = full.replace(/\n/g, "<br/>"); // Convert line breaks to <br/> tags. Comment out if not necessary.
model.set(field, full);
} else
if($target.hasClass('show-less-link')) {
var cid = $target.attr('data-cid');
var field = $target.attr('data-field');
var model = collection.get(cid);
var excerpt = model.readMoreFields[field].excerpt;
excerpt += ' <a href="#" class="read-more-link show-more-link" data-cid="'+cid+'" data-field="'+field+'"><small>[More]</small></a>';
model.set(field, excerpt);
}
return false;
};
// Iterate the collection and configure the values needed to swap between the excerpt
// and the full value.
collection.each(function(model) {
var excerpt = model.get(excerptField);
var full = model.get(fullField);
if(full.length > excerptLength) { // Only applies when the full value is long enough.
if(excerpt == null || excerpt.length < full.length) { // Excerpt is either not provided or is not shorter than the full value.
var cid = model.cid; // Use cid, which is backbone's internal unique model identifier
var full = model.get(fullField);
if(excerpt == null) {
// Trim full value without cutting words
excerpt = full.substring(0, excerptLength);
excerpt = excerpt.substring(0, excerpt.lastIndexOf(' ')) + '...';
}
// Use an array so we can apply this to more than one field in a collection.
if(model.readMoreFields == null) model.readMoreFields = new Array();
model.readMoreFields[field] = {
excerptFieldName: excerptField,
fullFieldName: fullField,
excerpt: excerpt,
full: full
};
// Set to excerpt initially
excerpt += ' <a href="#" class="read-more-link show-more-link" data-cid="'+cid+'" data-field="'+field+'"><small>[More]</small></a>';
model.set(field, excerpt);
}
}
});
var events = {
"click a.read-more-link": "readMoreClick"
};
if(view.events == null) view.events = {}; // Initialize if not set
view.delegateEvents(_.extend(view.events, events));
};
//
//
// Below code is needed for demonstration purposes. It a simple backbone application with one view.
//
//
var Model = Backbone.Model.extend({});
var Collection = Backbone.Collection.extend({
model: Model
});
var View = Backbone.View.extend({
template: _.template($("#home").html()),
initialize: function(options) {
var view = this;
// Create the collection object
this.collection = new Collection();
// Add data to the collection. This will create a new model for each row of data.
this.collection.add([
{id:1, name: 'Model', short_desc: 'Customer Excerpt: Models are the heart of any JavaScript application, containing the interactive data as well as a large part of the logic surrounding it.', desc: 'Models are the heart of any JavaScript application, containing the interactive data as well as a large part of the logic surrounding it: conversions, validations, computed properties, and access control. You extend Backbone.Model with your domain-specific methods, and Model provides a basic set of functionality for managing changes. The following is a contrived example, but it demonstrates defining a model with a custom method, setting an attribute, and firing an event keyed to changes in that specific attribute. After running this code once, sidebar will be available in your browser\'s console, so you can play around with it.'},
{id:2, name: 'Collection', short_desc: null, desc: 'Collections are ordered sets of models. You can bind "change" events to be notified when any model in the collection has been modified, listen for "add" and "remove" events, fetch the collection from the server, and use a full suite of Underscore.js methods. Any event that is triggered on a model in a collection will also be triggered on the collection directly, for convenience. This allows you to listen for changes to specific attributes in any model in a collection.'},
{id:3, name: 'Router', short_desc: null, desc: 'Web applications often provide linkable, bookmarkable, shareable URLs for important locations in the app. Until recently, hash fragments (#page) were used to provide these permalinks, but with the arrival of the History API, it\'s now possible to use standard URLs (/page). Backbone.Router provides methods for routing client-side pages, and connecting them to actions and events. For browsers which don\'t yet support the History API, the Router handles graceful fallback and transparent translation to the fragment version of the URL.'},
{id:4, name: 'View', short_desc: null, desc: 'Backbone views are almost more convention than they are code — they don\'t determine anything about your HTML or CSS for you, and can be used with any JavaScript templating library. The general idea is to organize your interface into logical views, backed by models, each of which can be updated independently when the model changes, without having to redraw the page. Instead of digging into a JSON object, looking up an element in the DOM, and updating the HTML by hand, you can bind your view\'s render function to the model\'s "change" event — and now everywhere that model data is displayed in the UI, it is always immediately up to date.'},
]);
// Change event is triggered when any of the models in the collection is changed.
this.listenTo(this.collection, "change", view.render);
// Sync event is triggered when the collection data is loaded from server
//this.listenTo(this.collection, "sync", view.render); // Not needed here
// Configure readMore for the "desc" field. Since the logic makes changes to the collection
// it will trigger an additional call to the "render" method.
$.readMore({
view: view,
collection: view.collection,
field: 'desc',
excerptField: null,
fullField: 'desc'
});
// Another usage example. This one uses another field as the preformatted source of the excerpt.
/*$.readMore({
view: view,
collection: view.collection,
field: 'desc',
excerptField: 'short_desc',
fullField: 'desc'
});*/
},
render: function() {
// Render the template
this.$el.html(this.template({collection: this.collection.models}));
// Delegate events - is needed to preserve events after subsequent renders
this.delegateEvents();
}
});
var Router = Backbone.Router.extend({
routes: {
'': 'home' // Default route will be called after initialization
},
home: function() {
this.view = new View({
el: '#main'
});
this.view.render();
}
});
// Initialize the Backbone application.
window.app = new Router();
Backbone.history.start();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment