Ques是一套组件化系统,解决如何定义、嵌套、扩展、使用组件。javascript
Javascript
、Template
、CSS
文件MV*
的方式去写代码,结果发现只有Javascript
是MV*
Bootstrap
),可是实际上UI库伴随产品迭代在反复变动,每次打开网站,用户依然反复下载UI库CSS
没有命名空间致使两个组件容易冲突
UI组件
使用来专门作UI的组件,其特色为只有模版、样式文件。系统会根据用户在页面已使用的UI组件
动态引用其依赖的资源。注意,UI组件
的前缀必须是ui-
。css
下面是一个简单的ui-button
的例子:html
.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
他的模版和样式才会被引入。java
.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 }
<content>
标签,<content>
标签做为Component内部的插入点(或者能够理解成占位符),当外部引用该Component时能够从外部向内部插入节点,例如:<ui-button class="ui-default">DEFAULT</ui-button>
则表示向Component的插入点插入DEFAULT这个文字节点。关于<content>
标签咱们后面还会提到其高级应用。jquery
Component是最多见的组件,其拥有模版、样式以及逻辑文件,使得这种Component更像一个自定义的元素(Custom Element)。体验上像引入一个
<input>
标签同样,咱们能够设置她的值,绑定她的事件,调用她的函数。git
下面是一个dialog
组件的例子:github
.$__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>
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>
show
方法,将其展现:var Q = require('Q'); Q.get('#my-dialog') .show();
$__
和$__$
两个占位符来充当命名空间,系统会自动转换成当前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
。web
逻辑层咱们使用了MVVM库Q.js,这里就不细说了。ajax
这里还用到<content>
标签的高级功能,select
属性。select属性是用来选择外部符合选择器的节点进行替换。例如:json
<content select="header *"></content>
的意思是选择外部<header>
标签内全部东西进行替换,因此“欢迎使用Ques”就被替换进去了。
第三方Component是一套兼容方案,使得业务方不依赖
Q.js
也能定义一个Component,可是因为系统没法控制第三方组件DOM的做用域,以及实现其扩展彷佛没啥意思,因此第三方没法嵌套和扩展。总的来讲第三方Component本质上就是系统给第三方一个容器,他在里面干什么,系统就无论了。第三方组件必定以third-
为前缀。
下面是一个高亮代码third-code
的例子:
.$__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>
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> <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> </third-code>
bind
和unbind
,即在容器建立时须要绑定什么,当容器删除时须要解绑什么。this会带来必要的东西,例如容器、父级ViewModel等等。当组件能够嵌套,组件件能够拆成较小的颗粒,使得复用性大大提高。
下面咱们是一个codeclick
组件,其用途是高亮展现代码片断,点击代码弹出dialog,也就是说咱们准备嵌套上面作出来的third-code
和dialog
组件:
/** 能够给组件定义一些特殊样式,但为了简单咱们什么也不作 **/
<div> <third-code q-ref="code"> <content></content> </third-code> <dialog q-ref="dialog"> <header>欢迎使用Ques</header> <article>你点击了代码</article> </dialog> </div>
var $ = require('jquery'); module.exports = { data: {}, ready: function () { var code = this.$.code, dialog = this.$.dialog; // 点击代码,弹出dialog $(code.el).on('click', function () { dialog.show(); }); } };
<codeclick>
<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> </codeclick>
咱们看到<content>
标签另外一个神奇的用法是可传递,咱们从third-code
传递到codeclick
,再传递到最外部。使得咱们能够在最外部改third-code
内部的节点。
咱们注意到q-ref
原本是Q.js
用于组件嵌套从母Component(为了和扩展中的父Component其分开来,这里称之为母Component)拿到子Component的引用,一样能够拿到第三方Component的引用。
组件可扩展,则差异不大的组件能够继承同一个父组件。
下面dialog
组件扩展的例子,效果是弹出一个dialog,要求输入内容:
/** 一样为了简单咱们什么也不作 **/
<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>
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>
var Q = require('Q'); Q.get('#my-dialog').show();
这里咱们引入extend
属性,用于表示该组件继承哪一个组件。
咱们还复写了dialog
的submit
和show
方法,而且能够调用其父Componnet的方法,如:
this.constructor.super.options.methods.show.call(this);
咱们能够嵌套使用扩展后的组件。
下面是一个复杂的例子,一个按钮点击后弹出inputval
,输入后alert出输入的内容。
.$__anchor { padding: 13px 35px 17px; box-shadow: inset 0 -4px 0 #2a6496; border: 0; color: #fff; transition: all .2s ease-in-out; background-color: #337ab7; border-color: #2e6da4; display: block; 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; border-radius: 4px; text-decoration: none; margin: 0 auto; } .$__anchor:hover, .$__anchor:active .$__anchor:focus { background-color: #286090; border-color: #204d74; }
<div>
<a href="javascript:void(0);" class="$__anchor" q-on="click: setMessage"> <content></content> </a> <inputval q-ref="input"></inputval> </div>
module.exports = { data: {}, methods: { setMessage: function (e) { var self = this; this.$.input.$once('submit', function (value) { value && alert('输入了:' + value); }); this.$.input.show(); } } };
<clkme>请点击我</clkme>
DIY组件容许页面经过Markup的方法引用NodeJS组件,这样咱们可使用该NodeJS组件分析咱们的代码来作一些神奇的事情。
例如咱们提供的diy-preload
组件提供的CGI预加载方案,整个过程没有改变开发人员的编码体验,只是简简单单标记一下哪一个CGI须要预加载,则系统会自动预加载CGI。
diy-preload
组件<diy-preload></diy-preload>
db.*.js
,的对应CGI设置成preload = true: var DB = require('db'); DB.extend({ ke: DB.httpMethod({ url: 'http://ke.qq.com/cgi-bin/index_json', type: 'JSONP', preload: true }) }) module.exports = DB;
diy-preload
组件会找到db.*.js
,而后分析出什么CGI须要预加载,在<diy-preload>
标签对应的位置插入预加载脚本。整个过程开发人员感知不到,体验上仍是调取一个CGI,可是实际上在页面文档打开后CGI请求马上发出,而不是等待Javascript加载完后才发出。