Merge branch 'embed-widget' of git://github.com/lapilofu/philo into develop
authorJoseph Spiros <joseph.spiros@ithinksw.com>
Fri, 8 Jul 2011 22:13:18 +0000 (18:13 -0400)
committerJoseph Spiros <joseph.spiros@ithinksw.com>
Fri, 8 Jul 2011 22:13:18 +0000 (18:13 -0400)
* 'embed-widget' of git://github.com/lapilofu/philo:
  Updated the grappelli styles to override the default styles.
  Updated styles to work in the original admin. Updated the javascript to overload the dismissAddAnotherPopup function as well.
  Made the embed widget javascript work by overloading the dismissRelatedLookupPopup function.
  Made the embed widget automatically generate URLs based on the ADMIN_URL global, which is provided by Grappelli. Unfortunately, this introduces a dependency on Grappelli. I'll return to this branch at a later date when my thinking is clearer.
  Initial work on a widget for TemplateFields that allows javascript selection of an object to embed.

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..d223605
--- /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 vLargeTextField', '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 7ab4326..575b3a4 100644 (file)
@@ -9,14 +9,20 @@ from django.utils.translation import ugettext_lazy as _
 from philo.forms.fields import JSONFormField
 from philo.utils.registry import RegistryIterator
 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..525e5e3
--- /dev/null
@@ -0,0 +1,51 @@
+.embed-widget{
+       float:left;
+}
+.embed-toolbar{
+       border:1px solid #CCC;
+       border-bottom:0;
+       padding:3px 5px;
+       background:#EEE -webkit-linear-gradient(#F5F5F5, #DDD);
+       background:#EEE -moz-linear-gradient(#F5F5F5, #DDD);
+       background-color:#EEE;
+}
+.embed-widget textarea{
+       margin-top:0;
+}
+.embed-widget button, .embed-widget select{
+       vertical-align:middle;
+       margin-right: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 button:hover{
+       color:#444;
+}
+.embed-toolbar button:active{
+       color:#FFF;
+       background:#666;
+       border-color:#666;
+}
+
+.grappelli .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;
+}
+.grappelli .embed-toolbar{
+       padding:0;
+       padding-bottom:3px;
+       background:none;
+       border:none;
+}
\ 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..f946740
--- /dev/null
@@ -0,0 +1,152 @@
+;(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);
+                       
+                       // overload the dismissRelatedLookupPopup function
+                       oldDismissRelatedLookupPopup = window.dismissRelatedLookupPopup;
+                       window.dismissRelatedLookupPopup = function (win, chosenId) {
+                               var name = windowname_to_id(win.name),
+                                       elem = $('#'+win.name), val;
+                               // if the original element was an embed widget, run our script
+                               if (elem.parent().hasClass('embed-widget')) {
+                                       contenttype = $('select',elem.parent()).val();
+                                       widget.appendEmbed(elem, contenttype, chosenId);
+                                       elem.focus();
+                                       win.close();
+                                       return;
+                               }
+                               // otherwise, do what you usually do
+                               oldDismissRelatedLookupPopup.apply(this, arguments);
+                       }
+                       
+                       // overload the dismissAddAnotherPopup function
+                       oldDismissAddAnotherPopup = window.dismissAddAnotherPopup;
+                       window.dismissAddAnotherPopup = function (win, newId, newRepr) {
+                               var name = windowname_to_id(win.name),
+                                       elem = $('#'+win.name), val;
+                               if (elem.parent().hasClass('embed-widget')) {
+                                       dismissRelatedLookupPopup(win, newId);
+                               }
+                               // otherwise, do what you usually do
+                               oldDismissAddAnotherPopup.apply(this, arguments);
+                       }
+                       
+                       // Add grappelli to the body class if the admin is grappelli. This will allow us to customize styles accordingly.
+                       if (window.grappelli) {
+                               $(document.body).addClass('grappelli');
+                       }
+               },
+               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 for the popup
+                       // TODO: Find a better way to get the admin URL if possible. This will break if the URL patterns for the admin ever change.
+                       href=['../../../', app_label,  '/', object_name, '/?pop=1'].join('');
+                       
+                       // open a new window
+                       win = window.open(href, id_to_windowname(textarea.attr('id')), 'height=500,width=980,resizable=yes,scrollbars=yes');
+               },
+               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]),
+                               new_cursor_pos = current_selection[0]+embed_string.length;
+                       $textarea.val(new_text);
+                       textarea.setSelectionRange(new_cursor_pos, new_cursor_pos);
+               }
+       }
+       
+       $(widget.init);
+}(django.jQuery));
\ No newline at end of file