jquery

Save Only Changed Attributes in Backbone.js

Backbone.js syncs all model data to the server by default regardless of what actually changed. This makes a lot of frontend tasks and debugging much easier, but for some models, sending all the data every time is overkill.

Here’s a quick extension to backbone models and collections to only sync changes. Just call model.saveChanges instead of model.save and only your changes will be synced.

# CoffeeScript
cx_backbone_common =
  sync: (method, model, options) ->
    # Changed attributes will be available here if model.saveChanges was called instead of model.save
    if method == 'update' && model.changedAttributes()
      options.data = JSON.stringify(model.changedAttributes())
      options.contentType = 'application/json';
    Backbone.sync.call(this, method, model, options)

cx_backbone_model =
  # Calling this method instead of set will force sync to only send changed attributes
  # Changed event will not be triggered until after the model is synced
  saveChanges: (attrs) ->
    @save(attrs, {wait: true})

_.extend(Backbone.Model.prototype, cx_backbone_common, cx_backbone_model)
_.extend(Backbone.Collection.prototype, cx_backbone_common)

Rails 3′s unobtrusive javascript support…

Rails 3′s unobtrusive javascript support makes it easy to integrate jQuery UI Autocomplete and any backend or custom query you want. There’s a Rails3 jQuery Autocomplete gem already available on github, but it’s not very RESTful and makes a lot of assumptions that might not fit your application.  So why not roll your own?

It’s easy.

First, make sure your rails app is setup with jQuery support.

Next, download jQuery UI Autocomplete, copy the appropriate files into your javascripts and stylesheets directories, and make sure the JS and CSS files are loading properly in your app.

Here’s the general process I use to setup autocomplete for Rails 3:

In the controller

  1. create a method to handle the autocomplete search request

In routes

  1. add a route for the method you created in the controller – this is your autocomplete search endpoint

In the view

  1. add a text field with a unique class name like ‘autocomplete’
  2. add a unique data attribute like ‘data-endpoint’ to the text field and set it to the path of your autocomplete endpoint

In application.js

  1. enable jQuery Autocomplete on any DOM element with the class ‘autocomplete’ (or whatever you used in the view)
  2. use the value of data-endpoint as the source URL for the jQuery Autocomplete widget

In the following example, I’m tagging a user and using autocomplete to help suggest tags.

controllers/users/tags_controller.rb

class Users::TagsController < ApplicationController
  respond_to :json
  skip_before_filter :authenticate_user!, :only => [:index]

  def index
    @user = User.find(params[:user_id])
    @tags = Tag.where(["LOWER(name) LIKE ?", "#{params[:term].downcase}%"]).select('name').limit(15).map {|tag| tag.name}
    respond_with(@tags)
  end

  def create
    @user = User.find(params[:user_id])
    tag = @user.tags.create!(params[:tag])
    flash[:success] = 'Tag added'
    redirect_to user_path(@user)
  end
end

The tags controller has two methods. One for creating a tag, and one (index) for handling tag autocomplete searches.

The index method expects user_id and term params. In this example, the user_id is being handled in the path /users/:user_id/tags and jQuery Autocomplete sends in the term param.

routes.rb

resources :users do
  resources :tags, :only => [:index, :create], :controller => 'users/tags'
end

This creates a /users/:user_id/tags route and a users_tags_path helper to use in your views.

views/users/show.html.haml

= form_for [@user, @user.tags.build] do |f|
  .field
    = f.label :name, 'Tag'
    = f.text_field :name, :class => 'autocomplete', :'data-endpoint' => user_tags_path(@user)
  = f.submit 'Add Tag'

javascripts/application.js

jQuery(function($){
  $('.autocomplete').each(function(){
    var self = $(this);
    self.autocomplete({source: self.attr('data-endpoint')});
  });
});

That’s all there is to it. You can easily modify your controller and model code to use MongoDB, Solr, Redis, or whatever you want.

Happy coding.