Learning Django by Example(12) Tag it in place

djangopython

The main challenge of tagging is the usability: tags are usually used for navigation, but they are subject to updating at any time. Therefore, tags needs to switch back and forth in display mode and edit mode in a non-intrusive way. Dojo’s dijit.InlineEditBox seems a good candidate for this purpose.

I failed to declare a dijit widget inside the HTML file, maybe the mechanism of dojo.parser prevents me to do so? So I went back to a local copy of dojo-svn and declared FilteredInlineEditBox in dijit namespace:

dojo.provide("dijit.FilteredInlineEditBox");
dojo.require("dijit.InlineEditBox");

dojo.declare("dijit.FilteredInlineEditBox", dijit.InlineEditBox, {
  postMixInProperties: function () {
    this.inherited("postMixInProperties", arguments);
    this.disabled = true;
  },

  setValue: function (val) {
    this.value = this.filterIn(val);
    this.displayNode.innerHTML = dojo.trim(val) || this.noValueIndicator;
  },

  filterIn: function (val) {
    return val.replace(/<[^>]+>/g, "");
  },
});

No extra functionalities added, the inherited functions just manipulate the status for different behaviors. First, the dijit widget is disable by default to block any mouse event.

In the client side, the edit control tailgates the FilteredInlineEditBox:

<p>
  Tags:
  <span
    id="tags"
    dojoType="dijit.FilteredInlineEditBox"
    editorParams="{lowercase:true, trim:true,}"
    onChange="updateTags"
  >
    {{ object.tags|popuptags|safe }}</span
  >
  (<a href="javascript:void(0);" id="edit">edit</a>)
</p>

and its onclick is hooked to invoke _edit:

dojo.connect(dojo.byId("edit"), "onclick", function () {
  dijit.byId("tags")._edit();
});

Notice that _edit never references/modifies disabled, so when the FilteredInlineEditBox exits edit mode, it is still disabled. This is the exact behavior we ask for!

A xhrPost request is used to update the tags asynchronously when onChange is invoked:

function updateTags(vals) {
  dojo
    .xhrPost({
      url: "/admin/bookshelf/tag/update/",
      content: { isbn: "{{object.isbn}}", tags: vals },
    })
    .addCallback(function (newTags) {
      dijit.byId("tags").setValue(newTags);
    });
}

In the serve side, the tags are updated, and HTML links are rendered with link attributes:

def tag_update(request):
    newTag = "none";
    book = Book.objects.get(isbn=request["isbn"])
    if book:
        book.tags = request["tags"]
        book.save()
        tags = [ x for x in re.split("\W+", request["tags"]) if x ]
        newTag = " ".join(['<a href="/bookshelf/tags/%s">%s</a>' % (t, t) for t in tags ])
    return HttpResponse(newTag)

Now in our hack of setValue, the displayNode is still rendered as HTML links, but all the HTML elements are filtered by filterIn, so only the raw text is passed to value, which is used to construct the edit widget later. Therefore, the tags are displayed to users as HTML links, but modified as raw text.