Mendix,在开发组件之前,需要了解的部分知识

环境安装

  • node 环境,建议安装v18以上版本
    • nodejs官网下载安装包,或通过nvm安装
    • node -v
  • 全局安装yo
    • npm install -g yo
  • 全局安装 @mendix/generator-widget
    • npm install -g @mendix/generator-widget

使用

  • 初始化项目
    • yo @mendix/widget myWidget
  • 进入组件目录并打包,将组件更新到mendix本地工程中
    • cd myWidget && npm run build

组件目录说明

go 复制代码
- dist
- node_modules
- src
    components
      HelloWorldSample.tsx   // 默认生成的子组件
    ui
      xxx.css   // 可修改为 scss文件,
    package.xml
    xxx.editorConfig.ts   // xml配置信息调整,比如某个xml字段的显示隐藏等操作
    xxx.editorPreview.tsx // mendix预览模式下的展示内容
    xxx.tsx  // 组件渲染的内容文件
    xxx.xml  // 配置组件需要用到的参数信息
- typings
    xxx.d.ts  // xml文件配置的字段类型,自动生成的

xml配置项说明

xml 复制代码
<?xml version="1.0" encoding="utf-8" ?>
<widget
    id="mendix.xxx.xxx"
    pluginWidget="true"
    needsEntityContext="true"
    offlineCapable="true"
    supportedPlatform="Web"
    xmlns="http://www.mendix.com/widget/1.0/"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.mendix.com/widget/1.0/ ../node_modules/mendix/custom_widget.xsd"
>
    <name>xxx</name>
    <description>My widget description</description>
    <icon />
    <properties>
        <propertyGroup caption="General">
            <!-- Data source分组 -->
            <propertyGroup caption="Data source">
                <property key="myDatasource" type="datasource" isList="true">
                    <caption>Data Source</caption>
                    <description>关联数据源</description>
                </property>
                <property key="mode" type="enumeration" defaultValue="undefined">
                    <caption>Mode</caption>
                    <description>枚举类型</description>
                    <enumerationValues>
                        <enumerationValue key="undefined">Single</enumerationValue>
                        <enumerationValue key="multiple">Multiple</enumerationValue>
                        <enumerationValue key="tags">Tags</enumerationValue>
                    </enumerationValues>
                </property>
                <property key="showCode" type="boolean" defaultValue="false">
                    <caption>Show Prefix</caption>
                    <description>布尔值</description>
                </property>
                <property key="code" type="attribute" dataSource="myDatasource">
                    <caption>Option Prefix</caption>
                    <description>从myDatasource中选取某个字段,需要指定类型</description>
                    <attributeTypes>
                        <!-- 根据字段类型显示字段 -->  
                        <attributeType name="String" />  
                        <attributeType name="AutoNumber" />  
                        <attributeType name="Boolean" />  
                        <attributeType name="DateTime" />  
                        <attributeType name="Decimal" />  
                        <attributeType name="Enum" />  
                        <attributeType name="Integer" />  
                        <attributeType name="Long" />
                    </attributeTypes>
                </property>
                <property key="labelName" type="attribute" dataSource="myDatasource">
                    <caption>Label Name</caption>
                    <description>从myDatasource中选取某个字段,需要指定类型</description>
                    <attributeTypes>
                        <attributeType name="String" />
                    </attributeTypes>
                </property>
                <property key="placeholder" type="string" defaultValue="placeholder">
                    <caption>PlaceHolder</caption>
                    <description>mendix配置的字符串</description>
                </property>
                <property key="isRequired" type="expression" required="true" defaultValue="false">
                    <caption>Is Required</caption>
                    <description>条件表达式,定义返回数据类型</description>
                    <returnType type="Boolean" />
                </property>
            </propertyGroup>
            <!-- Events分组 -->
            <propertyGroup caption="Events">
                <property key="onChangeAction" type="action" required="false">
                    <caption>Value Change</caption>
                    <description>事件</description>
                </property>
            </propertyGroup>
        </propertyGroup>
    </properties>
</widget>

更多文档内容,可参考Mendix组件文档

xml配置项的显示/隐藏方式

  • 修改 xxx.editorConfig.ts 文件

    typescript 复制代码
    import { xxxProps } from "../typings/xxx";
    import { hidePropertyIn } from "@mendix/pluggable-widgets-tools"; // 新增依赖    
    // ...
    // ...
    // ...    
    export function getProperties(_values: xxxProps, defaultProperties: Properties): Properties {
        // 主要代码
        if (!_values.showCode) {
            hidePropertyIn(defaultProperties, _values, "code");
        }
        if (!_values.isRequired) {
            hidePropertyIn(defaultProperties, _values, "errorText");
        }
        return defaultProperties;
    }

组件参数说明

  • 组件默认接收一个对象,默认参数在mendix中可配置且,包含有:

    • name
    • class
    • style:非必传
    • tabIndex:非必传
  • 用法

    typescript 复制代码
    import { ReactElement, createElement } from "react";
    import classNames from "classnames";
    import { xxxProps } from "../typings/xxxProps";
    import "./ui/xxx.scss";
    
    export function xxx(props: xxxProps): ReactElement {
        const { name, class: cls, style, tabIndex } = props;
    
        return (
            <div className={classNames(cls, "v-test-wrap")} style={style}>
                <p>Container</p>
            </div>
        );
    }

如需要用到mendix挂载在window上的mx方法,可在 typings 目录下添加 client.d.ts

typescript 复制代码
declare namespace mx {
    namespace ui {
        type OpenForm2Function = ((
            page: string,
            dh: DisposeCallback,
            title: string,
            currentForm: any,
            option: Option,
            numberOfPagesToClose: number
        ) => Promise<any>) & { ["_tabRouter"]: boolean };
        interface DisposeCallback {
            [key: string]: { unsubscribe: () => void };
        }
        interface Option {
            location: "content" | "popup" | "node";
            domNode?: Element;
        }
        let openForm2: OpenForm2Function;

        let getContentForm: any;
    }
    namespace data {
        type saveDocument = (
            guid: string,
            name: string,
            params: any,
            blob: File,
            callback: () => void,
            errorCallback: (error: any) => void
        ) => Promise<void>;

        type remove = (params: RemoveType) => Promise<void>;

        interface RemoveType {
            guid: string;
            callback: () => void;
            errorCallback: (error: any) => void;
        }

        type action = (
            actionname: any,
            applyto: any,
            guids: any[],
            params: any,
            callback: (result: any) => void,
            errorCallback: (error: any) => void
        ) => Promise<void>;

        type callNanoflow = (
            nanoflow: any,
            context: mx.lib.MxContext,
            origin: mx.lib.form._FormBase,
            callback: (result: any) => void,
            errorCallback: (error: any) => void
        ) => Promise<void>;

        type create = (
            entity: string,
            callback: (guid: string) => void,
            errorCallback: (error: any) => void
        ) => Promise<void>;

        const action = action;
        const callNanoflow = callNanoflow;
        const saveDocument = saveDocument;
        const remove = remove;
        const create = create;
    }
}
declare namespace mendix {
    namespace lib {
        class MxContext {
            setTrackObject(obj: any): void;
        }
    }
    interface Lang {
        getUniqueId(): string;
    }
    let lang: Lang;
}

declare namespace dijit {
    function getUniqueId(id: string): string;
}

declare namespace window {
    namespace vRequestManager {
        function request(url: string, options: any, requestId: string): Promise<any>;
        function vCreateObjFn(obj: any): Promise<any>;
        function cancelRequest(id: string): Promise<void>;
    }
}

mendix工程,默认是不会生成 index.html 文件的,需要手动生成,方式如下

  • mendix 菜单栏 App -> Show App Directory in Explorer

  • xxx/theme/web 目录下,就是项目启动的入口文件所在位置

  • index.html 文件内容

    html 复制代码
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
        <meta http-equiv="Pragma" content="no-cache" />
        <meta http-equiv="Expires" content="0" />
        <meta name="referrer" content="no-referrer" />
        <title>Mendix</title>
        <script type="text/javascript">
          // 定义全局 hash
          globalThis.hash = Math.floor(Math.random() * 0xffffff).toString(16);
          try {
            eval("async () => {}");
          } catch (error) {
            var homeUrl = window.location.origin + window.location.pathname;
            var appUrl = homeUrl.slice(0, homeUrl.lastIndexOf("/") + 1);
            window.location.replace(appUrl + "unsupported-browser.html");
          }
        </script>
      </head>
    
      <body>
        <noscript>To use this application, please enable JavaScript.</noscript>
        <div id="content"></div>
      </body>
    
      <script type="text/javascript">
        dojoConfig = {
          isDebug: false,
          useCustomLogger: true,
          async: true,
          baseUrl: "mxclientsystem/dojo/",
          cacheBust: globalThis.hash,
          rtlRedirect: "index-rtl.html",
        };
        // 需要添加script标签的地方
        const jsSrcList = [ "mxclientsystem/mxui/mxui.js" ];
        for (const src of jsSrcList) {
          const scriptEl = document.createElement("script");
          scriptEl.src = `${src}?s=${globalThis.hash}`;
          document.body.appendChild(scriptEl);
        }
        // 加载 link 标签
        const iconList = [
          // manifest
          {
            rel: "manifest",
            href: "manifest.webmanifest",
            crossorigin: "use-credentials",
          },
          // main.scss 编译之后文件
          { rel: "stylesheet", href: "theme.compiled.css" },
          // icon
          {
            rel: "apple-touch-icon",
            href: "apple-touch-icon.png",
            sizes: "180x180",
          },
          { rel: "icon", href: "icon-32.png", sizes: "32x32" },
          { rel: "icon", href: "icon-16.png", sizes: "16x16" },
          // apple-touch-startup-image
          {
            custorm: true,
            width: 1024,
            height: 1366,
            devicePixelRatio: 2,
            orientation: "portrait",
          },
          {
            custorm: true,
            width: 1366,
            height: 1024,
            devicePixelRatio: 2,
            orientation: "landscape",
          },
          {
            custorm: true,
            width: 834,
            height: 1194,
            devicePixelRatio: 2,
            orientation: "portrait",
          },
          {
            custorm: true,
            width: 1194,
            height: 834,
            devicePixelRatio: 2,
            orientation: "landscape",
          },
          {
            custorm: true,
            width: 834,
            height: 1112,
            devicePixelRatio: 2,
            orientation: "portrait",
          },
          {
            custorm: true,
            width: 1112,
            height: 834,
            devicePixelRatio: 2,
            orientation: "landscape",
          },
          {
            custorm: true,
            width: 810,
            height: 1080,
            devicePixelRatio: 2,
            orientation: "portrait",
          },
          {
            custorm: true,
            width: 1080,
            height: 810,
            devicePixelRatio: 2,
            orientation: "landscape",
          },
          {
            custorm: true,
            width: 768,
            height: 1024,
            devicePixelRatio: 2,
            orientation: "portrait",
          },
          {
            custorm: true,
            width: 1024,
            height: 768,
            devicePixelRatio: 2,
            orientation: "landscape",
          },
          {
            custorm: true,
            width: 428,
            height: 926,
            devicePixelRatio: 3,
            orientation: "portrait",
          },
          {
            custorm: true,
            width: 926,
            height: 428,
            devicePixelRatio: 3,
            orientation: "landscape",
          },
          {
            custorm: true,
            width: 390,
            height: 844,
            devicePixelRatio: 3,
            orientation: "portrait",
          },
          {
            custorm: true,
            width: 844,
            height: 390,
            devicePixelRatio: 3,
            orientation: "landscape",
          },
          {
            custorm: true,
            width: 360,
            height: 780,
            devicePixelRatio: 3,
            orientation: "portrait",
          },
          {
            custorm: true,
            width: 780,
            height: 360,
            devicePixelRatio: 3,
            orientation: "landscape",
          },
          {
            custorm: true,
            width: 414,
            height: 896,
            devicePixelRatio: 3,
            orientation: "portrait",
          },
          {
            custorm: true,
            width: 896,
            height: 414,
            devicePixelRatio: 3,
            orientation: "landscape",
          },
          {
            custorm: true,
            width: 414,
            height: 896,
            devicePixelRatio: 2,
            orientation: "portrait",
          },
          {
            custorm: true,
            width: 896,
            height: 414,
            devicePixelRatio: 2,
            orientation: "landscape",
          },
          {
            custorm: true,
            width: 375,
            height: 812,
            devicePixelRatio: 3,
            orientation: "portrait",
          },
          {
            custorm: true,
            width: 812,
            height: 375,
            devicePixelRatio: 3,
            orientation: "landscape",
          },
          {
            custorm: true,
            width: 375,
            height: 667,
            devicePixelRatio: 2,
            orientation: "portrait",
          },
          {
            custorm: true,
            width: 667,
            height: 375,
            devicePixelRatio: 2,
            orientation: "landscape",
          },
          {
            custorm: true,
            width: 414,
            height: 736,
            devicePixelRatio: 3,
            orientation: "portrait",
          },
          {
            custorm: true,
            width: 736,
            height: 414,
            devicePixelRatio: 3,
            orientation: "landscape",
          },
          {
            custorm: true,
            width: 320,
            height: 568,
            devicePixelRatio: 2,
            orientation: "portrait",
          },
          {
            custorm: true,
            width: 568,
            height: 320,
            devicePixelRatio: 2,
            orientation: "landscape",
          },
        ];
        for (const items of iconList) {
          const link = document.createElement("link");
          if (items.custorm) {
            link.rel = "apple-touch-startup-image";
            link.href = `img/startup-image-${
              items.width * items.devicePixelRatio
            }x${items.height * items.devicePixelRatio}.png?${globalThis.hash}`;
            link.media = `screen and (device-width: ${items.width}px) and (device-height: ${items.height}px) and (-webkit-device-pixel-ratio: ${items.devicePixelRatio}) and (orientation: ${items.orientation})`;
          } else {
            link.rel = items.rel;
            if (items.sizes) {
              link.sizes = items.sizes;
            }
            if (items.crossorigin) {
              link.crossorigin = items.crossorigin;
            }
            link.href = `${items.href}?l=${globalThis.hash}`;
          }
          document.head.appendChild(link);
        }
    
        // 处理登录页面的 cookie
        if (!document.cookie || !document.cookie.match(/(^|;) *originURI=/gi)) {
          const url = new URL(window.location.href);
          const subPath = url.pathname.substring(0, url.pathname.lastIndexOf("/"));
          document.cookie = `originURI=${subPath}/login.html${
            window.location.protocol === "https:" ? ";SameSite=None;Secure" : ""
          }`;
        }
      </script>
    </html>
相关推荐
_r0bin_1 小时前
前端面试准备-7
开发语言·前端·javascript·fetch·跨域·class
IT瘾君1 小时前
JavaWeb:前端工程化-Vue
前端·javascript·vue.js
potender1 小时前
前端框架Vue
前端·vue.js·前端框架
站在风口的猪11082 小时前
《前端面试题:CSS预处理器(Sass、Less等)》
前端·css·html·less·css3·sass·html5
程序员的世界你不懂2 小时前
(9)-Fiddler抓包-Fiddler如何设置捕获Https会话
前端·https·fiddler
MoFe12 小时前
【.net core】天地图坐标转换为高德地图坐标(WGS84 坐标转 GCJ02 坐标)
java·前端·.netcore
去旅行、在路上3 小时前
chrome使用手机调试触屏web
前端·chrome
Aphasia3113 小时前
模式验证库——zod
前端·react.js
lexiangqicheng4 小时前
es6+和css3新增的特性有哪些
前端·es6·css3
拉不动的猪5 小时前
都25年啦,还有谁分不清双向绑定原理,响应式原理、v-model实现原理
前端·javascript·vue.js