Component.ts 10.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/

L
lang 已提交
20

S
sushuang 已提交
21
import * as zrUtil from 'zrender/src/core/util';
S
sushuang 已提交
22 23
import Model from './Model';
import * as componentUtil from '../util/component';
24 25 26 27 28 29 30 31
import {
    enableClassManagement,
    parseClassType,
    isExtendedClass,
    ExtendableConstructor,
    ClassManager,
    mountExtend
} from '../util/clazz';
S
sushuang 已提交
32
import {makeInner} from '../util/model';
S
sushuang 已提交
33
import * as layout from '../util/layout';
34
import GlobalModel from './Global';
35 36 37 38 39 40 41 42
import {
    ComponentOption,
    ComponentMainType,
    ComponentSubType,
    ComponentFullType,
    ComponentLayoutMode,
    BoxLayoutOptionMixin
} from '../util/types';
S
sushuang 已提交
43

44 45
var inner = makeInner<{
    defaultOption: ComponentOption
46
}, ComponentModel>();
S
sushuang 已提交
47

48
class ComponentModel<Opt extends ComponentOption = ComponentOption> extends Model<Opt> {
S
sushuang 已提交
49

50 51 52 53 54
    // [Caution]: for compat the previous "class extend"
    // publich and protected fields must be initialized on
    // prototype rather than in constructor. Otherwise the
    // subclass overrided filed will be overwritten by this
    // class. That is, they should not be initialized here.
P
pah100 已提交
55

L
lang 已提交
56
    /**
57 58
     * @readonly
     */
59
    type: ComponentFullType;
60 61 62

    /**
     * @readonly
L
lang 已提交
63
     */
64
    id: string;
P
pah100 已提交
65

S
sushuang 已提交
66
    /**
S
sushuang 已提交
67 68 69 70 71 72
     * Because simplified concept is probably better, series.name (or component.name)
     * has been having too many resposibilities:
     * (1) Generating id (which requires name in option should not be modified).
     * (2) As an index to mapping series when merging option or calling API (a name
     * can refer to more then one components, which is convinient is some case).
     * (3) Display.
73 74 75 76 77
     * @readOnly But injected
     */
    name: string;

    /**
S
sushuang 已提交
78 79
     * @readOnly
     */
80
    mainType: ComponentMainType;
P
pah100 已提交
81

S
sushuang 已提交
82 83 84
    /**
     * @readOnly
     */
85
    subType: ComponentSubType;
S
sushuang 已提交
86 87 88 89

    /**
     * @readOnly
     */
90
    componentIndex: number;
S
sushuang 已提交
91 92 93 94

    /**
     * @readOnly
     */
95
    protected defaultOption: ComponentOption;
S
sushuang 已提交
96 97

    /**
98
     * @readOnly
S
sushuang 已提交
99
     */
100
    ecModel: GlobalModel;
S
sushuang 已提交
101 102 103 104

    /**
     * @readOnly
     */
1
100pah 已提交
105
    static dependencies: string[];
S
sushuang 已提交
106 107 108

    /**
     * key: componentType
109
     * value: Component model list, can not be null.
S
sushuang 已提交
110 111
     * @readOnly
     */
112 113 114
    dependentModels: {[componentType: string]: ComponentModel[]} = {};

    readonly uid: string;
S
sushuang 已提交
115

1
100pah 已提交
116 117 118
    // // No common coordinateSystem needed. Each sub class implement
    // // `CoordinateSystemHostModel` itself.
    // coordinateSystem: CoordinateSystemMaster | CoordinateSystemExecutive;
S
sushuang 已提交
119 120 121 122 123

    /**
     * Support merge layout params.
     * Only support 'box' now (left/right/top/bottom/width/height).
     */
1
100pah 已提交
124
    static layoutMode: ComponentLayoutMode | ComponentLayoutMode['type'];
125 126 127 128 129 130 131 132 133 134 135 136 137

    // Injectable properties:
    __viewId: string;

    static protoInitialize = (function () {
        var proto = ComponentModel.prototype;
        proto.type = 'component';
        proto.id = '';
        proto.name = '';
        proto.mainType = '';
        proto.subType = '';
        proto.componentIndex = 0;
    })();
S
sushuang 已提交
138 139


140
    constructor(option: Opt, parentModel: Model, ecModel: GlobalModel) {
141
        super(option, parentModel, ecModel);
S
sushuang 已提交
142
        this.uid = componentUtil.getUID('ec_cpt_model');
143
    }
S
sushuang 已提交
144

145
    init(option: Opt, parentModel: Model, ecModel: GlobalModel): void {
S
sushuang 已提交
146
        this.mergeDefaultAndTheme(option, ecModel);
147
    }
S
sushuang 已提交
148

149
    mergeDefaultAndTheme(option: Opt, ecModel: GlobalModel): void {
1
100pah 已提交
150
        var layoutMode = layout.fetchLayoutMode(this);
S
sushuang 已提交
151
        var inputPositionParams = layoutMode
152
            ? layout.getLayoutParams(option as BoxLayoutOptionMixin) : {};
S
sushuang 已提交
153 154 155 156 157 158

        var themeModel = ecModel.getTheme();
        zrUtil.merge(option, themeModel.get(this.mainType));
        zrUtil.merge(option, this.getDefaultOption());

        if (layoutMode) {
159
            layout.mergeLayoutParam(option as BoxLayoutOptionMixin, inputPositionParams, layoutMode);
L
lang 已提交
160
        }
161
    }
162

163
    mergeOption(option: Opt, ecModel: GlobalModel): void {
S
sushuang 已提交
164
        zrUtil.merge(this.option, option, true);
L
lang 已提交
165

1
100pah 已提交
166
        var layoutMode = layout.fetchLayoutMode(this);
S
sushuang 已提交
167
        if (layoutMode) {
168 169 170 171 172
            layout.mergeLayoutParam(
                this.option as BoxLayoutOptionMixin,
                option as BoxLayoutOptionMixin,
                layoutMode
            );
S
sushuang 已提交
173
        }
174
    }
S
sushuang 已提交
175 176

    // Hooker after init or mergeOption
177
    optionUpdated(newCptOption: Opt, isInit: boolean): void {}
178 179 180 181 182 183 184 185 186 187 188

    /**
     * [How to declare defaultOption]:
     *
     * (A) If using class declaration in typescript (since echarts 5):
     * ```ts
     * import {ComponentOption} from '../model/option';
     * export interface XxxOption extends ComponentOption {
     *     aaa: number
     * }
     * export class XxxModel extends Component {
189 190
     *     static type = 'xxx';
     *     static defaultOption: XxxOption = {
191 192 193
     *         aaa: 123
     *     }
     * }
194
     * Component.registerClass(XxxModel);
195 196
     * ```
     * ```ts
197
     * import {inheritDefaultOption} from '../util/component';
198 199 200 201 202
     * import {XxxModel, XxxOption} from './XxxModel';
     * export interface XxxSubOption extends XxxOption {
     *     bbb: number
     * }
     * class XxxSubModel extends XxxModel {
203
     *     static defaultOption: XxxSubOption = inheritDefaultOption(XxxModel.defaultOption, {
204
     *         bbb: 456
205
     *     })
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
     *     fn() {
     *         var opt = this.getDefaultOption();
     *         // opt is {aaa: 123, bbb: 456}
     *     }
     * }
     * ```
     *
     * (B) If using class extend (previous approach in echarts 3 & 4):
     * ```js
     * var XxxComponent = Component.extend({
     *     defaultOption: {
     *         xx: 123
     *     }
     * })
     * ```
     * ```js
     * var XxxSubComponent = XxxComponent.extend({
     *     defaultOption: {
     *         yy: 456
     *     },
     *     fn: function () {
     *         var opt = this.getDefaultOption();
     *         // opt is {xx: 123, yy: 456}
     *     }
     * })
     * ```
     */
233
    getDefaultOption(): Opt {
234 235 236 237 238 239
        var ctor = this.constructor;

        // If using class declaration, it is different to travel super class
        // in legacy env and auto merge defaultOption. So if using class
        // declaration, defaultOption should be merged manually.
        if (!isExtendedClass(ctor)) {
240 241
            // When using ts class, defaultOption must be declared as static.
            return (ctor as any).defaultOption;
242
        }
S
sushuang 已提交
243

244
        // FIXME: remove this approach?
S
sushuang 已提交
245 246
        var fields = inner(this);
        if (!fields.defaultOption) {
S
sushuang 已提交
247
            var optList = [];
248 249 250
            var clz = ctor as ExtendableConstructor;
            while (clz) {
                var opt = clz.prototype.defaultOption;
S
sushuang 已提交
251
                opt && optList.push(opt);
252
                clz = clz.superClass;
S
sushuang 已提交
253 254 255 256 257 258
            }

            var defaultOption = {};
            for (var i = optList.length - 1; i >= 0; i--) {
                defaultOption = zrUtil.merge(defaultOption, optList[i], true);
            }
S
sushuang 已提交
259
            fields.defaultOption = defaultOption;
S
sushuang 已提交
260
        }
261
        return fields.defaultOption as Opt;
262
    }
S
sushuang 已提交
263

264
    getReferringComponents(mainType: ComponentMainType): ComponentModel[] {
265 266
        const indexKey = (mainType + 'Index') as keyof Opt;
        const idKey = (mainType + 'Id') as keyof Opt;
S
sushuang 已提交
267 268
        return this.ecModel.queryComponents({
            mainType: mainType,
269 270
            index: this.get(indexKey, true) as unknown as number,
            id: this.get(idKey, true) as unknown as string
P
pah100 已提交
271
        });
P
pah100 已提交
272 273
    }

274 275 276 277 278 279 280 281 282 283 284 285 286
    getBoxLayoutParams() {
        // Consider itself having box layout configs.
        const boxLayoutModel = this as Model<ComponentOption & BoxLayoutOptionMixin>;
        return {
            left: boxLayoutModel.get('left'),
            top: boxLayoutModel.get('top'),
            right: boxLayoutModel.get('right'),
            bottom: boxLayoutModel.get('bottom'),
            width: boxLayoutModel.get('width'),
            height: boxLayoutModel.get('height')
        };
    }

287
    static registerClass: ClassManager['registerClass'];
1
100pah 已提交
288 289

    static registerSubTypeDefaulter: componentUtil.SubTypeDefaulterManager['registerSubTypeDefaulter'];
290
}
S
sushuang 已提交
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307

// Reset ComponentModel.extend, add preConstruct.
// clazzUtil.enableClassExtend(
//     ComponentModel,
//     function (option, parentModel, ecModel, extraOpt) {
//         // Set dependentModels, componentIndex, name, id, mainType, subType.
//         zrUtil.extend(this, extraOpt);

//         this.uid = componentUtil.getUID('componentModel');

//         // this.setReadOnly([
//         //     'type', 'id', 'uid', 'name', 'mainType', 'subType',
//         //     'dependentModels', 'componentIndex'
//         // ]);
//     }
// );

308
export type ComponentModelConstructor = typeof ComponentModel
309 310 311 312 313 314
    & ClassManager
    & componentUtil.SubTypeDefaulterManager
    & ExtendableConstructor
    & componentUtil.TopologicalTravelable<object>;

mountExtend(ComponentModel, Model);
315
enableClassManagement(ComponentModel as ComponentModelConstructor, {registerWhenExtend: true});
316 317
componentUtil.enableSubTypeDefaulter(ComponentModel as ComponentModelConstructor);
componentUtil.enableTopologicalTravel(ComponentModel as ComponentModelConstructor, getDependencies);
S
sushuang 已提交
318 319


320 321 322
function getDependencies(componentType: string): string[] {
    var deps: string[] = [];
    zrUtil.each((ComponentModel as ComponentModelConstructor).getClassesByMainType(componentType), function (clz) {
1
100pah 已提交
323
        deps = deps.concat((clz as any).dependencies || (clz as any).prototype.dependencies || []);
S
sushuang 已提交
324
    });
S
sushuang 已提交
325 326 327

    // Ensure main type.
    deps = zrUtil.map(deps, function (type) {
S
sushuang 已提交
328
        return parseClassType(type).main;
S
sushuang 已提交
329
    });
S
sushuang 已提交
330 331 332 333 334 335 336

    // Hack dataset for convenience.
    if (componentType !== 'dataset' && zrUtil.indexOf(deps, 'dataset') <= 0) {
        deps.unshift('dataset');
    }

    return deps;
S
sushuang 已提交
337 338
}

339
export default ComponentModel;