From: Harris Lapiroff Date: Fri, 10 Jun 2011 20:25:56 +0000 (-0400) Subject: Initial work on a widget for TemplateFields that allows javascript selection of an... X-Git-Tag: philo-0.9.1^2~4^2~4 X-Git-Url: http://git.ithinksw.org/philo.git/commitdiff_plain/7902f12cf4e054ee4dae904687ac97d5e2f7360a 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..739adc7 --- /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', '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 efd315f..0003575 100644 --- a/philo/models/fields/__init__.py +++ b/philo/models/fields/__init__.py @@ -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 index 0000000..3b21016 --- /dev/null +++ b/philo/static/philo/css/EmbedWidget.css @@ -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 index 0000000..0f972e4 --- /dev/null +++ b/philo/static/philo/js/EmbedWidget.js @@ -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($('
')); + 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); + }, + 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 + // 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