Skip to content

Latest commit

 

History

History

sifo-vue

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

sifo-vue

sifo-vue 是组装了sifo-modelsifo-singleton 的一个 Vue 组件。

sifo-vue 以 sifo-model 为内核,使用插件式的开发模式,为页面提供了多级定制与扩展能力。包含但不限于:页面结构的修改、渲染组件的替换、组件属性的变更、组件事件的监听与阻断等。结合不同的模型插件,可以实现更加丰富的特定功能。

  • 插件示例

codesandbox.io

SifoApp (sifo-vue) Props

参数 说明 类型 是否必传 默认值
namespace 命名空间,这是一个功能集合的主要标识,第三方将根据命名空间来进行功能扩展 string -
schema schema,描述了页面结构 object -
components 组件,引入局部组件时传入,全局组件不需传 object {}
plugins 插件,分为模型插件、页面插件和组件插件 array:[{ componentPlugin, pagePlugin, modelPlugin }, { modelPlugin: otherModelPlugin }] []
externals 任意其它信息 object {}
sifoExtProps 任意对象,mApi.getSifoExtProps 可以取到即时的值,这点与 externals 相区别 object {}
modelApiRef 模型接口外传方法,调用参数为 mApi(接口构建完成时) 或 null(模型销毁时) function
openLogger 是否在控制台打印出执行日志,不建议在生产环境使用 bool false
optimize 是否进行渲染优化,sifo-vue 是 top-down 的渲染模式,在复杂页面可以启用此参数 bool false
getModelPluginArgs 获取模型插件实例化时的构造函数参数 function:(modelPluginId, info) => ([arg1, arg2, ...])
class 样式类 vue.class规范

扩展的 mApi 模型接口

mApi说明

方法名 参数/类型 返回值类型 描述
getSifoVueInstance VueComponent 获取 sifo-vue 的组件实例
getSifoExtProps 任意对象 获取 SifoApp.sifoExtProps 即时的值
createElement (component, attribute, children) VueComponent vue 的 createElement 方法
renderSlot (nodeId, props) VueComponent 渲染作用域插槽的方法 ,将带slot标的指定schema节点(参数nodeId即节点id)渲染成 scopedSlots 中的节点对象,使用方法见后文

attributes

属性名 类型 默认值 描述
muteRenderOptimizeMark bool false 当节点上此属性为true时,该节点不受渲染优化标记的控制,按照普通渲染模式渲染,但仍然受父组件的渲染与否影响

如何使用

  • 项目

    • extend.js

      import SifoSingleton from '@schema-plugin-flow/sifo-singleton';
      const singleton = new SifoSingleton('quick-start'); // target namespace
      singleton.registerItem('testExtendId', () => {
        return {
          plugins,
          components
        }
      });
    • app.js

      const App = {
        template: `
          <sifo-app
            :namespace="namespace"
            class="quick-start-demo"
            :plugins="plugins"
            :components="components"
            :schema="schema"
            :openLogger="openLogger"
          />
        `
      };
      new Vue({
        render: (h) => h(App,
        {
          props: {
            namespace: 'quick-start'
          }
        }
        )
      }).$mount("#app");
  • runtime

    • load extend js

    • load app js

      你应该在 sifoApp 渲染前加载扩展 js 资源

      <script src="extend.js"></script>
      <script src="app.js"></script>

QuickStart

下面的例子演示了如何监听一个按钮组件的点击事件,并在点击事件中修改其它组件的属性,同时也演示了多个插件的情形。想了解更多的功能请参考sifo-model

<template>
  <sifo-app
    :namespace="namespace"
    class="quick-start-demo"
    :plugins="plugins"
    :components="components"
    :schema="schema"
    :openLogger="openLogger"
  />
</template>
// 注意改为script
<script--xxx-->
import SifoApp from "@schema-plugin-flow/sifo-vue";
// register local components
const components = {
  Container: {
    template: "<div><slot></slot></div>",
  },
  Slogan: {
    template: "<h2>{{content}}</h2>",
    props: ["content"],
  },
  Button: {
    template: `<button @click="$emit('click')">click to change</button>`,
  },
};
// schema 定义了初始的页面结构
const schema = {
  component: "Container",
  id: "mainId",
  attributes: {},
  children: [
    {
      component: "Slogan",
      id: "slogan_id",
      attributes: {
        content: "hello world",
      },
    },
    {
      component: "Button",
      id: "test_btn_id",
      attributes: {},
    },
  ],
};
// 组件插件可以实现与组件相关的功能
const componentPlugin1 = {
  test_btn_id: {
    onComponentInitial: (params) => {
      const { event, mApi } = params;
      mApi.addEventListener(event.key, "click", () => {
        mApi.setAttributes("slogan_id", {
          content: "hello sifo",
        });
      });
    },
  },
};
// 第二个插件
const componentPlugin2 = {
  test_btn_id: {
    onComponentInitial: (params) => {
      const { event, mApi } = params;
      mApi.addEventListener(event.key, "click", () => {
        console.log("test_btn_id clicked!");
      });
    },
  },
};
const plugins = [
  { componentPlugin: componentPlugin1 },
  { componentPlugin: componentPlugin2 },
];
export default {
  name: "quick-start",
  components: { SifoApp },
  beforeCreate: function () {
    const sifoAppProps = {
      namespace: "quick-start",
      plugins: plugins,
      components,
      schema,
      openLogger: true,
    };
    Object.keys(sifoAppProps).forEach((key) => {
      this[key] = sifoAppProps[key];
    });
  },
};
</script--xxx-->

外部扩展

如果一个页面是用 sifo 开发的,开发者可以在不接触原始代码的情况下,对页面进行扩展。这里用到了 sifo-singleton 全局扩展容器,只要在目标页面渲染前载入了扩展插件、组件,扩展功能就会在目标页面上生效。

import SifoSingleton from '@schema-plugin-flow/sifo-singleton';
const singleton = new SifoSingleton('quick-start');// 对目标命名空间进行扩展
// 插件的功能与使用跟前面的示例完全一致
const plugins = [{ pagePlugin, componentPlugin }];
const components = {};
singleton.registerItem('testExtendId', () => {
  return {
    plugins,
    components,
    openLogger: true, 
  }
});

renderSlot 使用示例

    const CompA = {
      template: `
        <div>
          <slot name="toslot" propa="val1" propb="val2">
        </div>
      `
    };
    const CompSlotItem = {
      template: `
        <div>
          {{ propa }}
          {{ propb }}
        </div>
      `,
      props: ['propa','propb']
    };
const schema = {
  component: 'CompA',
  id: 'compa-id',
  children:[
    {
      component: 'CompSlotItem',
      id: 't-slot-id',
      attributes: {
        slot: 'toslot'
      }
    }
  ]
};
mApi.setAttributes('compa-id', {
  scopedSlots: {
    // 有具名插槽时,无名(default)插槽要显式写,否则无法刷新
    toslot: function ({ propa, propb }) {
      return mApi.renderSlot('t-slot-id', { propa, propb });
    }
  }
})

sifoAppDecorator

为一个组件追加扩展能力时,可用修饰器方式。sifoAppDecorator 第一个参数是命名空间,第二个参数与上文的“SifoApp参数”一致(namespace 和 schema 除外)。此外还增加了如下参数:

参数 说明 类型 是否必传 默认值
fragments 片段列表。片段可以只定义一个id,通过 getFragment 方法获取片段来渲染;也可以传一个 schema,以 schema 的第一层 id 来标识 array -

被 sifoAppDecorator 修饰的组件,props 中将出现 sifoApp 对象,对象包含 addEventListener、 watch、getFragment 等方法和 mApi 接口。

sifoAppDecorator 示例

下面的示例包含:

  • 修饰 TestDecorator 组件,并返回修饰后的 App 组件;
  • App 组件标注命名空间为 test-sifo-decorator;
  • TestDecorator 组件向外暴露 click 事件,扩展件就可以监听与干预这些事件;
  • TestDecorator 注册了 updateData、getData 观测,扩展件可以发布相应观测消息来与 App 通信;
  • TestDecorator 定义了 $dynamic_panel, $static_panel 片段,以使扩展件可以在页面指定位置渲染内容。 完整示例请参照这里
  1. 对TestDecorator组件加修饰
import { sifoAppDecorator } from "@schema-plugin-flow/sifo-vue";
import TestDecorator from './test-decorator-target.vue';
//
const componentPlugin = {
  'test-sifo-decorator': {
    onComponentInitial: params => {
      const { event, mApi } = params;
      mApi.addEventListener(event.key, 'click', (ctx, ...arg) => {
        console.log('decorator: clicked', ctx, arg);
      });
    }
  }
};
const plugins = [{ componentPlugin }];
const App = sifoAppDecorator('test-sifo-decorator', {
  externals: { aa: 1 },
  plugins,
  fragments: ['$dynamic_panel', '$static_panel'],
  class: "decorator-test",
  openLogger: false
})(TestDecorator);
export default App;
  1. TestDecorator 组件
<template>
  <div>
    <div>
      <span>count:{{ count }}</span>
      <button v-bind:style="{ margin: '4px 8px' }" v-on:click="count++">
        add count
      </button>
    </div>
    <div>
      <button v-bind:style="{ margin: '4px 0' }" @click="click">
        fire click method
      </button>
    </div>
    <div>
      不传参片段:
      <component v-bind:is="staticFragment"></component>
    </div>
    <div>
      动态传参数片段:
      <component v-bind:is="getDynamicFragment()"></component>
    </div>
  </div>
</template>
//
<script--xxx-->
// this is the Vue Component who will be decorated.
const TestDecorator = {
  name: "decorator-test",
  components: {},
  data: function () {
    return {
      count: 0,
      staticFragment: this.sifoApp.getFragment("$static_panel"),
    };
  },
  created: function () {
    this.sifoApp.watch("updateData", (ctx, key, val) => {
      if (key === "count") {
        this.count++;
      }
    });
    this.sifoApp.watch("getData", (e, getter) => {
      getter({
        count: this.count,
      });
    });
    // prepose传入true可使事件先于扩展件注册,在希望外部能够覆盖(扩展)内部方法时可使用
    this.clickFn = this.sifoApp.addEventListener("click", (...args) => {
      console.log("target: clicked");
    }, true);
  },
  methods: {
    click: function (...args) {
      // 建议不要直接在模板上绑定clickFn,否则可能带来非预期问题
      this.clickFn(...args);
    },
    getDynamicFragment: function () {
      return this.sifoApp.getFragment("$dynamic_panel", {
        value: `count: ${this.count}`,
      });
    },
  },
  destroyed() {
    console.log("destroyed");
  },
  // declare sifoApp property
  props: ["sifoApp"],
};
export default TestDecorator;
</script--xxx-->
  1. sifoAppDecorator 下的外部扩展示例
import SifoSingleton from '@schema-plugin-flow/sifo-singleton';
import { Input, Button } from "ant-design-vue";
//
const customOnChange = (context, e) => {
  const { event, mApi } = context;
  const { key } = event;
  mApi.setAttributes(key, { value: e.target.value + 'extVal' });
}
const pagePlugin = {
  onNodePreprocess: (node, info) => {
    const { id, component } = node;
    if (id === '$sifo-header') {
      return {
        ...node,
        attributes: {
          style: {
            color: "green"
          }
        },
        children: ['这是扩展的header']
      }
    }
    if (id === '$dynamic_panel' || id === '$static_panel') {
      // 将片段直接换成新的组件,这个组件就可以拿到getFragment的参数
      return {
        ...node,
        component: 'Input',
        attributes: {
          ...node.attributes,
          others: {
            ok: false
          }
        }
      }
    }
    if (id === '$sifo-footer') {
      return {
        ...node,
        attributes: {
          style: {
            border: "1px solid green",
            padding: "4px"
          }
        },
        children: [
          {
            component: 'div',
            children: ['这是扩展的footer']
          },
          {
            component: 'Button',
            id: 'updateDataBtn',
            children: ['updateCount']
          }]
      }
    }
    return node;
  },
}
const componentPlugin = {
  'test-sifo-decorator': {
    onComponentInitial: params => {
      const { event, mApi } = params;
      let fcount = 0;
      mApi.addEventListener(event.key, 'click', (context, e) => {
        //context.event.stop();
        console.log('ext: click', context, e);
        mApi.setAttributes('$static_panel', {
          value: `ext click fired: ${++fcount}`
        });
      });
    }
  },
  $dynamic_panel: {
    onComponentInitial: params => {
      const { event, mApi } = params;
      mApi.addEventListener(event.key, 'change', customOnChange);
    }
  },
  $static_panel: {
    onComponentInitial: params => {
      const { event, mApi } = params;
      mApi.addEventListener(event.key, 'change', customOnChange);
    }
  },
  updateDataBtn: {
    onComponentInitial: params => {
      const { event, mApi } = params;
      mApi.addEventListener(event.key, 'click', () => {
        mApi.dispatchWatch('getData', data => {
          console.log('old data:', data);
        });
        mApi.dispatchWatch('updateData', 'count');
      });
    }
  }
};
const singleton = new SifoSingleton('test-sifo-decorator');
singleton.registerItem('ccc', () => {
  return {
    plugins: [
      {
        pagePlugin,
        componentPlugin
      }
    ],
    components: {
      Input, Button
    },
    openLogger: true
  };
});
//
export default singleton;