Node.js 切近实战 之Excel在线(文件&文件组)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Node.js 切近实战 之Excel在线(文件&文件组)相关的知识,希望对你有一定的参考价值。

最近西安的天气真他妈的热,感觉还是青海的天气美,最高温28度。上周逛了青海湖,感觉还是意犹未尽,其实我还是很喜欢去一趟西藏的,但是考虑到花费也没人陪我,我暂时放弃这个念头。计划去一下重庆或者甘南,也许是现实的。

OK,废话不多说,今天我们来看一下Excel在线部分的文件和文件组。首先我们来看一下页面,调一下胃口。俗话说无图无真相,先看图。

技术分享

没错,还是Telerik Kendo UI,其实我面试的时候当听到别人说自己用的是EasyUI和ExtJs的时候,我就不那么上心,但是如果有人用的是Kendo UI for html5&JS的话,我就会多关注一点。

先看一下页面整体代码。

技术分享

很简单,还是BootStrap布局,jade模板。注意下最底下的css样式,在jade模板中,如果想要在页面定义css,就要像上面这样写。注意这里我们引用了一个部分页,popup.jade,里面其实就是第一幅图里面New,Rename等按钮弹出的modal页。

技术分享

OK,首先我们看到的是左边的树,这个树叫kendoTreeView,我们来看一下这个树是怎么生成的。

var url = "/filegroup/list/" + userObj.UserID;
var dataSource = new kendo.data.HierarchicalDataSource({
        transport: {
            read: {
                url: url,
                dataType: "json"
            }
        },
        schema: {
            model: {
                id: "_id",
                children: "subgroup",
                expanded: true,
                hasChildren: function (node) {
                    return (node.subgroup && node.subgroup.length > 0);
                },
                spriteCssClass: "folder"
            }
        }
});
    
$("#treeview-filegroup").kendoTreeView({
        dataSource: dataSource,
        dataTextField: ["filecount"],
        dataValueField: ["_id"],
        change: function (e) {
            var tree = e.sender;
            selNode = tree.select();
            var data = tree.dataItem(selNode);
            
            if (data._id) {
                selGroupId = data._id;
                $("#chk_all").prop(‘checked‘, false);
                getFilelist(data._id);
            }
        }
    });

OK,这段代码就是生成树的代码,注意这里的dataSource,当页面加载以后,会请求url,filegroup/list/{0},我们来看一下后台这个api。

router.get(‘/filegroup/list/:userId‘, fileRoutes.fileGroupList);

再看一下fileGroupList方法。

exports.fileGroupList = function (req, res) {
    fileGroupModel.find({ ‘userid‘: req.params.userId }, null, { sort: { name: 1 } } , function (error, fileGroup) {
        res.json(fileGroup);
    });
}

其实就是根据传入的userid查询了一下fileGroup Collection,查出来后,注意这里的schema,它的model定义树节点id是我们mongodb的主键,children是subgroup(上节讲过group和subgroup的model定义,不明白的去上节看),hasChildrenC返回是否有子节点。spriteCssClass设置父节点样式,注意我们第一幅图定义的页面样式就用在这里。OK,接下里我们看树的change事件,当有选中的节点时,将右边列表表头的全选复选框uncheck,并根据选中的_id去mongodb查询group下面的数据,我们来看一下getFilelist方法。

function getFilelist(groupId) {
    if (!groupId) return;
    
    $.get("/filegroup/" + groupId, function (result) {
        var grid = $("#file_list").data("kendoGrid");
        if (result) {
            var dataSource = new kendo.data.DataSource({
                pageSize: 10,
                data: result,
                schema: {
                    parse: function (response) {
                        $.each(response, function (idx, elem) {
                            if (elem.createdate && typeof elem.createdate === "string") {
                                elem.createdate = kendo.parseDate(elem.createdate, "yyyy-MM-ddTHH:mm:ss.fffZ");
                            }
                            
                            if (elem.lasteditdate && typeof elem.lasteditdate === "string") {
                                elem.lasteditdate = kendo.parseDate(elem.lasteditdate, "yyyy-MM-ddTHH:mm:ss.fffZ");
                            }
                        });
                        return response;
                    }
                }
            });
            
            grid.setDataSource(dataSource);
        }
        else {
            grid.dataSource.data([]);
        }
    });
}

直接调用rest api filegroup/{0}查询数据,得到结果以后,构造kendo Grid的dataSource,对日期进行格式化。

router.get(‘/filegroup/:id‘, fileRoutes.fileGroup);
exports.fileGroup = function (req, res) {
    var groupId = req.params.id;
    fileGroupModel.findById(groupId).populate(‘file‘).exec(function (error, doc) {
        if (!doc || doc.length == 0) {
            fileGroupModel.findOne({ ‘subgroup._id‘: groupId })
            .populate(‘subgroup‘)
            .populate(‘subgroup.file‘)
            .exec(function (error, docs) {
                if (docs) {
                    var subGroupIndex = -1;
                    docs.subgroup.forEach(function (element, index, arra) {
                        if (subGroupIndex > -1) return;
                        
                        if (element._id == groupId) {
                            subGroupIndex = index;
                        }
                    });
                    
                    if (subGroupIndex > -1) {
                        res.json(docs.subgroup[subGroupIndex].file);
                    }
                }
            });
        }
        else {
            res.json(doc.file);
        }
    });
}

这里要注意,首先我们也不知道这里传入的是groupId还是subgroupId,所以我们先拿groupId查询,如果查到了,就返回数据,如果没查到,就拿_id去查subgroup,查询subgroup,注意这里要使用subgroup._id作为查询条件,因为subgroup是嵌入在group中的,是一个整体。查完之后注意这里的两个populate,如果没有populate的话,意味着这些嵌入的subgroup以及引用的file都不会被包含在查询结果中,我们来看一下查询的结果,在后台加一句console.log(docs)即可。有两个subgroup,两个下面都有文件。

技术分享

我们用robomongo也许看的更清晰一些,两个group,一个下面有7个文件,一个有2个文件。

技术分享

此时这个结果的话,我们还得找到我们页面传递的_id对应的subgroup下面的file。所以在代码中又有了循环匹配id确定subgroup索引下标的过程,找到后,直接根据索引拿出file。是不是很麻烦,如果你有什么好的办法,可以给我留言。

接下来我们来看一下kendo grid,首先看一下这个全选。

$("#chk_all").click(function () {
    var isChecked = $(this).prop("checked");
    $("#file_list table:eq(1)").find("tr").each(function () {
        $(this).children("td:first").find("input[type=‘checkbox‘]").first().prop(‘checked‘, isChecked);
    });
});

看起来很简单,找到第一个table找到所有tr,再找到第一个td,锁定checkbox让它选中或者不选中。

$("#file_list").kendoGrid({
        scrollable: true,
        selectable: true,
        allowCopy: true,
        resizable: false,
        sortable: true,
        pageable: {
            refresh: true,
            pageSizes: [10, 20, 50, 100],
            buttonCount: 5
        },
        toolbar: [
            { name: ‘share‘, imageClass: ‘glyphicon glyphicon-share-alt‘ },
            { name: ‘unshare‘, imageClass: ‘glyphicon glyphicon-lock‘ },
            { name: ‘batch_delete‘, text: "Delete" , imageClass: ‘glyphicon glyphicon-trash‘ }, 
            { name: ‘export‘, imageClass: ‘k-icon k-i-excel‘ }],
        columns: [{
                template: "<div class=‘center-align-text‘>" +
                    "<input id=‘chkId_#=_id#‘ type=‘checkbox‘ class=‘k-checkbox‘ value=‘#=_id#‘ onclick=‘chkHeader_click‘/>" 
                    + "<label class=‘k-checkbox-label‘ for=‘chkId_#=_id#‘></label></div>",
                field: "",
                title: "<div class=‘center-align-text‘>" +
                    "<input type=‘checkbox‘ class=‘k-checkbox‘ id=‘chk_all‘/>" 
                    + "<label class=‘k-checkbox-label‘ for=‘chk_all‘></label></div>",
                width: 40
            },
            {
                field: "fullname", 
                title: "File Name"
            }, {
                field: "isshared", 
                title: "Shared" ,
                width: 85, 
                template: "<div><input type=‘checkbox‘ class=‘k-checkbox‘ value=‘#=isshared#‘ #=isshared ? \"checked=‘checked‘\":\"\" # />" 
                + "<label class=‘k-checkbox-label‘></label></div>",
                sortable: false
            },
            {
                command: [
                    {
                        name: "preview",
                        text: "", 
                        imageClass: ‘glyphicon glyphicon-search‘,
                        click: showDetails
                    }, {
                        name: "delete",
                        text: "", 
                        imageClass: ‘glyphicon glyphicon-trash‘,
                        click: confirmFileDelete
                    }, {
                        name: "rename",
                        text: "",
                        imageClass: ‘glyphicon glyphicon-list-alt‘,
                        click: fileRename
                    }, {
                        name: "edits", 
                        text: "", 
                        imageClass: ‘glyphicon glyphicon-pencil‘,
                        click: viewfile
                    }
                ], 
                width: 310,
                title: "Operation"
            }]
    });

注意grid中的Shared列,使用的是kendo的模板#=#这种写法。最后我们再看一下command列,这个列的话其实就是定义一些按钮,每个按钮都定义好了click事件。就看第一个showDetails,看看js代码

function showDetails(e) {
    var wnd = $("#wd_details").kendoWindow({
        title: "File Detail Info",
        modal: true,
        visible: false,
        resizable: false,
        minWidth: 300
    }).data("kendoWindow");
    
    var detailsTemplate = kendo.template($("#popup_detail").html());
    e.preventDefault();
    
    var dataItem = this.dataItem($(e.currentTarget).closest("tr"));
    wnd.content(detailsTemplate(dataItem));
    wnd.center().open();
}

在这里其实就是弹出一个kendo window,这个弹出页中又加载了kendo 模板popup_detail,我们来看一下这个template。

script#popup_detail(type="text/x-kendo-template")
 dl
  dt
   label File Name:
  dd #=fullname#
  dt
   label Shared:
  dd #=isshared#
  dt
   label CreateDate:
  dd #=kendo.toString(createdate,‘MM/dd/yyyy HH:mm tt‘)#
  dt
   label LastEditUser:
  dd #=lastedituser#
  dt
   label LastEditDate:
  dd #=kendo.toString(lasteditdate,‘MM/dd/yyyy HH:mm tt‘)#

只要我们将一个对象给模板,模板就会自己替换对应的内容。在这里我们会先拿到点击行的对象,然后通过.content赋给模板,最后弹出modal页。

技术分享

就是这么简单,点击重命名按钮,就会弹出重命名页面。

技术分享

rename功能其实很简单,看看前台和后台。

$("#btn_fileRename").click(function () {
    var fileName = $.trim($("#new_fileName").val());
    if (!fileName) {
        showMsg("info", "Please input new file name!");
        return;
    }
    
    var postData = {
        id: $("#hfd_fileId").val(),
        filename: fileName
    };
    
    $.ajax({
        url: ‘/file/rename‘, 
        type: ‘PUT‘, 
        dataType: ‘json‘,
        data: { postData: JSON.stringify(postData) },
        success: function (res) {
            if (!res.isSuc) {
                showMsg(‘error‘, res.msg);
                return;
            }
            
            $("#wd_fileRename").data("kendoWindow").close();
            getFilelist(selGroupId);
        }
    });
});
router.put(‘/file/rename‘, fileRoutes.fileRename);
exports.fileRename = function (req, res) {
    var data = JSON.parse(req.body.postData);
    fileModel.findByIdAndUpdate(data.id, { $set: { name: data.filename, lasteditdate: Date.now() } }
        , function (error, result) {
        if (error) {
            res.json({ isSuc: false, msg: error.message });
        }
        else {
            res.json({ isSuc: true });
        }
    });
}

nodejs,用起来就是这么爽,好了今天就到这里,明天我们继续会讲剩下的group&subgroup创建,文件删除,共享设置等功能,敬请期待。

本文出自 “技术创造价值” 博客,请务必保留此出处http://leelei.blog.51cto.com/856755/1795764

以上是关于Node.js 切近实战 之Excel在线(文件&文件组)的主要内容,如果未能解决你的问题,请参考以下文章

Node.js 切近实战 之图书管理系统

Node.js 切近实战 之Linux部署

Node.js 切近实战 之图书管理系统(图书查询)

[node 工具 ] 用 Node.js 将 bugzilla 上的 bug 列表导入到 excel 表格在线版本之一( web 端)

[node 工具] 用 Node.js 将 bugzilla 上的 bug 列表导入到 excel 表格在线版本之一(server 端)

Node.js实战之Node多进程与JXcore 打包深入运用