0
0
mirror of https://github.com/wagtail/wagtail.git synced 2024-12-01 11:41:20 +01:00

Initial idea for a table stream field block using handsontable.js.

Fixed TableBlock exception at module load time.

Refactored table block into wagtail.contrib.table_block.

Removed unused imports.
This commit is contained in:
Moritz Pfeiffer 2015-09-17 17:23:30 +02:00 committed by Matt Westcott
parent 4b7d691908
commit 311c2da421
6 changed files with 318 additions and 0 deletions

View File

@ -0,0 +1,100 @@
from __future__ import absolute_import, unicode_literals
import json
from wagtail.utils.widgets import WidgetWithScript
from wagtail.wagtailcore.blocks import FieldBlock
from django.template.loader import render_to_string
from django.utils.functional import cached_property
from django.utils import translation
from django import forms
class TableInput(WidgetWithScript, forms.HiddenInput):
def __init__(self, table_options=None, attrs=None):
self.table_options = table_options
super(TableInput, self).__init__(attrs=attrs)
def render(self, name, value, attrs=None):
original_field_html = super(TableInput, self).render(name, value, attrs)
return render_to_string("table_block/widgets/table.html", {
'original_field_html': original_field_html,
'attrs': attrs,
'value': value,
})
def render_js_init(self, id_, name, value):
return "initTable({0}, {1});".format(json.dumps(id_), json.dumps(self.table_options))
class TableBlock(FieldBlock):
def __init__(self, required=True, help_text=None, table_options=None, **kwargs):
# CharField's 'label' and 'initial' parameters are not exposed, as Block handles that functionality
# natively (via 'label' and 'default')
# CharField's 'max_length' and 'min_length' parameters are not exposed as table data needs to
# have arbitrary length
# table_options can contain any valid handsontable options: http://docs.handsontable.com/0.18.0/Options.html
self.field_options = {'required': required, 'help_text': help_text}
language = translation.get_language()
if language is not None and len(language) > 2:
language = language[:2]
default_table_options = {
'minSpareRows': 0,
'startRows': 3,
'startCols': 3,
'colHeaders': False,
'rowHeaders': False,
'contextMenu': True,
'editor': 'text',
'stretchH': 'all',
'height': 400,
'language': language,
'renderer': 'html',
'autoColumnSize': False,
}
if table_options is not None:
default_table_options.update(table_options)
self.table_options = default_table_options
super(TableBlock, self).__init__(**kwargs)
@cached_property
def field(self):
return forms.CharField(widget=TableInput(table_options=self.table_options), **self.field_options)
def value_from_form(self, value):
return json.loads(value)
def value_for_form(self, value):
return json.dumps(value)
def render(self, value):
template = getattr(self.meta, 'template', None)
if template:
table_header = value['data'][0] if value.get('data', None) and len(value['data']) > 0 and value.get('first_row_is_table_header', False) else None
first_col_is_header = value.get('first_col_is_header', False)
context = {
'self': value,
self.TEMPLATE_VAR: value,
'table_header': table_header,
'first_col_is_header': first_col_is_header,
'data': value['data'][1:] if table_header else value.get('data', [])
}
return render_to_string(template, context)
else:
return self.render_basic(value)
@property
def media(self):
return forms.Media(
css={'all': ['table_block/css/vendor/handsontable-0.18.0.full.min.css']},
js=['table_block/js/vendor/handsontable-0.18.0.full.min.js', 'table_block/js/table.js']
)
class Meta:
default = None
template = 'table_block/blocks/table.html'

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,97 @@
'use strict';
function initTable(id, tableOptions) {
var containerId = id + '-handsontable-container';
var tableHeaderCheckboxId = id + '-handsontable-header';
var colHeaderCheckboxId = id + '-handsontable-col-header';
var hiddenStreamInput = $('#' + id);
var tableHeaderCheckbox = $('#' + tableHeaderCheckboxId);
var colHeaderCheckbox = $('#' + colHeaderCheckboxId);
var hot;
var finalOptions = {};
var persist;
var cellEvent;
var structureEvent;
var dataForForm = null;
var getWidth = function() {
return $('footer').innerWidth();
};
try {
dataForForm = $.parseJSON(hiddenStreamInput.val());
} catch (e) {
// do nothing
}
for (var key in tableOptions) {
if (tableOptions.hasOwnProperty(key)) {
finalOptions[key] = tableOptions[key];
}
}
if (dataForForm !== null) {
if (dataForForm.hasOwnProperty('data')) {
// Overrides default value from tableOptions (if given) with value from database
finalOptions.data = dataForForm.data;
}
if (dataForForm.hasOwnProperty('first_row_is_table_header')) {
tableHeaderCheckbox.prop('checked', dataForForm.first_row_is_table_header);
}
if (dataForForm.hasOwnProperty('first_col_is_header')) {
colHeaderCheckbox.prop('checked', dataForForm.first_col_is_header);
}
}
if (!tableOptions.hasOwnProperty('width')) {
// Size to footer width if width is not given in tableOptions
$(window).resize(function() {
hot.updateSettings({
width: getWidth()
});
});
}
persist = function() {
hiddenStreamInput.val(JSON.stringify({
data: hot.getData(),
first_row_is_table_header: tableHeaderCheckbox.prop('checked'),
first_col_is_header: colHeaderCheckbox.prop('checked')
}));
};
cellEvent = function(change, source) {
if (source === 'loadData') {
return; //don't save this change
}
persist();
};
structureEvent = function(index, amount) {
persist();
};
tableHeaderCheckbox.change(function() {
persist();
});
colHeaderCheckbox.change(function() {
persist();
});
finalOptions.afterChange = cellEvent;
finalOptions.afterCreateCol = structureEvent;
finalOptions.afterCreateRow = structureEvent;
finalOptions.afterRemoveCol = structureEvent;
finalOptions.afterRemoveRow = structureEvent;
hot = new Handsontable(document.getElementById(containerId), finalOptions);
hot.render(); // Call to render removes 'null' literals from empty cells
// Apply resize after document is finished loading (footer width is set)
if ('resize' in $(window)) {
$(window).load(function() {
$(window).resize();
});
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,38 @@
<div class="table">
<table>
{% if table_header %}
<thead>
<tr>
{% for column in table_header %}
<th>
{% if column.strip %}
{{ column.strip|safe|linebreaksbr }}
{% endif %}
</th>
{% endfor %}
</tr>
</thead>
{% endif %}
<tbody>
{% for row in data %}
<tr>
{% for column in row %}
{% if first_col_is_header and forloop.first %}
<th>
{% if column.strip %}
{{ column.strip|safe|linebreaksbr }}
{% endif %}
</th>
{% else %}
<td>
{% if column.strip %}
{{ column.strip|safe|linebreaksbr }}
{% endif %}
</td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>

View File

@ -0,0 +1,24 @@
{% load i18n %}
<div class="field boolean_field widget-checkbox_input">
<label for="{{ attrs.id }}-handsontable-header">{% trans 'Row header' %}</label>
<div class="field-content">
<div class="input">
<input type="checkbox" id="{{ attrs.id }}-handsontable-header" name="handsontable-header"/>
</div>
<p class="help">{% trans 'Display the first row as a header.' %}</p>
</div>
</div>
<div class="field boolean_field widget-checkbox_input">
<label for="{{ attrs.id }}-handsontable-col-header">{% trans 'Column header' %}</label>
<div class="field-content">
<div class="input">
<input type="checkbox" id="{{ attrs.id }}-handsontable-col-header" name="handsontable-col-header"/>
</div>
<p class="help">{% trans 'Display the first column as a header.' %}</p>
</div>
</div>
<div id="{{ attrs.id }}-handsontable-container"></div>
{{ original_field_html }}