angularjs 自定义 jexcel 编辑器

本文介绍了如何在 AngularJS 项目中使用 jExcel 扩展,特别是如何创建一个自定义的模态窗口编辑器,利用 ui.bootstrap 模态组件实现单元格编辑。作者通过 TypeScript 创建了一个基类 EditorBase,包含了 closeEditor、openEditor、getValue 和 setValue 方法,并定义了一个抽象方法 onEdit,允许子类实现弹窗编辑逻辑。通过 $jexcelEditorProvider 注册编辑器,实现了编辑器的复用性和扩展性,减少了重复工作量。

jexcel是一个在线形式编辑excel的jquery扩展,现在angular项目中引用,目前有个需求是编辑一个单元格时可以弹出一个模态窗口编辑单元格的值,需要使用ui.bootstrap的模态窗口,通过传递templateUrl和controller等参数实现mvvm方式绑定

效果是这样的

双击单元格,弹出模态窗口,模态窗口的内容从模板加载(先弄个简单的)

经过仔细看jexcel的源码和例子后得知,扩展一个编辑器需要创建一个对象,包含closeEditor、openEditor、getValue、setValue这四个方法,其中:

  • closeEditor (cell, save):关闭编辑器时候触发的事件,在这个方法里需要返回编辑的值
  • openEditor (cell):打开编辑器,在双击单元格或触发编辑行为的时执行的方法,这里可定义编辑的行为,编辑完成后要使用jexcel对象触发执行closeEditor方法
  • getValue (cell):用于获取编辑的值
  • setValue (cell, value):用于设置编辑的值,可在这里执行验证逻辑,返回布尔值

使用typescript,代码实现可以这样:

var editor = {
    closeEditor(cell: JQLite, save: boolean): string {
        return this._value;
    },

    openEditor(cell: JQLite) {
        var je = $('#' + $.fn.jexcel.current);
        $modal.open({
            templateUrl: '板地址...',
            controller: 'controller定义',
            // 这里要把 $rootScope.$new() 写前面
            scope: angular.extend($rootScope.$new(), {
                val: this._value
            })
        }).result.then(result => {
            je.jexcel('closeEditor', cell, true);
        }).catch(() => {
            je.jexcel('closeEditor', cell, false);
        });
    },

    getValue(cell: JQLite): string {
        this._value = cell.html();
        return this._value;
    },
    
    setValue(cell: JQLite, value: string): boolean {
        cell.html(value);
        return true;
    }
};

var jexcelOptions = {
    colHeaders: ['列1', '列2', '列3', '列4'],
    columns: [
        { type: 'text' },
        { type: 'text' },
        {
          type: 'text',
          editor: editor
        },
        { type: 'text', editor: editor  }
    ]
}

但现在存在一些问题,遇到其他需要用到这种弹窗方式编辑的情况,每个地方都要实现一次,而且closeEditor、getValue、setValue这3个方法基本上不会有什么变化,如果出现其他单元格编辑方式还要单独开发产生重复工作量,最好能够通过angular中类似 $filter 服务的形式返回创建编辑器对象的方法,像这样:

$filter('date')(new Date(),'yyyy-MM-dd')

再执行该方法传递参数的方式创建编辑器

先列出最终使用的结果

import mod = require('modules/manage/module');
import angular = require('angular');

class Controller {
  static $inject = ['$scope', '$rootScope', '$jexcelEditor'];
  constructor(
    private $scope: any | ng.IScope,
    private $rootScope,
    // $jexcelEditor 是一个类似 $filter 的服务
    // 可通过 $jexcelEditor(<编辑器名称>)(<参数>) 形式构建编辑器
    private $jexcelEditor: manage.IJExcelEditorFactory
  ) {
    $scope.vm = this;
    $scope.search = { keyword: '' };
    $scope.jexcel = {
      colHeaders: ['设备编号', '设备名称', '所属类别', '所在舱室'],
      columns: [
        { type: 'text' },
        { type: 'text' },
        {
          type: 'text',
          // 这里用 $jexcelEditor 获取编辑器构建方法,通过传递参数实例化编辑器
          editor: $jexcelEditor('modalEditor')({
            template: '<div>aaaaaaaaaaaaaaaaa</div>'
          })
        },
        {
          type: 'text',
          editor: $jexcelEditor('modalEditor')({
            templateUrl: 'modules/manage/configs/jexcel/equipmentCategory.html',
            controller: 'modules/manage/controllers/equipmentCategory'
          })
        }
      ]
    };
  }

  keywordCallback() {}

  create() {}
}

mod.controller('modules/manage/components/equipment/list', Controller);

关于 $jexcelEditor 的实现,实际上是一个 provider,提供一个register方法在应用初始化过程中提前注册编辑器构造方法(一个factory),在使用时通过名称找到编辑器的构造方法,传递参数创建编辑器

import cfg = require('modules/manage/configs');
import angular = require('angular');

class Provider implements manage.IJExcelEditorProvider {
  static $inject = ['$provide'];
  static suffix = 'JExcelEditor';
  constructor(private $provide) {}

  register(name: string | object, factory: Function) {
    if (angular.isObject(name)) {
      var editors = {};
      angular.forEach(name, (editor, key) => {
        editors[key] = this.register(key, editor);
      });
      return editors;
    } else {
      return this.$provide.factory(name + Provider.suffix, factory);
    }
  }

  $get = [
    '$injector',
    $injector => {
      return name => {
        return $injector.get(name + Provider.suffix);
      };
    }
  ];
}

cfg.provider('$jexcelEditor', Provider);

关于编辑器构造采用 typescript 面向对象的继承机制,先构建一个编辑器抽象基类 EditorBase,包括编辑器4个基本方法的实现,基类中加入一个抽象方法 onEdit,方法返回值是一个promise对象,用于处理编辑器打开时的操作和返回值的回调,所有编辑器都继承这个基类

export abstract class EditorBase implements jexcel.IEditor {
  protected _value;

  // 抽象方法,派生类中实现处理打开编辑器的行为
  // 需要返回一个 promise 对象
  abstract onEdit(cell: JQLite, value: any): ng.IPromise<any>;

  closeEditor(cell: JQLite, save: boolean): string {
    return this._value;
  }

  openEditor(cell: JQLite) {
    var je = $('#' + $.fn.jexcel.current);
    this.onEdit(cell, this._value)
      .then(result => {
        this._value = result;
        je.jexcel('closeEditor', cell, true);
      })
      .catch(() => {
        je.jexcel('closeEditor', cell, false);
      });
  }

  getValue(cell: JQLite): string {
    this._value = cell.html();
    return this._value;
  }

  setValue(cell: JQLite, value: string): boolean {
    cell.html(value);
    return true;
  }
}

继承 EditorBase 实现一个弹出模态窗口的编辑器,并使用 $jexcelEditorProvider.register 注册编辑器

import cfg = require('modules/manage/configs');
import angular = require('angular');
import { EditorBase } from 'modules/manage/configs/jexcel/editorBase';

class ModalEditor extends EditorBase {
  // 编辑器 onEdit 方法的实现
  onEdit(cell: JQLite, value: any): angular.IPromise<any> {
    // 因为 ui.bootstrap 中 $modal 的结果本身就是一个 promise 对象所以可以直接作为返回值
    return this.$modal.open(
      angular.extend(
        {
          scope: angular.extend(this.$rootScope.$new(), { $value: value })
        },
        this.options
      )
    ).result;
  }
  constructor(
    private $modal: ng.ui.bootstrap.IModalService,
    private $rootScope: ng.IRootScopeService,
    private options: ng.ui.bootstrap.IModalSettings
  ) {
    super();
  }
}

class Config {
  static $inject = ['$jexcelEditorProvider'];
  constructor($jexcelEditorProvider: manage.IJExcelEditorProvider) {
    // 之前定义的 $jexcelEditor 在config中调用 register 方法注册编辑器
    $jexcelEditorProvider.register('modalEditor', [
      '$modal',
      '$rootScope',
      (
        $modal: ng.ui.bootstrap.IModalService,
        $rootScope: ng.IRootScopeService
      ) => {
        // factory 返回的是一个编辑器构建方法,使用时传递参数 options
        return (options: ng.ui.bootstrap.IModalSettings) => {
          // 编辑器构建方法执行后返回真正的编辑器对象
          return new ModalEditor($modal, $rootScope, options);
        };
      }
    ]);
  }
}

cfg.config(Config);

最后用这种形式创建编辑器,如果以后有别的形式的编辑器并且又需要用这种形式跟 angular 整合的只要继承 EditorBase 实现 onEdit 并用 $jexcelEditorProvider 注册就行了

$jexcelEditor('modalEditor')({
    templateUrl: 'modules/manage/configs/jexcel/equipmentCategory.html',
    controller: 'modules/manage/controllers/equipmentCategory'
})

最后想说为什么要用 typescript 实现,还不是因为各种 js 编辑器智能提示功能太弱,而且开发惯了后台代码再写前台,继承机制又不那么习惯,都不能像java或c#那样,文件一多就混乱了也容易出错还不好排查

里面那些类型定义和 d 文件不具体列了,都是方法定义

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值