# -*- coding: utf-8 -*- """ Developed by Massimo Di Pierro, optional component of web2py, BSDv3 license. """ from __future__ import print_function import re import pickle import copy import json def quote(text): return str(text).replace('\\', '\\\\').replace("'", "\\'") class Node: def __init__(self, name, value, url='.', readonly=False, active=True, onchange=None, select=False, size=4, **kwarg): self.url = url self.name = name self.value = str(value) self.computed_value = '' self.incoming = {} self.outcoming = {} self.readonly = readonly self.active = active self.onchange = onchange self.size = size self.locked = False self.select = value if select and not isinstance(value, str) else False def xml(self): if self.select: selectAttributes = dict(_name=self.name,_id=self.name,_size=self.size, _onblur="ajax('%s/blur',['%s']);"%(self.url,self.name)) # _onkeyup="ajax('%s/keyup',['%s'], ':eval');"%(self.url,self.name), # _onfocus="ajax('%s/focus',['%s'], ':eval');"%(self.url,self.name), for k,v in selectAttributes.items(): self.select[k] = v return self.select.xml() else: return """ """ % (self.name, self.name, self.computed_value, self.size, self.url, self.name, self.url, self.name, self.url, self.name, (self.readonly and 'readonly ') or '') def __repr__(self): return '%s:%s' % (self.name, self.computed_value) class Sheet: """ Basic class for creating web spreadsheets New features: -dal spreadsheets: It receives a Rows object instance and presents the selected data in a cell per field basis (table rows are sheet rows as well) Input should be short extension data as numeric data or math expressions but can be anything supported by unicode. -row(), column() and matrix() class methods: These new methods allow to set bulk data sets without calling .cell() for each node Example:: # controller from gluon.contrib.spreadsheet import Sheet def callback(): return cache.ram('sheet1', lambda: None, None).process(request) def index(): # standard spreadsheet method sheet = cache.ram('sheet1', lambda: Sheet(10, 10, URL(r=request, f='callback')), 0) #sheet.cell('r0c3', value='=r0c0+r0c1+r0c2', readonly=True) return dict(sheet=sheet) def index(): # database spreadsheet method sheet = cache.ram('sheet1', lambda: Sheet(10, 10, URL(r=request, f='callback'), data=db(db.mydata).select()), 0) return dict(sheet=sheet) # view {{extend 'layout.html'}} {{=sheet}} or insert invidivual cells via {{=sheet.nodes['r0c0']}} Sheet stores a JavaScript w2p_spreadsheet_data object for retrieving data updates from the client. The data structure of the js object is as follows: # columns: a dict with colname, column index map # colnames: a dict with column index, colname map # id_columns: list with id columns # id_colnames: dict with id colname: column index map # cells: dict of "rncn": value pairs # modified: dict of modified cells for client-side Also, there is a class method Sheet.update(data) that processes the json data as sent by the client and returns a set of db modifications (see the method help for more details) client JavaScript objects: -var w2p_spreadsheet_data Stores cell updates by key and Used for updated cells control -var w2p_spreadsheet_update_button Stores the id of the update command Used for event binding (update click) var w2p_spreadsheet_update_result object attributes: modified - n updated records errors - n errors message - a message for feedback and errors Stores the ajax db update call returned stats and the db_callback string js Used after calling w2p_spreadsheet_update_db() -function w2p_spreadsheet_update_cell(a) Used for responding to normal cell events (encapsulates old behavior) -function w2p_spreadsheet_update_db_callback(result) Called after a background db update -function w2p_spreadsheet_update_db() Called for updating the database with client data First method: Sending data trough a form helper: (the data payload must be inserted in a form field before submission) -Applying db changes made client-side Example controller: ... # present a submit button with the spreadsheet form = SQLFORM.factory(Field("", "text", readable=False, writable=False, formname="")) # submit button label form.elements("input [type=submit]").attributes["_value"] = \ T("Update database") form.elements("textarea")[0].attributes["_style"] = "display: none;" w2p_spreadsheet_update_script = SCRIPT(''' jQuery( function(){ jQuery("").submit(function(){ jQuery("[name=]").val(JSON.stringify( w2p_spreadsheet_data) ); }); } ); ''') # retrieve changes if form.process().accepted: data = form.vars. changes = Sheet.updated(data) # Do db I/O: for table, rows in changes.iteritems(): for row, values in rows.iteritems(): db[table][row].update_record(**values) # the action view should expose {{=form}}, {{=sheet}}, {{=myscript}} return dict(form=form, sheet=sheet, myscript=w2p_spreadseet_update_script) Second method: Sending data updates with .ajax() -spreadsheet page's view: {{ =INPUT(_type="button", _value="update data", _id="w2p_spreadsheet_update_data") }} {{=SCRIPT(''' jQuery(function(){ jQuery("#w2p_spreadsheet_update_data").click( function(){ jQuery.ajax({url: "%s", type: "POST", data: {data: JSON.stringify(w2p_spreadsheet_data)} } ); } ); }); ''' % URL(c="default", f="modified"))}} -controller: modified function def modified(): data = request.vars.data changes = Sheet.updated(data) # (for db I/O see first method) return "ok" Third method: When creating a Sheet instance, pass a kwarg update_button=