From: Joseph Spiros Date: Fri, 8 Jul 2011 22:13:18 +0000 (-0400) Subject: Merge branch 'embed-widget' of git://github.com/lapilofu/philo into develop X-Git-Tag: philo-0.9.1^2~4 X-Git-Url: http://git.ithinksw.org/philo.git/commitdiff_plain/6dc154ec98d58159b3e75447ce7b7fe5642acec4?hp=93eac03cfd379525b6cf41825b14a5d37cd03505 Merge branch 'embed-widget' of git://github.com/lapilofu/philo into develop * '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. --- diff --git a/philo/forms/widgets.py b/philo/forms/widgets.py new file mode 100644 index 0000000..d223605 --- /dev/null +++ b/philo/forms/widgets.py @@ -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 diff --git a/philo/models/fields/__init__.py b/philo/models/fields/__init__.py index 7ab4326..575b3a4 100644 --- a/philo/models/fields/__init__.py +++ b/philo/models/fields/__init__.py @@ -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 index 0000000..525e5e3 --- /dev/null +++ b/philo/static/philo/css/EmbedWidget.css @@ -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 index 0000000..f946740 --- /dev/null +++ b/philo/static/philo/js/EmbedWidget.js @@ -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($('
')); + EmbedWidgets = $('.embed-widget'); + EmbedWidgets.prepend($('
')); + EmbedBars = $('.embed-toolbar'); + EmbedBars.append(''); + 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(''); + + // 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] = $(''); + // 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(''); + } + } + } + }, + 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