Ques

Road to the future.

 
> 本页面由Ques生成,有兴趣可以看看源码是如何写的:https://github.com/miniflycn/Ques/blob/master/src/components.html

### UI组件

> `UI组件`是用来专门做UI的组件,其特点为只有模版、样式文件。系统会根据用户在页面已使用的`UI组件`动态引用其依赖的资源。注意,`UI组件`的前缀必须是`ui-`。

下面是一个简单的`ui-button`的例子:

##### 定义

* CSS文件
 
  
.ui-button {
    display: inline-block;
    padding: 6px 12px;
    margin-bottom: 0;
    font-size: 14px;
    font-weight: 400;
    line-height: 1.42857143;
    text-align: center;
    white-space: nowrap;
    vertical-align: middle;
    touch-action: manipulation;
    cursor: pointer;
    user-select: none;
    background-image: none;
    border: 1px solid transparent;
    border-radius: 4px;
    text-transform: none;
    -webkit-appearance: button;
    overflow: visible;
    margin: 0;
}

.ui-default {
    color:#333;
    background-color:#fff;
    border-color:#ccc
}
.ui-default.focus,.ui-default:focus {
    color:#333;
    background-color:#e6e6e6;
    border-color:#8c8c8c
}
.ui-default:hover {
    color:#333;
    background-color:#e6e6e6;
    border-color:#adadad
}

// 后面可添加更多样式的按钮
 
 
* 模版文件
 
  
<button class="ui-button">
    <content></content>
</button>
 
 
##### 效果

* 在页面上直接引用:
 
  
    <ui-button class="ui-default">DEFAULT</ui-button>
    <ui-button class="ui-success">SUCCESS</ui-button>
    <ui-button class="ui-info">INFO</ui-button>
    <ui-button class="ui-warning">WARNING</ui-button>
    <ui-button class="ui-danger">DANGER</ui-button>
 
 
* 展示
 
 
这样我们就实现了一个`ui-button`组件,其可以在任意其他组件中嵌套使用。其依赖的资源会动态引用,也就是说,只有我们使用了`ui-button`他的模版和样式才会被引入。

##### 备注

* 由于我们使用了强大的[cssnext](https://github.com/cssnext/cssnext),所以CSS吐出来的时候会根据配置转换成兼容版本,也就是说我们只需要按照标准去写CSS,系统会自动帮我们适配:
 
  
.ui-button {
    display: inline-block;
    padding: 6px 12px;
    margin-bottom: 0;
    font-size: 14px;
    font-weight: 400;
    line-height: 1.42857143;
    text-align: center;
    white-space: nowrap;
    vertical-align: middle;
    -ms-touch-action: manipulation;
        touch-action: manipulation;
    cursor: pointer;
    -webkit-user-select: none;
       -moz-user-select: none;
        -ms-user-select: none;
            user-select: none;
    background-image: none;
    border: 1px solid transparent;
    border-radius: 4px;
    text-transform: none;
    -webkit-appearance: button;
    overflow: visible;
    margin: 0;
}

.ui-default {
    color:#333;
    background-color:#fff;
    border-color:#ccc
}
.ui-default.focus,.ui-default:focus {
    color:#333;
    background-color:#e6e6e6;
    border-color:#8c8c8c
}
.ui-default:hover {
    color:#333;
    background-color:#e6e6e6;
    border-color:#adadad
}
 
 
* 注意到我们引入了Shadow DOM中的`<content>`标签,`<content>`标签作为Component内部的插入点(或者可以理解成占位符),当外部引用该Component时可以从外部向内部插入节点,例如:
 
  
<ui-button class="ui-default">DEFAULT</ui-button>
 
 
则表示向Component的插入点插入DEFAULT这个文字节点。关于`<content>`标签我们后面还会提到其高级应用。

### Component

> Component是最常见的组件,其拥有模版、样式以及逻辑文件,使得这种Component更像一个自定义的元素(Custom Element)。体验上像引入一个`<input>`标签一样,我们可以设置她的值,绑定她的事件,调用她的函数。

下面是一个`dialog`组件的例子:

##### 定义

* CSS文件:
 
  
.$__mask {
    position: fixed;
    width: 100%;
    height: 100%;
    padding: 0px;
    margin: 0px;
    left: 0px;
    top: 0px;
    z-index: 999;
    background-color: rgba(0,0,0,.6) !important;
    display: none;
}

.$__mask.show {
    display: block;
}

.$__$ {
    position: fixed;
    top: 10%;
    opacity: .5;
    left: 50%;
    width: 490px;
    margin-left: -245px;
    z-index: 999;
    background: #fff;
    font-size: 14px;
    border-radius: 4px;
    overflow: hidden;
    transition: all 200ms ease-in-out;
}

.$__mask .$__$.show {
    top: 50%;
    opacity: 1;
}

.$__$ .header {
    height: 30px;
    line-height: 30px;
    text-indent: 12px;
    background: #039ae3;
    color: #fff;
    font-size: 14px;
}

.$__$ .body {
    padding: 30px 40px;
    position: relative;
    line-height: 24px;
    max-height: 500px;
    overflow-y: auto;
    overflow-x: hidden;
}

.$__$ .msg {
    margin-left: 66px;
    position: relative;
    top: 10px;
    word-break: break-all;
}

.$__$ .bottom {
    margin: 20px;
    text-align: right;
}

.icon-info-large {
    background: url(http://9.url.cn/edu/img/sprite/common.a8642.png) -41px -276px no-repeat;
    width: 36px;
    height: 36px;
    display: block;
    float: left;
    margin-top: 4px;
}
 
 
* 模版文件:
 
  
<div class="$__mask" q-class="show: isShow">
    <div class="$__$">
        <div class="header">
            <content select="header *"></content>
        </div>
        <div class="body">
            <div class="icon-info-large"></div>
            <div class="msg">
                <content select="article *"></content>
            </div>
        </div>
        <div class="bottom">
            <ui-button class="ui-info" q-on="click: submit">确定</ui-button>
            <ui-button class="ui-default" q-on="click: cancel">取消</ui-button>
        </div>
    </div>
</div>
 
 
* Javascript文件:
 
  
var $ = require('jquery');

module.exports = {
    data: {
        isShow: false
    },
    methods: {
        submit: function () {
            this.$emit('submit');
        },
        cancel: function () {
            this.$emit('cancel')
                .hide();
        },
        show: function () {
            this.$set('isShow', true);
        },
        hide: function () {
            this.$set('isShow', false);
        }
    },
    ready: function () {
        var dialog = $('.$__$', this.$el);
        this.$watch('isShow', function (val) {
            if (val) {
                setTimeout(function () {
                    dialog.addClass('show');
                }, 20);
            } else {
                dialog.removeClass(dialog, 'show');
            }
        }, false, true);
    }
}
 
 
##### 效果

* 在页面直接引入`<dialog>`:
 
  
<dialog id="my-dialog">
    <header>
        欢迎使用Ques
    </header>
    <article>
        Hello World!
    </article>
</dialog>
 
 
* 则可以在Controller中直接使用,例如拿到其实例,再调用其`show`方法,将其展示:
 
  
var Q = require('Q');

Q.get('#my-dialog')
    .show();
 
欢迎使用Ques
Hello World!
 
##### 备注

* 由于CSS没有命名空间,所以我们引入了两个`$__`和`$__$`两个占位符来充当命名空间,系统会自动转换成当前Component的名字,所以CSS最终变成:
 
  
.dialog__mask {
    position: fixed;
    width: 100%;
    height: 100%;
    padding: 0px;
    margin: 0px;
    left: 0px;
    top: 0px;
    z-index: 999;
    background-color: #000000 !important;
    background-color: rgba(0,0,0,.6) !important;
    display: none;
}

.dialog__mask.show {
    display: block;
}

.dialog {
    position: fixed;
    top: 10%;
    opacity: .5;
    left: 50%;
    width: 490px;
    margin-left: -245px;
    z-index: 999;
    background: #fff;
    font-size: 14px;
    border-radius: 4px;
    overflow: hidden;
    -webkit-transition: all 200ms ease-in-out;
            transition: all 200ms ease-in-out;
}

.dialog__mask .dialog.show {
    top: 50%;
    opacity: 1;
}

.dialog .header {
    height: 30px;
    line-height: 30px;
    text-indent: 12px;
    background: #039ae3;
    color: #fff;
    font-size: 14px;
}

.dialog .body {
    padding: 30px 40px;
    position: relative;
    line-height: 24px;
    max-height: 500px;
    overflow-y: auto;
    overflow-x: hidden;
}

.dialog .msg {
    margin-left: 66px;
    position: relative;
    top: 10px;
    word-break: break-all;
}

.dialog .bottom {
    margin: 20px;
    text-align: right;
}

.icon-info-large {
    background: url(http://9.url.cn/edu/img/sprite/common.a8642.png) -41px -276px no-repeat;
    width: 36px;
    height: 36px;
    display: block;
    float: left;
    margin-top: 4px;
}
 
 
可以看见`$__`被转换成了`dialog__`,而`$__$`被转换成了`dialog`。

* 逻辑层我们使用了MVVM库[Q.js](https://github.com/imweb/Q.js),这里就不细说了。


* 这里还用到`<content>`标签的高级功能,`select`属性。select属性是用来选择外部符合选择器的节点进行替换。例如:
 
  
<content select="header *"></content>
 
 
的意思是选择外部`<header>`标签内所有东西进行替换,所以“欢迎使用Ques”就被替换进去了。

### 第三方Component

> 第三方Component是一套兼容方案,使得业务方不依赖`Q.js`也能定义一个Component,但是由于系统无法控制第三方组件DOM的作用域,以及实现其扩展似乎没啥意思,所以第三方无法嵌套和扩展。总的来说第三方Component本质上就是系统给第三方一个容器,他在里面干什么,系统就不管了。第三方组件一定以`third-`为前缀。

下面是一个高亮代码`third-code`的例子:

##### 定义

* CSS文件:
 
  
.$__pre {
    width: 900px;
    margin: 10px;
}

/**  引入hightlight.js的css库  **/
@import "http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.6/styles/default.min.css";
 
 
* 模版文件:
 
  
<pre class="$__pre">
    <code>
        <content></content>
    </code>
</pre>
 
 
* Javascript文件:
 
  
module.exports = {
    bind: function () {
        var el = this.el,
            script = document.createElement('script');
        script.onload = function () {
            hljs.highlightBlock(el);
        }
        script.src = '//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.6/highlight.min.js';
        document.body.appendChild(script);
    },
    unbind: function () {}
};
 
 
##### 效果

* 引入`third-code`:
 
  
<third-code>
    &lt;ui-button class=&quot;ui-default&quot;&gt;DEFAULT&lt;/ui-button&gt;
    &lt;ui-button class=&quot;ui-success&quot;&gt;SUCCESS&lt;/ui-button&gt;
    &lt;ui-button class=&quot;ui-info&quot;&gt;INFO&lt;/ui-button&gt;
    &lt;ui-button class=&quot;ui-warning&quot;&gt;WARNING&lt;/ui-button&gt;
    &lt;ui-button class=&quot;ui-danger&quot;&gt;DANGER&lt;/ui-button&gt;
</third-code>
 
 
* 效果:
 
  
    <ui-button class="ui-default">DEFAULT</ui-button>
    <ui-button class="ui-success">SUCCESS</ui-button>
    <ui-button class="ui-info">INFO</ui-button>
    <ui-button class="ui-warning">WARNING</ui-button>
    <ui-button class="ui-danger">DANGER</ui-button>
 
 
##### 备注

* 第三方Component需要实现两个接口`bind`和`unbind`,即在容器创建时需要绑定什么,当容器删除时需要解绑什么。this会带来必要的东西,例如容器、父级ViewModel等等。

### 组件嵌套

> 当组件可以嵌套,组件件可以拆成较小的颗粒,使得复用性大大提升。

下面我们是一个`clickbutton`组件,其用途是显示按钮,点击按钮弹出dialog,也就是说我们准备嵌套上面做出来的`ui-button`和`dialog`组件:

##### 定义

* CSS文件:
 
  
/** 可以给组件定义一些特殊样式,但为了简单我们什么也不做 **/
 
 
* 模版文件:
 
  
<div>
    <ui-button class="ui-info" q-on="click: showDialog">
        <content></content>
    </ui-button>
    <dialog q-ref="dialog">
        <header>欢迎使用Ques</header>
        <article>你点击了按钮</article>
    </dialog>
</div>
 
 
* Javascript文件:
 
  
var $ = require('jquery');

module.exports = {
    data: {},
    methods: {
        showDialog: function () {
            this.$.dialog.show();
        }
    }
};
 
 
##### 效果

* 在页面上引用:
 
  
<clickbutton>点击按钮</clickbutton>
 
 
* 展示:
 
欢迎使用Ques
你点击了按钮
 
##### 备注

* 我们看到`<content>`标签另一个神奇的用法是可传递,我们从`ui-button`传递到`clickbutton`,再传递到最外部。使得我们可以在最外部改`ui-button`内部的节点。


* 我们注意到`q-ref`本来是`Q.js`用于组件嵌套从母Component(为了和扩展中的父Component其分开来,这里称之为母Component)拿到子Component的引用,同样可以拿到第三方Component的引用。

### 组件扩展

> 组件可扩展,则差别不大的组件可以继承同一个父组件。

下面`dialog`组件扩展的例子,效果是弹出一个dialog,要求输入内容:

##### 定义

* CSS文件:
 
  
/** 同样为了简单我们什么也不做 **/
 
 
* 模版文件:
 
  
<dialog extend>
    <header>
        <h2>欢迎使用Ques</h2>
    </header>
    <article>
        <p>请输入要设置的值</p>
        <ui-input value="" q-model="curVal" q-on="keyup: submit | key enter" q-focus="focus"></ui-input>
    </article>
</dialog>
 
 
* Javascript文件:
 
  
var filters = require('filters');

module.exports = {
    methods: {
        submit: function () {
            if (!this.curVal) {
                this.$set('focus', true);
            } else {
                this.$emit('submit', this.curVal);
                this.$set('curVal', '');
                this.hide();
            }
        },
        show: function () {
            // call super.show
            this.constructor.super.options.methods.show.call(this);
            this.$set('focus', true);
        }
    },
    directives: {
        focus: function (val) {
            val && this.el.focus();
        }
    },
    filters: {
        key: filters.key
    }
};
 
 
##### 效果

* 在页面上引用`inputval`:
 
  
<inputval id="my-dialog"></inputval>
 
 
* 在Controller调用其show方法:
 
  
var Q = require('Q');

Q.get('#my-dialog').show();
 
 
则页面弹出一个弹出,要求输入值:
 

欢迎使用Ques

请输入要设置的值

 
##### 备注

* 这里我们引入`extend`属性,用于表示该组件继承哪个组件。


* 我们还复写了`dialog`的`submit`和`show`方法,并且可以调用其父Componnet的方法,如:
 
  
this.constructor.super.options.methods.show.call(this);