`
xjk2131650
  • 浏览: 57144 次
  • 性别: Icon_minigender_1
  • 来自: 河北
社区版块
存档分类
最新评论

利用 Django 与 jQuery 来创建电子表格应用程序

阅读更多

 

本文描述了如何利用 jQuery、jQuery 插件、以及 Django 来实现基于 web 的电子表格。并不是为了与 Google Docs 进行竞争,而是要演示如果创建 “office” 风格的应用程序,并给出大量可用的 jQuery 插件与工具。我采用 SQLite/Python/Django 栈作为后端,您也可以通过很小的工作量,来实现到其他框架的端口,比如 Ruby on Rails。

项目依赖项

本文采用如下 Python 技术(见 参考资料 中的链接):

  • Python 2.5+
  • simplejson
  • Django 1.2.3

注意: Python 2.5 不包括 simplejson,但在 Python 后续的版本中包含。

要想避免获取所有 jQuery 依赖项过程中的麻烦,可通过 参考资料 中的链接来下载完整的演示。在前端,需要如下技术:

  • jQuery 1.4.3
  • jQuery UI 1.8.5
  • SlickGrid
  • jQuery JSON

所有的第三方库能处理大部分的工作量,特别是 SlickGrid。我选择 SlickGrid 是因为,它支持突出显示/选择单元格组 — 适用于优化单元格的数学操作与分析功能。它还支持在滚动时加载数据。还有很多优秀的 jQuery 网格插件可供使用,包括 Flexigrid、jQuery Grid、jqGridView、以及 Ingrid。此外,jQuery 项目已宣布提供官方 jQuery Grid 插件的计划。

电子表格规范

每个电子表格都包含单个工作簿,每个工作簿包含一个或多个数据表。当首次输入的字符是等号(=)时,表中的每个单元格应当执行算术运算。否则,输入的文本应保持原样。数据加载到 JSON 对象中,异步发送给后端,并保存到数据库中。电子表格将处理 Open、New、以及 Save 操作,并且工作簿的名称将出现在顶部的可编辑文本框内。

单击 Open 打开一个 jQuery UI 窗口,显示数据库中的现有工作簿。选择工作簿后,利用 Asynchronous JavaScript and XML(Ajax)来检索所存储的 JSON 数据,并呈现给网格。异步地将以 JSON 格式发送的网格数据存储到后端。New 操作会清除所有引用,并重新加载干净的工作簿。

最后,工作簿的表格将被分成不重复的 jQuery 选项卡。同任何其他电子表格一样,选项卡将在底部展示,并通过单击底部的按钮来动态增加。

项目结构

将所有的 CSS/JavaScript/images 放置到项目顶级目录中的 resources 文件夹中。Django 应用程序将包含名为 index.html 的模板,那只是一些标记,用于保持 HTML 语义与 JavaScript 代码语法。组件的生成,比如网格,是动态完成的。电子表格的定义包含在文件 spreadsheet.js 中。

创建 Django 后端

首先,通过执行以下命令来创建 Django 项目:

django-admin startproject spreadsheet


然后,cd 到新创建的项目中,通过调用以下内容来创建应用程序:

django-admin startapp spreadsheet_app


本文采用 SQLite3 来避免过多的数据库相关工作,但是,您可随意选择任何关系型数据库系统(RDBS)。修改 settings.py 文件来应用清单 1 中的代码。


清单 1. Django settings.py 文件

				
import os
APPLICATION_DIR = os.path.dirname( globals()[ '__file__' ] )

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3', 
        'NAME': 'db',                      
        'USER': '',                      
        'PASSWORD': '',                 
        'HOST': '',                    
        'PORT': '',                   
    }
}

MEDIA_ROOT = os.path.join( APPLICATION_DIR, 'resources' )
MEDIA_URL = 'http://localhost:8000/resources/'

ROOT_URLCONF = 'spreadsheet.urls'

TEMPLATE_DIRS = (
    os.path.join( APPLICATION_DIR, 'templates' ),
)

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'spreadsheet_app',
)


不必修改 settings.py 文件中的任何其他变量。现在需要配置 URL 映射。在本例中,仅需要两个映射:一个用于静态传送的文件,另一个用于指向索引。清单 2 展示了相关代码。


清单 2. urls.py 文件

				
from django.conf.urls.defaults import *
from django.conf import settings
import spreadsheet.spreadsheet_app.views as views

urlpatterns = patterns('',
    ( r'^resources/(?P<path>.*)$',
      'django.views.static.serve',
      { 'document_root': settings.MEDIA_ROOT } ),
    url( r'^spreadsheet_app/', views.index, name="index" ) ,
)


在项目的顶级目录中创建目录 resources,并创建 css、js、与 images 子目录。SlickGrid 之类的下载依赖项,以及用于应用程序的自定义 JavaScript 代码存储在此处。如果觉得麻烦,可以下载演示版并复制 resources 目录即可。

接下来,创建域模型(见清单 3)。该模型将仅包含 3 个字段:workbook_name、 sheet_name 与 data。 Django Object-Relational Mapper(ORM)会自动创建关键字段 id


清单 3. models.py 文件

				
# file: spreadsheet_app/models.py

from django.db import models

class Workbooks(models.Model):
    workbook_name = models.CharField(max_length=30)
    sheet_name = models.CharField(max_length=30)
    data = models.TextField()


其实,您并没有完全发挥 Django 的优势。您只是想在处理后端工作的同时提升 jQuery 前端的性能。

最后,创建索引视图。索引视图处理电子表格相关的创建/读取/更新操作。不必深入探讨索引视图的细节问题,清单 4 展示了如何处理进入的请求。


清单 4. 视图

				
# file:spreadsheet_app/views.py
from django.template.context import RequestContext
from spreadsheet.spreadsheet_app.models import Workbooks
import simplejson as json
from django.http import HttpResponse

def index(request):

    app_action = request.POST.get('app_action')
    posted_data = request.POST.get('json_data')

    if posted_data is not None and app_action == 'save':
        ...

    elif app_action == 'get_sheets':
        ...
  
    elif app_action == 'list':
        ...


import 完成后,可以看到索引视图接受了包含客户端所发送 post 数据的请求对象。可以得到两个参数:app_action 与posted_dataapp_action 参数说明客户端请求什么动作,比如创建新的工作表。posted_data 参数是客户端发送的,针对单个表的 JSON 数据。不同的动作通过 if 语句来处理;可以保存表,取得工作簿的所有表,或者取得数据库中工作簿的清单。

您将在后面看到索引视图的相关细节。此时,要在 spreadsheets_app 目录中增加名为 templates 的目录。在 templates 子目录中,增加文件 index.html,这是本项目唯一的模板。清单 5 展示了相关代码。


清单 5. 索引模板

				
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 
    "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
     <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"i>
     <title>Overly Simple Spreadsheet</title>
     <link rel="stylesheet" href="{{MEDIA_URL}}css/smoothness/jquery-ui-1.8.5.custom.css" 
           type="text/css" media="screen" charset="utf-8" />
     <link rel="stylesheet" href="{{MEDIA_URL}}css/slick.grid.css" type="text/css" 
           media="screen" charset="utf-8" />
     <link rel="stylesheet" href="{{MEDIA_URL}}css/examples.css" type="text/css" 
           media="screen" charset="utf-8" />
     <link rel="stylesheet" href="{{MEDIA_URL}}css/spreadsheet.css" type="text/css" 
           media="screen" charset="utf-8" />

     <script type="text/javascript" src="{{MEDIA_URL}}js/jquery-1.4.3.min.js"></script>
     <script type="text/javascript" src="{{MEDIA_URL}}js/jquery.json.js"></script>
     <script type="text/javascript" src="{{MEDIA_URL}}js/jquery-ui-1.8.5.custom.min.js">
         </script>
     <script type="text/javascript" src="{{MEDIA_URL}}js/jquery.event.drag-2.0.min.js">
         </script>
     <script type="text/javascript" src="{{MEDIA_URL}}js/ui/jquery.ui.tabs.js"></script>
     <script type="text/javascript" src="{{MEDIA_URL}}js/slick.editors.js"></script>
     <script type="text/javascript" src="{{MEDIA_URL}}js/slick.grid.js"></script>
     <script type="text/javascript" src="{{MEDIA_URL}}js/spreadsheet.js"></script>

  </headi>
  <body>
  </body>
</html>


如您所见,在 HTML 中没有控制逻辑或样式 — 仅有标记。事实上,甚至主体中也没有任何元素。这些都通过 JavaScript 代码来动态生成,也就是通过方法调用来增加或删除元素。

spreadsheet.js 概览

加载完成后,电子表格呈现 UI 并加载单个选项卡。清单 6 展示了用于 render_ui 的代码。


清单 6. spreadsheet.js 中的 render_ui 方法

				
function render_ui(){
    insert_menu_markup();
    insert_grid_markup();
    make_grid_component();
    add_newtab_button();
    insert_open_dialog_markup();
    make_open_dialog();
}


我们来看一下每个方法的工作方式,从 insert_menu_markup 开始。该方法仅向顶部菜单增加 HTML 代码,如清单 7 所示。该菜单由三个按钮 — new、open、和 save — 以及用于展示并输入工作簿名称的文本字段组成。可采用 jQuery prepend 来确定何时增加了标记,它将作为第一个元素插入到主体中。


清单 7. 用于生成菜单标记的方法

				
// OK, it's not really a menu...yet :-)
function insert_menu_markup(){
    $("body").prepend(
       '<input type="text" id="workbook_name" name="workbook_name" value="">');
    $("body").prepend('<input id="save" type="button" value="save"/>');
    $("body").prepend('<input id="open" type="button" value="open"/>');
    $("body").prepend('<input id="new" type="button" value="new"/>');
}


图 1 展示了一个非常简单的菜单,它仅包含一些按钮与文本框。


图 1. 菜单
截屏包含三个选项卡:new、open、和 save,以及 invoices_2010 的名称。 

insert_grid_markup 方法与 insert_menu_markup 类似,只是这一次采用了 append 方法。jQuery UI 请求 <div> 中的一个清单来生成选项卡组件:

function insert_grid_markup(){
    var workbook_widget = '<div id="tabs" class="tabs-bottom"><ul><li></li></ul></div>';  
    $('body').append(workbook_widget);
}


此时,通过调用方法 make_grid_component 来使选项卡生效。清单 8 展示了相关代码。


清单 8. 将网格 div 转换为选项卡

				
function make_grid_component(){
    $("#tabs").tabs();
    $(".tabs-bottom .ui-tabs-nav, .tabs-bottom .ui-tabs_nav > *")
    .removeClass("ui-corner-all ui-corner-top")
    .addClass("ui-corner-bottom");
}


可采用 jQuery id 选择器来获取到选项卡 <div> 的引用,然后调用 tabs() 方法将 <div> 转换成选项卡组件。默认 CSS 类 ui-corner-top 被移除,然后增加了类 ui-corner-bottom,因此选项卡出现在底部,如图 2 所示。


图 2. 工作簿选项卡
三个选项卡名为 Sheet 0、Sheet 1、Sheet 2。 

该组件是所有数据网格的容器。此时,在选项卡的下面增加按钮,它将会在每次单击时动态增加选项卡。可通过方法add_newtab_button 来完成此任务:

function add_newtab_button(){
    $('body').append('<input id="new_tab_button" type="button" value="+"/>');
}


最后一个可视化组件是创建 Open 窗体,可通过清单 9 中展示的 insert_open_dialog_markup 方法来创建。同其他插入标记方法一样,它创建包含标记信息的字符串,并将其附加到主体中。


清单 9. 用于生成对话框标记的方法

				
function insert_open_dialog_markup(){
    var dlg = '<div id="dialog_form" title="Open">' +
    '<div id="dialog_form" title="Open">' +
    '<p>Select an archive.</p><form>'+
    '<select id="workbook_list" name="workbook_list">' +
    '</select></form></div>';
    $("body").append(dlg);
}


此时已有了用于窗体的标记,可通过清单 10 中展示的 make_open_dialog 方法来增加其功能 — render_ui 中的最后一个方法。通过调用 .dialog() 方法,并向其传递参数 autoOpen:false,只有当表单被明确地打开时,它才会在 web 页面中展示。对话框表单包含选择清单,该清单中包含了所加载工作簿的名称。


清单 10. 使 Open 窗体生效

				
function make_open_dialog(){
    $('#dialog_form').dialog({
        autoOpen: false,
        modal: true,
        buttons: {
            "OK":function(){
                selected_wb = $('option:selected').attr('value');
                $(this).dialog('close');

                // remove grid, existing forms, and recreate
                $('body').html('');
                render_ui();

                // load grids and create forms with invisible inputs
                load_sheets(selected_wb);

                // place workbook name in text field
                $('#workbook_name').val(selected_wb);
            },
            "Cancel":function(){
                $(this).dialog('close');
            }
        }
    });
}


再次使用 jQuery 选择器来对 dialog_form 进行处理并调用 dialog() 方法,该方法将 html 元素转换为 jQuery 窗体。Open 窗体是个模式,不会随着页面加载而打开。它还包含两个按钮 — OK 与 Cancel。后者的功能是关闭窗体。OK 函数在选择清单中找到所选项目,并获取其值。然后它会关闭窗体,移除主体中的任何子元素,重新呈现 GUI 组件,并加载表格(术语称为 SlickGrids)。正如前面提到的,因为标记的生成全在方法中实现,所以增加与移除这些组件已无关紧要。

呈现 UI 基础之后,现在编写方法来打开具有网格的选项卡。继续采用这种自上而下的方法,清单 11 展示了 openTab 方法,这是本应用程序的关键功能。

注意: 应用程序中的每个表都包含一个 ID,它符合一个简单的命名约定:tabs_ 后面加上表中的选项卡号。


清单 11. 向工作簿增加新选项卡的方法

				
function openTab(sheet_id) {
  numberOfTabs = $("#tabs").tabs("length");
  tab_name = "tabs_" + numberOfTabs;

  $("#tabs").tabs("add","#" + tab_name,"Sheet " + numberOfTabs, numberOfTabs);
  $("#" + tab_name).css("display","block");
  $("#tabs").tabs("select",numberOfTabs);

  $('#'+tab_name ).css('height','80%');
  $('#'+tab_name ).css('width','95%');
  $('#'+tab_name ).css('float','left');
  add_grid(tab_name, numberOfTabs);

  // add form for saving this tabs data
  if(!sheet_id){
   $('body').append(
   '<form method="post" action="?" id="'+tab_name +'_form" name="'+tab_name+'_form">'+
   '<input type="hidden" id="data'+numberOfTabs+'" name="data'+numberOfTabs+'" value="">'+
   '<input type="hidden" id="sheet_id" name="sheet_id" value="">' +
   '</form>');
  } else {
   $('body').append(
   '<form method="post" action="?" id="'+tab_name +'_form" name="'+tab_name+'_form">' +
  '<input type="hidden" id="data'+numberOfTabs +'" name="data'+numberOfTabs+'" value="">'+
   '<input type="hidden" id="sheet_id" name="sheet_id" value="'+sheet_id+'">' +
   '</form>');
  }
}


如果您有兴趣,那么可以选择比较优雅的方法来编写标记字符串,那就是采用一些 JavaScript 模板,而不是将字符串连接在一起。唯一的表 ID 保存在隐藏的 input 元素中。为了保存 JSON 数据,还增加了另一个隐藏元素。这一隐藏 input 遵循一个简单的命名规则 — 数据加上选项卡号。另一点需要注意的是,新增加的选项卡具有在实际增加 SlickGrid 之前修改的 CSS 属性。如果不这样做,那么网格将无法正确呈现。

方法 openTab 调用方法 add_grid,它完成 SlickGrid 对象的一个真实实例。清单 12 显示出应用程序任务繁重。可创建两个 JavaScript 对象 — workbook 与 grid_referencesWorkbook 对象包含到当前 workbook 对象的引用,而 grid_references 包含到每个 SlickGrid 对象的引用。add_grid 方法接受两个参数:网格名与网格号。

在列定义中,需要将 16 列作为默认值 — a 到 p — 采用 TextCellEditor 来在一个 SlickGrid 示例中提供。完成列定义后,要向 SlickGrid 提供参数定义。单元格可编辑、可调整、可选择。要注意确保 asyncEditorLoading 选项已设置为 True ,这用于在网格上实现 Ajax 操作。


清单 12. 为应用程序增加 SlickGrids

				
var workbook = {};
var grid_references = {};

function add_grid(grid_name, gridNumber){
    var grid;
    var current_cell = null;

    // column definitions
    var columns = [
         {id:"row", name:"#", field:"num", cssClass:"cell-selection", width:40, 
         cannotTriggerInsert:true, resizable:false, unselectable:true },
         {id:"a", name:"a", field:"a", width:70, cssClass:"cell-title", 
              editor:TextCellEditor},
         {id:"b", name:"b", field:"b", width:70, cssClass:"cell-title", 
              editor:TextCellEditor},
         {id:"c", name:"c", field:"c", width:70, cssClass:"cell-title", 
              editor:TextCellEditor},
         {id:"d", name:"d", field:"d", width:70, cssClass:"cell-title", 
              editor:TextCellEditor},
         {id:"e", name:"e", field:"e", width:70, cssClass:"cell-title", 
              editor:TextCellEditor},
         {id:"f", name:"f", field:"f", width:70, cssClass:"cell-title", 
              editor:TextCellEditor},
         {id:"g", name:"g", field:"g", width:70, cssClass:"cell-title", 
              editor:TextCellEditor},
         {id:"h", name:"h", field:"h", width:70, cssClass:"cell-title", 
              editor:TextCellEditor},
         {id:"i", name:"i", field:"i", width:70, cssClass:"cell-title", 
              editor:TextCellEditor},
         {id:"j", name:"j", field:"j", width:70, cssClass:"cell-title", 
              editor:TextCellEditor},
         {id:"k", name:"k", field:"k", width:70, cssClass:"cell-title", 
              editor:TextCellEditor},
         {id:"l", name:"l", field:"l", width:70, cssClass:"cell-title", 
              editor:TextCellEditor},
         {id:"m", name:"m", field:"m", width:70, cssClass:"cell-title", 
              editor:TextCellEditor},
         {id:"n", name:"n", field:"n", width:70, cssClass:"cell-title", 
              editor:TextCellEditor},
         {id:"o", name:"o", field:"o", width:70, cssClass:"cell-title", 
              editor:TextCellEditor},
         {id:"p", name:"p", field:"p", width:70, cssClass:"cell-title", 
              editor:TextCellEditor},
    ];

    var options = {
          editable: true,
          autoEdit: true,
          enableAddRow: true,
          enableCellNavigation: true,
          enableCellRangeSelection : true,
          asyncEditorLoading: true,
          multiSelect: true,
          leaveSpaceForNewRows : true,
          rerenderOnResize : true
    };

    eval("var data" + gridNumber + " = [];");
    workbook["data" + gridNumber] = [];
    for( var i=0; i < 100 ; i++ ){
        var d = (workbook["data"+gridNumber][i] = {});
        d["num"] = i;
        d["value"] = "";
    }

    grid = new Slick.Grid($("#"+grid_name),workbook["data"+gridNumber], columns, options);
...


以 “类似黑客” 的方式使用 eval 语句,动态创建变量名。变量 data 将以空字符串数据方式加载,并完成 SlickGrid 实例的创建。

此时,针对事件将 attach 增加到网格,如清单 13 所示。当 onCurrentCellChanged 事件发生时,它获取网格中的数据并更新单元格内容。在完成单元格编辑之前,调用 onBeforeCellEditorDestroy 事件。然后事件触发,将会获取单元格数据,但这一次要确定第一个字符是等号。如果是,则采用 eval 方法来计算输入的表达式。

注意: 不要在生产环境中使用此代码。它会使您的系统完全开放给各类注入攻击。始终要注意净化您的数据。

最后,到网格的引用将保存以供其他方法使用。


清单 13. 向网格增加事件处理

				
// file: resources/js/spreadsheet.js continued
    // Events
    grid.onCurrentCellChanged = function(){
        d = grid.getData();
        row  = grid.getCurrentCell().row;
        cell = grid.getCurrentCell().cell;
        this_cell_data = d[row][grid.getColumns()[cell].field];
    };

    grid.onBeforeCellEditorDestroy = function(){
        d = grid.getData();
        row  = grid.getCurrentCell().row;
        cell = grid.getCurrentCell().cell;
        this_cell_data = d[row][grid.getColumns()[cell].field];

        if(this_cell_data && this_cell_data[0] === "="){
            // evaluate JavaScript expression, don't use
            // in production!!!!
            eval("var result = " + this_cell_data.substring(1));
            d[row][grid.getColumns()[cell].field] = result;
        }
    };
    grid_references[grid_name] = grid;
};


增加菜单事件

根据前面的规范,您的电子表格必须创建新的工作簿,以及保存并打开现有工作簿。现在实现新工作簿函数,这是三个任务中最简单的一个。在此处,所要做的就是毁掉 UI 以及任何引用,然后重新创建,如清单 14 所示。


清单 14. 创建新工作簿

				
$('#new').live('click', function(){
    // delete any existing references
    workbook = {};
    grid_references = {};

    // remove grid, existing forms, and recreate
    $('body').html('');

    // recreate
    render_ui();
    openTab();
});


“save” 函数有点复杂。可采用 jQuery 选择器来获取名称属性以 data 开头的每个元素,然后采用 each 方法来循环处理结果集。需要关注的重点是采用用于 “线上” 发送的插件 jquery.json 来将网格数据编码成 JSON 格式。采用 $.post 方法来异步发送数据。传递给索引视图的参数是需要执行的动作(在本例中为 save,如清单 15 所示)、唯一的表 ID、工作簿名、以及 JSON 格式的网格数据。


清单 15. 保存网格数据

				
$('#save').live('click',function(){
    // Do a foreach on all the grids. The ^= operator gets all
    // the inputs with a name attribute that begins with data
    $("[name^='data']").each(function(index, value){
        var data_index = "data"+index;
        var sheet_id = $('#tabs_'+index+'_form').find('#sheet_id').val();
        if(sheet_id !== ''){
          sheet_id = eval(sheet_id);
        }

        // convenience variable for readability
        var data2post  = $.JSON.encode(workbook[data_index]);
        $("#"+data_index).val(data2post);

        $.post( '{% url index %}', {'app_action':'save', 'sheet_id': sheet_id,
                'workbook_name':$('#workbook_name').val(),
                'sheet':data_index, 'json_data':data2post});
    });
});


如果重新调用,那么打开操作需要 load_sheets 方法。现在就添加该方法(见清单 16)。此方法必须调用后端,请求表,并将工作簿的所有数据传递给方法。然后将数据加载到相应的 SlickGrid 对象中。注意,在将数据插入到对象之前,必须利用 decode 方法来反序列化 JSON 数据。然后重新呈现网格。


清单 16. 将数据加载到网格中

				
function load_sheets(workbook_name){
    $('#workbook_list').load('{% url index %}', 
        {'app_action':'get_sheets','workbook_name':workbook_name}, 
        function(sheets, resp, t){
        sheets = $.JSON.decode(sheets);

        workbook = {}; // reset
        grid_references = {};
        $.each(sheets, function(index, value){

            // add to workbook object
            var sheet_id = value["sheet_id"];
            openTab(sheet_id);

            // By calling eval, we translate value from
            // a string to a JavaScript object
            workbook[index] = eval(value["data"]);

            // insert data into hidden
            $("#data"+index).attr('value', workbook[index]);
            grid_references["tabs_"+index].setData(workbook[index]);
            grid_references["tabs_"+index].render();

        });
    });
}


最后,需要实现 open 函数。再次进行异步调用,此次发送 list 动作,然后再次反序列化 JSON 发送的数据。接下来,利用数据库中所有工作簿的名称来更新 select 清单。对话框表单将打开。清单 17 展示了相关代码。


清单 17. 打开现有工作簿

				
$('#open').live('click',function(){
    // load is used for doing asynchronous loading of data
    $('#workbook_list').load('{% url index %}', {'app_action':'list'}, 
        function(workbooks,success){
        workbooks = $.JSON.decode(workbooks);
        $.each(workbooks, function(index, value){
            $('#workbook_list').append(
              '<option value="'+ value +'">'+value +'*lt;/option>');
        });
    });

    $('#dialog_form').dialog('open');
});


重新访问索引视图

此时已完成客户端 post 编码,可以看到完整的索引视图了。清单 18 展示了除导入之外的完整索引视图。

想要序列化(反序列化)JSON 代码,可采用 simplejson 模块与命令转储来对其进行序列化并为反转进行加载。对于 Save 动作,如果 ID 是针对某个表的,那么该表将被更新。否则将创建新表。

与此相反,获取表动作将创建具有特定工作簿的所有表的 JSON 对象。它利用命令 filter(),针对某个工作簿来检索所有表(QuerySet 对象)。这等效于 select * from spreadsheet_app_workbooks,其中 workbook_name = wb_name"。检索完这些设置后,再次将其转换为 JSON 格式并发回客户端。

List 也采用 Django ORM,但此次与 values() 方法一起使用。在这里,您正在指示 Django “获取 workbook_name 列”。通过在QuerySet 对象上调用 distinct 方法,等于在说明,不想要任何重复。可再次采用清单解析,从结果创建清单。对于 get_sheets 和list,必须返回 HttpResponse 对象,以便 jQuery 处理 Ajax 响应。


清单 18. 最终视图

				
def index(request):
    template = 'index.html'

    app_action = request.POST.get('app_action')
    posted_data = request.POST.get('json_data')

    if posted_data is not None and app_action == 'save':
        this_sheet = request.POST.get("sheet")
        this_workbook = request.POST.get("workbook_name")
        sheet_id = request.POST.get("sheet_id")

        posted_data = json.dumps(posted_data)

        if(sheet_id):
            wb = Workbooks(id=sheet_id, workbook_name=this_workbook, 
                   sheet_name=this_sheet, data=posted_data)
        else:
            wb = Workbooks(workbook_name=this_workbook, 
                   sheet_name=this_sheet, data=posted_data)
        wb.save()

    elif app_action == 'get_sheets':
        wb_name = request.POST.get('workbook_name')
        sheets = Workbooks.objects.filter(workbook_name=wb_name)

        # use list comprehension to create python list which is like a JSON object
        sheets = [{ "sheet_id":i.id, "workbook_name": i.workbook_name.encode("utf-8"),
                    "sheet_name": i.sheet_name.encode("utf-8"), 
                    "data": json.loads(i.data.encode("utf-8"))} for i in sheets ]

        # dumps -> serialize to JSON
        sheets = json.dumps(sheets)

        return HttpResponse( sheets, mimetype='application/javascript' )

    elif app_action == 'list':
        workbooks = Workbooks.objects.values('workbook_name').distinct()

        # use list comprehension to make a list of just the work books names
        workbooks = [ i['workbook_name'] for i in workbooks ]

        # encode into json format before sending to page
        workbooks = json.dumps(workbooks)

        # We need to return an HttpResponse object in order to complete
        # the ajax call
        return HttpResponse( workbooks, mimetype='application/javascript' )

    return render_to_response(template, {},
           context_instance = RequestContext( request ))


完成网格

此时已经定义了所有的 JavaScript 方法,可以利用两个方法调用来生成应用程序了,如清单 19 所示。


清单 19. 页面加载时需要执行的代码

				
$(document).ready(function(){
    render_ui();
    openTab();
});


完成页面加载后,UI 呈现出来,新的选项卡插入到了工作簿中。

现在可以通过在命令行运行 python manage syncdb 创建数据库,来展示电子表格应用程序的功能了。当操作完成后,执行命令python manage.py runserver 并导航到 http://localhost:8000/spreadsheet_app。应当能看到最终版本,如图 3 所示。


图 3. 完整电子表格应用程序
截图包含 7 列,14 行,并填有客户端信息。 

可向项目添加很多内容,比如:

  • 图表
  • 针对单元格中输入的公式的、更安全、功能更好的解析器
  • 导出为其他格式,比如 Microsoft® Office Excel
  • 采用附加 jQuery 插件的真实菜单

结束语

虽然这一 web 应用程序还不能用于生产环境,但它演示了如何将多项技术组合在一起。采用 JavaScript、语义 HTML、JSON 对象来实现服务器数异步传输据,最重要的是,有很多现成的 jQuery 插件可供使用,这就大大简化了您的工作。

分享到:
评论

相关推荐

    e-commerce:这是电子商务迷你项目。 在这个项目中,我实现了基于电子商务的Web应用程序的最小实现。 在这个网站上,用户可以将项目添加到购物车,通过使用订单,搜索项目等来跟踪订单

    使用Django的工具创建框架网站和应用程序。 启动和停止开发服务器。 创建模型来表示应用程序的数据。 使用Django管理网站来填充网站的数据。 创建视图以响应不同的请求来检索特定数据,并创建模板以将数据呈现...

    Luckysheet在线表格 v2.1.13.zip

    Luckysheet是一款强大的在线表格应用,它提供了丰富的功能,让用户可以在网页上进行类似于Microsoft Excel的表格操作。在v2.1.13版本中,它可能包含了一系列的更新和优化,旨在提升用户体验和性能。 首先,让我们...

    Kuki-shop:使用PythonDjango,Bootstrap和React构建的中型企业电子商务网站。 仍在开发中

    主要特点结帐表格购物车部分用户认证异步任务假脱机纺织品类别无需注册即可作为客人购物技术栈Django的jQuery和Ajax 引导程序Allauth建立克隆项目创建并启动虚拟环境安装项目依赖项pip install -r requirements.txt ...

    表格 各行换色

    标题“表格各行换色”指的是在电子表格或者网页中,为增强数据可读性,每行或每列采用不同颜色交替显示的一种技巧。这通常应用于大数据量的表格中,使得用户能更容易地区分不同的行,避免视觉疲劳。下面将详细阐述...

    Python+django实现文件下载

    对于某些文件类型,如Word文档和Excel表格,浏览器通常会直接弹出下载对话框。然而,对于图片、文本文件等,浏览器可能会尝试直接显示而不是下载,这可能不是我们期望的行为。 ```html 下载文件 ``` 这里的`file_...

    django-steam-tc-api:Steam交易卡API

    Django Rest框架django-cors-headers由Otto Yiu撰写请求-进行HTTP通话网站前端具有以下依赖性: 引导程序jQuery的JS-旋转器数据表JS-自然排序money.js 2015年9月27日:Steam卡纸关闭: 自从我创建SteamCardSheet...

    hotel:一个网络应用程序,使用户可以预订旅馆房间或旅行

    在后端,"hotel"应用可能采用了服务器端技术,如Node.js、Python的Django或Flask框架,或者PHP的Laravel等,来处理用户的预订请求,与数据库交互,验证用户信息,以及执行其他业务逻辑。数据库管理系统,如MySQL、...

    luckysheet,python后端,javascript前端

    Luckysheet是一款开源的Web电子表格组件,其设计理念是提供与Microsoft Excel类似的操作体验,但完全基于浏览器环境。用户无需安装任何桌面应用,只需通过浏览器就能进行数据的编辑、计算和展示,这大大提高了数据...

    每页显示固定行数

    这种技术在各种应用程序,尤其是数据分析工具、数据库管理系统和电子表格软件中广泛使用。下面我们将深入探讨这个主题。 在网页开发中,实现“每页显示固定行数”的方法多种多样,常见的有前端处理和后端处理两种...

    Python库 | lino-xl-18.12.1.tar.gz

    在企业环境中,Excel表格常常用于存储和分析大量数据。Lino-xl提供了对Excel文件的读写支持,使得开发者可以方便地在Python程序中操作Excel数据。它可能包含了对pandas库的集成,pandas是Python数据分析领域的一个...

    launch-with-code:使用代码启动

    Django 3.2和讲座代码引导程序姜戈应用程序使用表格与南方改变模式获取用户 IP 地址参考 ID 分享页面跟踪份额的中间件使用外键保存共享分享页面 Pt 2在 Django 中加载静态文件更新设计分享页面 Pt 3将 JQuery 用于 ...

    px-acquire

    下面我们将深入探讨与之相关的Python编程知识、数据处理和流式应用程序设计。 首先,Python是当前最流行的数据科学和Web开发语言之一,因为它具有丰富的库支持和简洁的语法。在"px-acquire"中,Python可能被用来...

    Admin後台管理模板26套.rar

    2. **WOW后台管理网站模板.rar**:“WOW”可能指的是一个特效库,用于创建引人注目的动画效果,提升用户体验。它可能结合HTML5和CSS3实现,同时也可能利用JavaScript或jQuery增强交互性。 3. **虚拟货币交易后台...

    codes

    - 有可能采用了响应式设计,利用Bootstrap或自定义CSS媒体查询来适应不同设备。 2. **项目8:瓢城杂货店** - 这可能是一个电子商务平台,涉及的技术可能包括PHP与Laravel框架,或者Java与Spring Boot。 - 使用...

    json-to-csv.github.io:json-to-csv.com

    这种转换在处理大量数据时尤其有用,因为CSV格式普遍被各种数据分析软件和电子表格程序所支持。 JSON是一种轻量级的数据交换格式,它基于JavaScript编程语言的子集,采用完全独立于语言的文本格式来存储和表示数据...

    省市区三级联动

    在中国的Web应用开发中,"省市区三级联动"是一个常见的功能需求,特别是在电子商务、物流配送、用户信息填写等场景中。这个功能涉及到地理信息的层级结构,即省份、城市(地级市)和区县(区/县级市),通过联动...

    samarasingheSuper:萨马拉辛赫超级市场

    "萨马拉辛赫超级市场"项目,标记为#J_N_Super,主要涉及HTML技术,这是一个可能用于构建在线超市或电子商务平台的Web应用程序。在这个项目中,`samarasingheSuper-master`作为压缩包的主目录,暗示了这是一个源代码...

    购物车

    在实际开发中,为了实现更复杂的功能,如库存检查、商品推荐、优惠券应用等,通常会结合CSS(层叠样式表)和JavaScript(或其库如jQuery)来增强用户体验和交互性。此外,服务器端技术如PHP、Node.js或Python的...

    超漂亮的Bootstrap 富文本编辑器summernote

    Summernote可以广泛应用于需要富文本编辑功能的Web应用中,例如内容管理系统、论坛、博客、电子商务网站的商品描述编辑等。它提供的简单操作和丰富的自定义选项使得它成为前端开发者的优选编辑器之一。 ### 知识点...

    1000+常用Python库大全.docx

    【Python库大全】这篇文档汇总了1000多个Python常用库,涵盖了多个领域的应用,如文本处理、文件处理、图像处理、游戏和多媒体、大数据和科学计算、人工智能和机器学习、系统与命令行、数据库、网络、Web框架、安全...

Global site tag (gtag.js) - Google Analytics