Initial work on a widget for TemplateFields that allows javascript selection of an...
authorHarris Lapiroff <hlapirof@oberlin.edu>
Fri, 10 Jun 2011 20:25:56 +0000 (16:25 -0400)
committerHarris Lapiroff <hlapirof@oberlin.edu>
Fri, 10 Jun 2011 20:25:56 +0000 (16:25 -0400)
philo/forms/widgets.py [new file with mode: 0644]
philo/models/fields/__init__.py
philo/static/philo/css/EmbedWidget.css [new file with mode: 0644]
philo/static/philo/js/EmbedWidget.js [new file with mode: 0644]

diff --git a/philo/forms/widgets.py b/philo/forms/widgets.py
new file mode 100644 (file)
index 0000000..739adc7
--- /dev/null
@@ -0,0 +1,30 @@
+from django.forms.widgets import Textarea
+from django.utils import simplejson as json
+
+__all__ = ('EmbedWidget',)
+
+class EmbedWidget(Textarea):
+       """A form widget with the HTML class embedding and an embedded list of content-types."""
+       def __init__(self, attrs=None):
+               from philo.models import value_content_type_limiter
+               
+               content_types = value_content_type_limiter.classes
+               data = []
+               
+               for content_type in content_types:
+                       data.append({'app_label': content_type._meta.app_label, 'object_name': content_type._meta.object_name.lower(), 'verbose_name': unicode(content_type._meta.verbose_name)})
+               
+               json_ = json.dumps(data)
+               
+               default_attrs = {'class': 'embedding', 'data-content-types': json_ }
+               
+               if attrs:
+                       default_attrs.update(attrs)
+                       
+               super(EmbedWidget, self).__init__(default_attrs)
+               
+       class Media:
+               css = {
+                       'all': ('philo/css/EmbedWidget.css',),
+               }
+               js = ('philo/js/EmbedWidget.js',)
\ No newline at end of file
index efd315f..0003575 100644 (file)
@@ -8,14 +8,20 @@ from django.utils.translation import ugettext_lazy as _
 
 from philo.forms.fields import JSONFormField
 from philo.validators import TemplateValidator, json_validator
+from philo.forms.widgets import EmbedWidget
 #from philo.models.fields.entities import *
 
 
-class TemplateField(models.TextField):
+class TemplateField(models.Field):
        """A :class:`TextField` which is validated with a :class:`.TemplateValidator`. ``allow``, ``disallow``, and ``secure`` will be passed into the validator's construction."""
        def __init__(self, allow=None, disallow=None, secure=True, *args, **kwargs):
                super(TemplateField, self).__init__(*args, **kwargs)
                self.validators.append(TemplateValidator(allow, disallow, secure))
+       
+       def formfield(self, **kwargs):
+               defaults = {'widget': EmbedWidget}
+               defaults.update(kwargs)
+               return super(TemplateField, self).formfield(**defaults)
 
 
 class JSONDescriptor(object):
diff --git a/philo/static/philo/css/EmbedWidget.css b/philo/static/philo/css/EmbedWidget.css
new file mode 100644 (file)
index 0000000..3b21016
--- /dev/null
@@ -0,0 +1,32 @@
+.embed-widget{
+       background:#DDD;
+       padding:2px;
+       border:1px solid #CCC;
+       border-radius:5px;
+       -webkit-border-radius:5px;
+       -moz-border-radius:5px;
+       display:inline-block;
+       margin:0 -3px;
+}
+.embed-toolbar{
+       padding-bottom:3px;
+}
+.embed-toolbar button{
+       background:#FFF;
+       border:1px solid #CCC;
+       border-radius:3px;
+       -webkit-border-radius:3px;
+       -moz-border-radius:3px;
+       color:#666;
+}
+.embed-toolbar select, .embed-toolbar button{
+       margin-right:3px;
+}
+.embed-toolbar button:hover{
+       color:#444;
+}
+.embed-toolbar button:active{
+       color:#FFF;
+       background:#666;
+       border-color:#666;
+}
\ No newline at end of file
diff --git a/philo/static/philo/js/EmbedWidget.js b/philo/static/philo/js/EmbedWidget.js
new file mode 100644 (file)
index 0000000..0f972e4
--- /dev/null
@@ -0,0 +1,135 @@
+;(function ($) {
+       var widget = window.embedWidget;
+       
+       widget = {
+               options: {},
+               optgroups: {},
+               init: function () {
+                       var EmbedFields = widget.EmbedFields = $('.embedding'),
+                               EmbedWidgets = widget.EmbedWidgets,
+                               EmbedBars = widget.EmbedBars,
+                               EmbedButtons = widget.EmbedButtons,
+                               EmbedSelects = widget.EmbedSelects;
+
+                       EmbedFields.wrap($('<div class="embed-widget" />'));
+                       EmbedWidgets = $('.embed-widget');
+                       EmbedWidgets.prepend($('<div class="embed-toolbar" />'));
+                       EmbedBars = $('.embed-toolbar');
+                       EmbedBars.append('<select class="embed-select"></select><button class="embed-button">Embed</button>');
+                       EmbedButtons = $('.embed-button');
+                       EmbedSelects = $('.embed-select');
+                       
+                       widget.parseContentTypes();
+                       EmbedSelects.each(widget.populateSelect);
+                       
+                       EmbedButtons.click(widget.buttonHandler);
+               },
+               parseContentTypes: function () {
+                       var string = widget.EmbedFields.eq(0).attr('data-content-types'),
+                               data = $.parseJSON(string),
+                               i=0,
+                               current_app_label = '',
+                               optgroups = {};
+                               
+                               // this loop relies on data being clustered by app
+                               for(i=0; i < data.length; i++){
+                                       item = data[i]
+                                       // run this next loop every time we encounter a new app label
+                                       if (item.app_label !== current_app_label) {
+                                               current_app_label = item.app_label;
+                                               optgroups[current_app_label] = {}
+                                       }
+                                       optgroups[current_app_label][item.verbose_name] = [item.app_label,item.object_name].join('.');
+                                       
+                                       widget.optgroups = optgroups;
+                               }
+               },
+               populateSelect: function () {
+                       var $this = $(this),
+                               optgroups = widget.optgroups,
+                               optgroup_els = {},
+                               optgroup_el, group;
+                               
+                       // append a title
+                       $this.append('<option value="">Media Types</option>');
+                       
+                       // for each group
+                       for (name in optgroups){
+                               if(optgroups.hasOwnProperty(name)){
+                                       // assign the group to variable group, temporarily
+                                       group = optgroups[name];
+                                       // create an element for this group and assign it to optgroup_el, temporarily
+                                       optgroup_el = optgroup_els[name] = $('<optgroup label="'+name+'" />');
+                                       // append this element to the select menu
+                                       $this.append(optgroup_el);
+                                       // for each item in the group
+                                       for (name in group) {
+                                               // append an option to the optgroup
+                                               optgroup_el.append('<option value='+group[name]+'>'+name+'</option>');
+                                       }
+                               }
+                       }
+               },
+               buttonHandler: function (e) {
+                       var $this = $(this),
+                               select = $this.prev('select'),
+                               embed_widget = $this.closest('.embed-widget'),
+                               textarea = embed_widget.children('.embedding').eq(0),
+                               val, app_label, object_name,
+                               href,
+                               win;
+                       
+                       // prevent the button from submitting the form
+                       e.preventDefault();
+                       // handle the case that they haven't chosen a type to embed
+                       if (select.val()==='') {
+                               alert('Please select a media type to embed.');
+                               textarea.focus();
+                               return;
+                       }
+                       
+                       // split the val into app and object
+                       val = select.val();
+                       app_label = val.split('.')[0];
+                       object_name = val.split('.')[1];
+                       
+                       // generate the url
+                       // TODO: abstract this so it can calculate the admin url dynamically
+                       href=["/admin", app_label, object_name, '?pop=1'].join('/');
+                       
+                       // this is a bit hackish. let's walk through it.
+                       // TODO: best to write our own template for this in the future
+                       
+                       // open a new window
+                       win = window.open(href, id_to_windowname(textarea.attr('id')), 'height=500,width=980,resizable=yes,scrollbars=yes');
+                       
+                       // when the window finishes loading
+                       win.addEventListener('load', function(){
+                               // collect all the links to objects in that window
+                               var links = win.django.jQuery('#changelist th:first-child a');
+                                       // for each link
+                                       links.each(function(){
+                                               // capture the pk
+                                               var pk = $(this).attr('href').split('/')[0];
+                                               // bind our own function to onclick instead of the function that's currently there
+                                               this.onclick = function () { widget.appendEmbed(textarea, val, pk); win.close(); return false; };
+                                       });
+                       }, false)
+                       
+                       // return focus to the textarea
+                       textarea.focus();
+               },
+               appendEmbed: function (textarea, embed_type, embed_id) {
+                       var $textarea = $(textarea),
+                               textarea = $textarea[0], // make sure we're *not* working with a jQuery object
+                               current_selection = [textarea.selectionStart, textarea.selectionEnd],
+                               current_text = $textarea.val(),
+                               embed_string = ['{% embed', embed_type, embed_id, '%}'].join(' '),
+                               new_text = current_text.substring(0, current_selection[0]) + embed_string + current_text.substring(current_selection[1]);
+                               
+                       $textarea.val(new_text);
+               }
+       }
+       
+       $(widget.init);
+}(django.jQuery));
\ No newline at end of file