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:
parent
4b7d691908
commit
311c2da421
100
wagtail/contrib/table_block/__init__.py
Normal file
100
wagtail/contrib/table_block/__init__.py
Normal 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'
|
15
wagtail/contrib/table_block/static/table_block/css/vendor/handsontable-0.18.0.full.min.css
vendored
Normal file
15
wagtail/contrib/table_block/static/table_block/css/vendor/handsontable-0.18.0.full.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
97
wagtail/contrib/table_block/static/table_block/js/table.js
Normal file
97
wagtail/contrib/table_block/static/table_block/js/table.js
Normal 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();
|
||||
});
|
||||
}
|
||||
}
|
44
wagtail/contrib/table_block/static/table_block/js/vendor/handsontable-0.18.0.full.min.js
vendored
Normal file
44
wagtail/contrib/table_block/static/table_block/js/vendor/handsontable-0.18.0.full.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -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>
|
@ -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 }}
|
||||
|
Loading…
Reference in New Issue
Block a user