├── components ├── gf-aaa │ ├── gf-aaa.html │ └── gf-aaa.js ├── common-sidebar │ ├── common-sidebar.html │ └── common-sidebar.js ├── common-header │ ├── common-header.js │ └── common-header.html ├── gf-demo-fast │ ├── gf-demo-fast.js │ └── gf-demo-fast.html ├── gf-dashboard │ ├── gf-dashboard.js │ └── gf-dashboard.html ├── doc-ms-table │ ├── doc-ms-table.html │ └── doc-ms-table.js ├── doc-ms-form │ ├── doc-ms-form.js │ └── doc-ms-form.html ├── gf-demo │ ├── gf-demo.js │ └── gf-demo.html ├── gf-demo-redux │ ├── gf-demo-redux.html │ └── gf-demo-redux.js └── common-curd │ └── common-curd.js ├── vendor └── jquery │ ├── jquery.minicolors.png │ └── jquery.minicolors.js ├── services ├── configService.js ├── filterService.js ├── ajaxService.js ├── routerService.js ├── storeService.js └── menuService.js ├── tsconfig.json ├── mock ├── server.conf ├── update.json ├── file │ └── insert.js ├── loged.json └── sample.json ├── pages ├── rxjs-demo │ ├── rxjs-demo.html │ └── rxjs-demo.js └── redux-demo │ ├── redux-demo.html │ └── redux-demo.js ├── typings ├── index.d.ts ├── mmRouter.d.ts └── avalon.d.ts ├── .deploy.sh ├── .travis.yml ├── LICENSE ├── index.js ├── .gitignore ├── package.json ├── README.md ├── static ├── uilog.js ├── mod.js └── polyfill │ ├── respond.src.js │ └── html5shiv.js ├── index.html └── fis-conf.js /components/gf-aaa/gf-aaa.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/jquery/jquery.minicolors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxapp/ms-bus/HEAD/vendor/jquery/jquery.minicolors.png -------------------------------------------------------------------------------- /services/configService.js: -------------------------------------------------------------------------------- 1 | export const pageSize = 10; 2 | 3 | export const domain = '__DOMAIN__'; 4 | 5 | export const serviceUrl = '__SERVICE_URL__'; -------------------------------------------------------------------------------- /components/common-sidebar/common-sidebar.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es3", 4 | "module": "commonjs" 5 | }, 6 | "typeRoots": [ 7 | "typings", 8 | "../node_modules/@types" 9 | ] 10 | } -------------------------------------------------------------------------------- /components/gf-aaa/gf-aaa.js: -------------------------------------------------------------------------------- 1 | import avalon from 'avalon2'; 2 | 3 | export const name = 'gf-aaa'; 4 | 5 | avalon.component(name, { 6 | template: __inline('./gf-aaa.html'), 7 | defaults: { 8 | text: 'aaa' 9 | } 10 | }); -------------------------------------------------------------------------------- /mock/server.conf: -------------------------------------------------------------------------------- 1 | rewrite ^\/api\/demo$ /mock/sample.json 2 | rewrite ^\/api\/demo/update$ /mock/update.json 3 | rewrite ^\/api\/loged$ /mock/loged.json 4 | 5 | rewrite ^\/api\/file\/uploadFile$ /mock/file/insert.js 6 | 7 | rewrite ^/search/repositories$ /mock/github/repository.json -------------------------------------------------------------------------------- /mock/update.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": "0", 3 | "t": { 4 | "region_id": "635", 5 | "region_parent_id": "67", 6 | "region_name": "永昌县", 7 | "region_nick": null, 8 | "region_type": "2", 9 | "region_enble": 1 10 | } 11 | } -------------------------------------------------------------------------------- /mock/file/insert.js: -------------------------------------------------------------------------------- 1 | module.exports = function(req, res, next) { 2 | res.set('Content-Type', 'text/html'); 3 | // res.setStatus('404'); 4 | setTimeout(function () { 5 | res.json({ 6 | "url": "https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" 7 | }); 8 | }, 500); 9 | }; -------------------------------------------------------------------------------- /components/common-header/common-header.js: -------------------------------------------------------------------------------- 1 | import avalon from 'avalon2'; 2 | import ajax from '../../services/ajaxService'; 3 | 4 | export const name = 'common-header'; 5 | 6 | avalon.component(name, { 7 | template: __inline('./common-header.html'), 8 | defaults: { 9 | currentUserName: '', 10 | logout() { 11 | global.sessionStorage.removeItem('adminSession'); 12 | global.location.href = '/login.html'; 13 | } 14 | } 15 | }); -------------------------------------------------------------------------------- /components/gf-demo-fast/gf-demo-fast.js: -------------------------------------------------------------------------------- 1 | import avalon from 'avalon2'; 2 | 3 | import 'ane'; 4 | import curdComponent from '../common-curd/common-curd'; 5 | import { demo as demoStore } from '../../services/storeService'; 6 | 7 | export const name = 'gf-demo-fast'; 8 | 9 | curdComponent.extend({ 10 | displayName: name, 11 | template: __inline('./gf-demo-fast.html'), 12 | defaults: { 13 | $store: demoStore, 14 | pattern: /^\d+-\d+-\d+( \d+:\d+:\d+)?$/ 15 | } 16 | }); -------------------------------------------------------------------------------- /services/filterService.js: -------------------------------------------------------------------------------- 1 | import avalon from 'avalon2'; 2 | 3 | avalon.filters.showPrices = function (priceList) { 4 | let prices = ''; 5 | if (!priceList) { 6 | return prices; 7 | } 8 | for (let i = 0; i < priceList.length; i++) { 9 | if (i !== 0) { 10 | prices += ','; 11 | } 12 | prices += priceList[i].discount_price + '/' + priceList[i].count_unit; 13 | } 14 | return prices; 15 | } 16 | 17 | avalon.filters.decodeHTML = function (str) { 18 | return decodeURIComponent(str); 19 | } -------------------------------------------------------------------------------- /pages/rxjs-demo/rxjs-demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | RxJS Demo 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /typings/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | // FIS3 inline syntax 6 | declare var __inline 7 | 8 | // runtime global 9 | 10 | interface MyWindow extends Window { 11 | Promise: Promise, 12 | $, 13 | jQuery, 14 | __REDUX_DEVTOOLS_EXTENSION__ 15 | } 16 | 17 | declare var global: MyWindow 18 | 19 | declare var require 20 | 21 | declare var module: { 22 | exports: any 23 | } 24 | 25 | declare var exports: any 26 | 27 | interface JQueryStatic { 28 | ajaxFileUpload 29 | } -------------------------------------------------------------------------------- /.deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 使用fis进行构建 4 | fis3 release gh-pages -cd ./output 5 | 6 | if [ -d output ]; then 7 | # 提交代码至gh-pages分支 8 | echo "➥ Commit files" 9 | git clone --quiet --branch=gh-pages https://${GH_TOKEN}@github.com/${REPO_SLUG}.git ${REPO_SLUG} > /dev/null 10 | rm -rf ${REPO_SLUG}/* 11 | cp -rf output/* ${REPO_SLUG} 12 | cd ${REPO_SLUG} 13 | git status 14 | git config user.email "travis@travis-ci.org" 15 | git config user.name "travis-ci" 16 | git add -A 17 | git commit -m "[ci skip] publish gh-pages" 18 | git push -fq origin gh-pages > /dev/null 19 | else 20 | echo '➥ Fail' 21 | exit 1 22 | fi -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | env: 5 | global: 6 | - GH_PAGES_DEPLOY: YES 7 | - PATH: node_modules/.bin/:$PATH 8 | - REPO_SLUG: xxapp/ms-bus 9 | #- secure: dRG3aZiBO9p7oxHXJ/JLI6I6IbmFFrFs380R17upqlWZpCFnCTyrHVm+nqXJEOWn3cpwtMs2lZRffXUgA/a9nhoecsK7YTbaZBJFGn4Oz2nIupz9eq9xDq2NiJwfj3s9qUBYANaVXc9n2crX60zgmZTW0msVhl19Bl/8dYLOoMdhtUZyfBp0drYY0lGMAdD0fha16FbsKwu/eTpXXpip7sIFfNoDYNG65Yrrsy89rQShI3CuGDVuTBjJYqA+aJr04i8I2OcXK0Qf5YOmU1R6UJv/m6iRB1tJguVun4psylH6iXkHtnWipNpkKHUJ1nNv5W5Zz0ru0SD4ZF7ZksO9FCyoTlyavSD1h5XIEsirJTBByJ8MEaC4hOx9/2JwFcqL9ZZn2tgyIshCVUQpCyrJ7Rcyt8yj2PiaI0/IwOr6nLFPkspR3JL5700qAhMdTQynl/1KMdUPjQHSsVUky9AKeOCRSUBX89TyILHnismv6sb3ny2Yq8Eao3Ped2o0Xne/F8Lq8TdSR/Pxlu7QYfjQz5agaAmeO/z+HOYh5sx16viDcxZzWa9n9HLbbP1eZwdg+Xj5eYvlcHGS9mIR0J+GNXbotaR+XuWc4TJikOEwT/SzUtM9hRaHhnUwuf+Ffe3jMxFS33MRBeLjPn5onObc/QN5y0ftUTAdMrh3FwztEAg= 10 | install: 11 | - npm install fis3 12 | - fis3 -v 13 | - npm i 14 | script: sh .deploy.sh 15 | cache: 16 | directories: 17 | - node_modules 18 | branches: 19 | only: 20 | - master -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 PIEr_xx 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import 'es5-shim'; 2 | import 'es6-promise/dist/es6-promise.auto'; 3 | import jQuery from 'jquery'; 4 | global.$ = global.jQuery = jQuery; 5 | require('bootstrap'); 6 | // 提前禁止avalon对Object.create的实现 7 | if (!Object.create) { 8 | Object.create = function () { 9 | function F() {} 10 | 11 | return function (o) { 12 | F.prototype = o; 13 | return new F(); 14 | }; 15 | }(); 16 | } 17 | var avalon = require('avalon2'); 18 | if (avalon.msie < 8) { 19 | Object.defineProperty = function (obj, property, meta) { 20 | obj[property] = meta.value; 21 | } 22 | } 23 | require('es5-shim/es5-sham'); 24 | require('./services/routerService'); 25 | let { breadcrumb } = require('./services/storeService'); 26 | require('ane/dist/layout'); 27 | 28 | const root = avalon.define({ 29 | $id: 'root', 30 | currentPage: '', 31 | breadcrumb: [], 32 | user: {} 33 | }); 34 | breadcrumb.list$.subscribe(v => { 35 | root.breadcrumb = v; 36 | }); 37 | avalon.history.start({ 38 | fireAnchor: false 39 | }); 40 | if (!/#!/.test(global.location.hash)) { 41 | avalon.router.navigate('/', 2); 42 | } 43 | avalon.scan(document.body); -------------------------------------------------------------------------------- /components/gf-dashboard/gf-dashboard.js: -------------------------------------------------------------------------------- 1 | import avalon from 'avalon2'; 2 | import { createForm, notification } from 'ane'; 3 | import ajax from '../../services/ajaxService' 4 | 5 | export const name = 'gf-dashboard'; 6 | 7 | avalon.component(name, { 8 | template: __inline('./gf-dashboard.html'), 9 | defaults: { 10 | show: false, 11 | message: '欢迎', 12 | masterpiece: ['ane', 'ms-bus'], 13 | handleCancel(e) { 14 | //console.log(e); 15 | this.show = false; 16 | }, 17 | handleSelectChange(e) { 18 | console.log(e.target.value); 19 | }, 20 | fetchOptions(query) { 21 | return ajax({ 22 | url: 'https://randomuser.me/api/?results=5', 23 | }).then(body => { 24 | return body.data.results.map(user => ({ 25 | label: `${user.name.first}${user.name.last}`, 26 | value: user.login.username 27 | })); 28 | }); 29 | } 30 | } 31 | }); 32 | 33 | avalon.define({ 34 | $id: 'dashboard_from', 35 | title: 'Title', 36 | $form: createForm({ 37 | onFieldsChange(fields) { 38 | console.log(this.record); 39 | } 40 | }) 41 | }); -------------------------------------------------------------------------------- /components/common-sidebar/common-sidebar.js: -------------------------------------------------------------------------------- 1 | import avalon from 'avalon2'; 2 | 3 | import * as menuService from '../../services/menuService'; 4 | import { menu as menuStore } from '../../services/storeService'; 5 | import 'ane'; 6 | 7 | avalon.effect('collapse', { 8 | enter(elem, done) { 9 | $(elem).slideDown(200, done); 10 | }, 11 | leave(elem, done) { 12 | $(elem).slideUp(200, done); 13 | } 14 | }); 15 | 16 | export const name = 'common-sidebar'; 17 | 18 | avalon.component(name, { 19 | template: __inline('./common-sidebar.html'), 20 | defaults: { 21 | menu: [], 22 | selectedKeys: ['dashboard'], 23 | openKeys: [], 24 | handleMenuClick(item, key, keyPath) { 25 | avalon.history.setHash(item.uri); 26 | }, 27 | handleOpenChange(openKeys) { 28 | this.openKeys = openKeys.slice(-1); 29 | }, 30 | onInit(event) { 31 | menuService.menu.then((menu) => { 32 | this.menu = menu; 33 | }); 34 | menuStore.selectedKeys$.subscribe(v => { 35 | this.selectedKeys = v; 36 | }); 37 | menuStore.openKeys$.subscribe(v => { 38 | this.openKeys = v; 39 | }); 40 | } 41 | } 42 | }); -------------------------------------------------------------------------------- /components/gf-dashboard/gf-dashboard.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pages/redux-demo/redux-demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Redux Demo 8 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | title 18 | Hello 19 | 20 | 25 |

26 | Clicked: {{value}} times 27 | 28 | 29 | 30 | 31 |

32 |
33 | 36 | 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Nodejs 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | *.pid.lock 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # nyc test coverage 20 | .nyc_output 21 | 22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 23 | .grunt 24 | 25 | # node-waf configuration 26 | .lock-wscript 27 | 28 | # Compiled binary addons (http://nodejs.org/api/addons.html) 29 | build/Release 30 | 31 | # Dependency directories 32 | node_modules 33 | jspm_packages 34 | 35 | # Optional npm cache directory 36 | .npm 37 | 38 | # Optional eslint cache 39 | .eslintcache 40 | 41 | # Optional REPL history 42 | .node_repl_history 43 | 44 | ## MacOS 45 | *.DS_Store 46 | .AppleDouble 47 | .LSOverride 48 | 49 | # Icon must end with two \r 50 | Icon 51 | 52 | 53 | # Thumbnails 54 | ._* 55 | 56 | # Files that might appear in the root of a volume 57 | .DocumentRevisions-V100 58 | .fseventsd 59 | .Spotlight-V100 60 | .TemporaryItems 61 | .Trashes 62 | .VolumeIcon.icns 63 | .com.apple.timemachine.donotpresent 64 | 65 | # Directories potentially created on remote AFP share 66 | .AppleDB 67 | .AppleDesktop 68 | Network Trash Folder 69 | Temporary Items 70 | .apdisk 71 | 72 | ## VSC 73 | .vscode/* 74 | !.vscode/settings.json 75 | !.vscode/tasks.json 76 | !.vscode/launch.json -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ms-bus", 3 | "version": "0.1.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "fis3 server start -p 8081 & fis3 release -w" 9 | }, 10 | "author": "", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/xxapp/msBus.git" 14 | }, 15 | "license": "ISC", 16 | "dependencies": { 17 | "@types/jquery": "^2.0.41", 18 | "@types/rx": "^4.1.1", 19 | "addeventlistener-with-dispatch": "^1.0.2", 20 | "ane": "^0.1.1", 21 | "avalon2": "^2.2.7", 22 | "bootstrap": "^3.2.0", 23 | "es5-shim": "^4.5.9", 24 | "es6-promise": "^4.1.0", 25 | "font-awesome": "^4.7.0", 26 | "jquery": "^1.12.4", 27 | "mmRouter": "^0.9.6", 28 | "moment": "^2.17.1", 29 | "redux": "^3.6.0", 30 | "redux-thunk": "^2.2.0", 31 | "rx": "^4.1.0" 32 | }, 33 | "devDependencies": { 34 | "babel-plugin-add-module-exports": "^0.2.1", 35 | "babel-plugin-transform-es3-member-expression-literals": "^6.22.0", 36 | "babel-plugin-transform-es3-property-literals": "^6.22.0", 37 | "fis-parser-babel-6.x": "^6.24.1", 38 | "fis3-hook-commonjs": "^0.1.25", 39 | "fis3-hook-node_modules": "^2.2.8", 40 | "fis3-packager-deps-pack": "^0.1.2", 41 | "fis3-postpackager-loader": "^2.1.3", 42 | "fis3-postprocessor-component-view": "^1.0.8", 43 | "fis3-preprocessor-js-require-css": "^0.1.1" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pages/redux-demo/redux-demo.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'redux'; 2 | import avalon from 'avalon2'; 3 | 4 | import jQuery from 'jquery'; 5 | global.$ = global.jQuery = jQuery; 6 | import 'bootstrap/dist/css/bootstrap.css'; 7 | import 'bootstrap'; 8 | import { createForm } from 'ane'; 9 | 10 | function counter(state = 0, action) { 11 | switch (action.type) { 12 | case 'INCREMENT': 13 | return state + 1 14 | case 'DECREMENT': 15 | return state - 1 16 | default: 17 | return state 18 | } 19 | } 20 | const store = createStore( 21 | counter, 22 | global.__REDUX_DEVTOOLS_EXTENSION__ && global.__REDUX_DEVTOOLS_EXTENSION__() 23 | ) 24 | function render() { 25 | vm.value = store.getState(); 26 | } 27 | store.subscribe(render) 28 | 29 | const vm = avalon.define({ 30 | $id: 'demo', 31 | value: 0, 32 | title: 'hello', 33 | show: false, 34 | $form: createForm({ 35 | onFieldsChange(fields) { 36 | console.log(fields); 37 | console.log(this.record); 38 | } 39 | }), 40 | increment() { 41 | store.dispatch({ type: 'INCREMENT' }); 42 | }, 43 | decrement() { 44 | store.dispatch({ type: 'DECREMENT' }); 45 | }, 46 | incrementIfOdd() { 47 | if (store.getState() % 2 !== 0) { 48 | store.dispatch({ type: 'INCREMENT' }); 49 | } 50 | }, 51 | incrementAsync() { 52 | setTimeout(() => { 53 | store.dispatch({ type: 'INCREMENT' }); 54 | }, 1000); 55 | } 56 | }); -------------------------------------------------------------------------------- /typings/mmRouter.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | /** 4 | * avalon.history.start 配置项 5 | */ 6 | interface mmHistoryOptions { 7 | /** 8 | * 根路径 9 | */ 10 | root?: string, 11 | /** 12 | * 是否使用HTML 5 history 13 | */ 14 | html5?: boolean, 15 | /** 16 | * hash 前缀 17 | */ 18 | hashPrefix?: '!', 19 | /** 20 | * 滚动 21 | */ 22 | autoScroll?: boolean, 23 | fireAnchor: boolean 24 | } 25 | 26 | interface AvalonStatic { 27 | history: { 28 | hash: string, 29 | check: () => void, 30 | start: (options: mmHistoryOptions) => void, 31 | stop: () => void, 32 | setHash: (s: string, replace?: boolean) => void, 33 | writeFrame: (s: string) => void, 34 | syncHash: () => this, 35 | getPath: () => string, 36 | onHashChanged: (hash: string, clickMode: number) => void 37 | }, 38 | router: { 39 | getLastHash: () => string, 40 | setLastHash: (path: string) => void, 41 | /** 42 | * 当目标页面不匹配我们所有路由规则时, 就会执行此回调.有点像404 43 | * @param cb 回调 44 | */ 45 | error: (cb?: () => void) => void, 46 | /** 47 | * 添加 一个路由规则与对象的回调, cb为rule规则中捕捉的参数 48 | * @param rule 路由规则 49 | * @param cb 匹配时的回调 50 | */ 51 | add: (rule: string, cb?: (rule: object, args) => any, opts?) => void, 52 | /** 53 | * 手动触发对应的回调 54 | * @param hash 路径 55 | * @param mode 0或undefined, 不改变URL, 不产生历史实体, 执行回调; 1, 改变URL, 不产生历史实体, 执行回调; 2, 改变URL, 产生历史实体, 执行回调 56 | */ 57 | navigate: (hash: string, mode: number) => string 58 | } 59 | } -------------------------------------------------------------------------------- /mock/loged.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": "0", 3 | "t": { 4 | "name": "qweqw", 5 | "functions": [ 6 | { 7 | "id": 9, 8 | "name": "例子一级", 9 | "parent_id": 0, 10 | "menu_index": 12, 11 | "status": "N", 12 | "developer": null, 13 | "code": "demo1", 14 | "uri": null, 15 | "icon_url": "fa fa-book", 16 | "open_mode": "N", 17 | "dispatch_mode": "T" 18 | }, 19 | { 20 | "id": 9, 21 | "name": "例子", 22 | "parent_id": 0, 23 | "menu_index": 12, 24 | "status": "N", 25 | "developer": null, 26 | "code": "demo", 27 | "uri": "/demo", 28 | "icon_url": "fa fa-home", 29 | "open_mode": "N", 30 | "dispatch_mode": "T" 31 | }, 32 | { 33 | "id": 1928, 34 | "name": "商品", 35 | "parent_id": 602, 36 | "menu_index": 3, 37 | "status": "N", 38 | "developer": null, 39 | "code": "item", 40 | "uri": "/item", 41 | "icon_url": "fa fa-home", 42 | "open_mode": "C", 43 | "dispatch_mode": "T" 44 | } 45 | ], 46 | "allowedFunctions": { 47 | "all": true, 48 | "demo1": true, 49 | "demo": true, 50 | "item": true 51 | }, 52 | "allowed": { 53 | "demo.query": true, 54 | "demo.add": true, 55 | "demo.edit": true, 56 | "demo.delete": true 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 非常抱歉,此项目已不再被维护。这个项目陪我度过很艰难的一段时间,有些不舍但是要说再见了。 2 | 3 |

:oncoming_bus:ms-bus:oncoming_bus:

4 | 5 |
6 |  基于 Avalon2 的 SPA 脚手架 beta 7 |
8 |
9 | 中文名:巴适 10 |
11 | 12 | ## 开始 13 | 14 | 1. 首先安装配置 node 环境, 需要 6.x 版本。 15 | 16 | 2. 全局安装构建工具 FIS3。 17 | ``` bash 18 | npm install fis3 -g 19 | ``` 20 | 3. 克隆项目到本地,并安装依赖模块 21 | ``` bash 22 | git clone https://github.com/xxapp/ms-bus.git 23 | 24 | cd ms-bus 25 | 26 | npm install 27 | ``` 28 | 4. 日常运行项目 29 | ``` bash 30 | npm run dev 31 | ``` 32 | 33 | ## 目录结构 34 | 35 | ``` 36 | - components // 将页面按功能和业务切分后的模块 37 | + common-header // 命名规范:[业务名称]-[模块名称] 38 |  - gf-user             // gf 业务下的 user 模块 39 | - gf-user.html // 模块的页面结构 40 | - gf-user.js // 模块的业务逻辑 41 |    - gf-user.css       // 模块的表现样式 42 | + mock                 // 模拟后端服务的数据 43 | + pages // 除 index.html 的完整 HTML 页面,用于多页面应用 44 | - services // 超脱页面的业务逻辑模块 45 | - ajaxService.js // 封装 ajax 方法,规范请求参数和响应数据的格式, 根据响应结果显示提示信息 46 | - configService.js // 应用的配置项,可在构建时动态替换配置项 47 | - filterService.js // 自定义的 Avalon2 过滤器 48 | - menuService.js // 功能菜单的逻辑,权限过滤 49 | - routerService.js // 路由配置 50 | - storeService.js // 数据服务,包括后端数据交互和全局状态管理 51 | - static // 非 commonjs 模块化的资源 52 | - mod.js 53 | - typings // 如果使用 TS 且有必要,就存在这个目录 54 | - index.d.ts 55 | + vendor // 不能通过 npm 安装的模块 56 | - index.html // 应用主页面 57 | - index.js // 应用启动,包括 polyfill/必要的依赖/root VM/路由启动 58 | ``` 59 | 60 | ## 浏览器支持 61 | 62 | 现代浏览器、IE8 及以上 63 | 64 | ## 鸣谢 65 | 66 | [活儿好又性感的在线 Mock 平台 - Easy Mock](https://www.easy-mock.com/) 提供模拟数据支持 67 | -------------------------------------------------------------------------------- /pages/rxjs-demo/rxjs-demo.js: -------------------------------------------------------------------------------- 1 | import 'es5-shim'; 2 | import 'es6-promise/dist/es6-promise.auto'; 3 | import 'addeventlistener-with-dispatch/src/addeventlistener-with-dispatch'; 4 | 5 | import avalon from 'avalon2'; 6 | if (avalon.msie === 8) { 7 | Object.defineProperty = function (obj, property, meta) { 8 | obj[property] = meta.value; 9 | } 10 | } 11 | 12 | import Rx from 'rx'; 13 | 14 | avalon.define({ 15 | $id: 'demo', 16 | text: 'Click Me', 17 | click(e) { 18 | } 19 | }); 20 | 21 | // const button = document.getElementsByTagName('button')[0]; 22 | 23 | // const source = Rx.Observable.fromEvent(button, 'click') 24 | // .map(e => (e.target as HTMLButtonElement).value) 25 | // .debounce(500); 26 | 27 | // const subscription = source.subscribeOnNext(value => console.log(`Input value: %${value}`)); 28 | 29 | // const throttled = Rx.Observable.fromEvent(window, 'resize').throttle(250); 30 | 31 | // throttled.subscribeOnNext(e => { 32 | // console.log(window.innerHeight, window.innerWidth); 33 | // }); 34 | 35 | // setTimeout(() => { 36 | // subscription.dispose(); 37 | // }, 2000) 38 | 39 | // const source = Rx.Observable.interval(1000).bufferWithTime(3000).map(arr => arr.reduce((acc, x) => acc + x, 0)); 40 | 41 | // const subscription = source.subscribe( 42 | // x => console.log(`onNext: ${x}`), 43 | // e => console.log(`onError: ${e}`), 44 | // () => console.log('onCompleted')); 45 | 46 | const codes = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65]; 47 | 48 | function isKonamiCode(buffer) { 49 | console.log(buffer.toString()); 50 | return codes.toString() === buffer.toString(); 51 | } 52 | 53 | const keys = Rx.Observable.fromEvent(document, 'keyup') 54 | .map(e => e.keyCode) 55 | .bufferWithCount(10, 1) 56 | .filter(isKonamiCode) 57 | .subscribeOnNext(() => console.log('KONAMI!')); -------------------------------------------------------------------------------- /components/doc-ms-table/doc-ms-table.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/uilog.js: -------------------------------------------------------------------------------- 1 | window.myLog = function() { 2 | if (!window.myLog._div) { window.myLog.createDiv(); } 3 | 4 | var logEntry = document.createElement('span'); 5 | for (var i=0; i < arguments.length; i++) { 6 | logEntry.innerHTML += window.myLog.toJson(arguments[i]) + '
'; 7 | } 8 | logEntry.innerHTML += '
'; 9 | 10 | window.myLog._div.appendChild(logEntry); 11 | } 12 | window.myLog.createDiv = function() { 13 | window.myLog._div = document.body.appendChild(document.createElement('div')); 14 | var props = { 15 | position:'absolute', top:'10px', right:'10px', background:'#333', border:'5px solid #333', 16 | color: 'white', width: '400px', height: '300px', overflow: 'auto', fontFamily: 'courier new', 17 | fontSize: '11px', whiteSpace: 'nowrap' 18 | } 19 | for (var key in props) { window.myLog._div.style[key] = props[key]; } 20 | } 21 | window.myLog.toJson = function(obj) { 22 | if (typeof window.uneval == 'function') { return uneval(obj); } 23 | if (typeof obj == 'object') { 24 | if (!obj) { return 'null'; } 25 | var list = []; 26 | if (obj instanceof Array) { 27 | for (var i=0;i < obj.length;i++) { list.push(this.toJson(obj[i])); } 28 | return '[' + list.join(',') + ']'; 29 | } else { 30 | for (var prop in obj) { list.push('"' + prop + '":' + this.toJson(obj[prop])); } 31 | return '{' + list.join(',') + '}'; 32 | } 33 | } else if (typeof obj == 'string') { 34 | return '"' + obj.replace(/(["'])/g, '\\$1') + '"'; 35 | } else { 36 | return new String(obj); 37 | } 38 | } 39 | 40 | window.myLog('log statement'); 41 | window.myLog('logging an object', { name: 'Marcus', likes: 'js' }); 42 | 43 | (function () { 44 | var matches = window.navigator.userAgent.match(/MSIE ([^;]*)/); 45 | if (!matches) { return ; } 46 | var ieVersion = parseInt(matches[1], 10); 47 | if (ieVersion < 9) { 48 | window.console = { 49 | log: window.myLog 50 | } 51 | } 52 | })(); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Bus Admin 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 | 21 | <ms-layout-header :widget="{fixed:true}"> 22 | <h3>MS-BUS SPA 脚手架</h3> 23 | </ms-layout-header> 24 | <ms-layout-footer :widget="{fixed:true}"> 25 | <div class="text-center" style="line-height: 30px;"> 26 | <a href="https://github.com/xxapp/ms-bus" target="_blank">Github 仓库地址</a> 27 | </div> 28 | </ms-layout-footer> 29 | <ms-layout :widget="{style:{backgroundColor:'#ececec'}}"> 30 | <ms-layout-sider :widget="{fixed:true}"> 31 | <wbr is="common-sidebar"/> 32 | </ms-layout-sider> 33 | <ms-layout-content> 34 | <ol class="breadcrumb"> 35 | <li :for="($index,item) in @breadcrumb" :class="[($index===breadcrumb.length-1)?'active':'']">{{item.title}}</li> 36 | </ol> 37 | <div class="ane-layout-content-wrapper"> 38 | <div ms-html="@currentPage"></div> 39 | </div> 40 | </ms-layout-content> 41 | </ms-layout> 42 | 43 | 44 | 48 | 49 | -------------------------------------------------------------------------------- /mock/sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": "0", 3 | "message": "获取城市列表成功!", 4 | "sessionId": null, 5 | "currentPage": 0, 6 | "pageSize": 0, 7 | "pageCount": 0, 8 | "total": 25, 9 | "rows": [ 10 | { 11 | "region_id": "10", 12 | "region_parent_id": "1", 13 | "region_name": "河北", 14 | "region_type": "2", 15 | "region_enble": 1, 16 | "suites": [{ 17 | "name": "Tom" 18 | }, { 19 | "name": "Jerry" 20 | }, { 21 | "name": "Jimmy" 22 | }] 23 | }, 24 | { 25 | "region_id": "635", 26 | "region_parent_id": "67", 27 | "region_name": "永昌县", 28 | "region_type": "2", 29 | "region_enble": 1, 30 | "suites": [{ 31 | "name": "Tom" 32 | }] 33 | }, 34 | { 35 | "region_id": "636", 36 | "region_parent_id": "68", 37 | "region_name": "肃州区", 38 | "region_type": "2", 39 | "region_enble": 1, 40 | "suites": [{ 41 | "name": "Tom" 42 | }] 43 | }, 44 | { 45 | "region_id": "637", 46 | "region_parent_id": "68", 47 | "region_name": "玉门市", 48 | "region_type": "2", 49 | "region_enble": 1, 50 | "suites": [{ 51 | "name": "Tom" 52 | }] 53 | }, 54 | { 55 | "region_id": "638", 56 | "region_parent_id": "68", 57 | "region_name": "敦煌市", 58 | "region_type": "2", 59 | "region_enble": 1, 60 | "suites": [{ 61 | "name": "Tom" 62 | }] 63 | }, 64 | { 65 | "region_id": "639", 66 | "region_parent_id": "68", 67 | "region_name": "金塔县", 68 | "region_type": "2", 69 | "region_enble": 1, 70 | "suites": [{ 71 | "name": "Tom" 72 | }] 73 | } 74 | ] 75 | } -------------------------------------------------------------------------------- /components/doc-ms-table/doc-ms-table.js: -------------------------------------------------------------------------------- 1 | import avalon from 'avalon2'; 2 | import * as bootbox from 'bootbox'; 3 | 4 | import ajax from '../../services/ajaxService'; 5 | import { message } from "ane"; 6 | 7 | export const name = 'doc-ms-table'; 8 | 9 | avalon.component(name, { 10 | template: __inline('./doc-ms-table.html'), 11 | defaults: { 12 | list: avalon.range(29).map(n => ({ 13 | id: n, name: `老狼${n}`, address: '深山', province: '老林' 14 | })), 15 | remoteList: [], 16 | loading: false, 17 | pagination: { 18 | pageSize: 6, total: 0 19 | }, 20 | fetch(params = {}) { 21 | this.loading = true; 22 | ajax({ 23 | url: '/api/demo', 24 | method: 'get', 25 | data: { 26 | ...params 27 | } 28 | }).then(data => { 29 | this.pagination.total = data.total; 30 | data.list[0].region_parent_id = Date.now(); 31 | this.remoteList = data.list; 32 | this.loading = false; 33 | }); 34 | }, 35 | handleTableChange(pagination) { 36 | if (this.pagination.hasOwnProperty('current')) { 37 | this.pagination.current = pagination.current; 38 | } 39 | this.fetch({ 40 | start: pagination.pageSize * (pagination.current - 1), 41 | limit: pagination.pageSize 42 | }); 43 | }, 44 | actions(type, text, record, index) { 45 | if (type == 'delete') { 46 | this.list.removeAll(el => el.id == record.id ); 47 | message.success({ 48 | content: '删除成功' 49 | }); 50 | } 51 | }, 52 | handleSelect(record, selected, selectedRows) { 53 | console.log(record, selected, selectedRows); 54 | }, 55 | handleSelectAll(selected, selectedRows) { 56 | console.log(selected, selectedRows); 57 | }, 58 | handleSelectionChange(selectedRowKeys, selectedRows) { 59 | console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows); 60 | }, 61 | handleChange(e) { 62 | 63 | }, 64 | onInit(event) { 65 | this.fetch(); 66 | } 67 | } 68 | }); -------------------------------------------------------------------------------- /services/ajaxService.js: -------------------------------------------------------------------------------- 1 | import avalon from 'avalon2'; 2 | import jQuery from 'jquery'; 3 | global.$ = global.jQuery = jQuery; 4 | 5 | import { notification } from 'ane'; 6 | import { serviceUrl } from './configService'; 7 | 8 | // 拦截ajax请求,检测是否超时,以重新登录 9 | $(document).ajaxComplete((event, xhr, settings) => { 10 | if (xhr.status === 200) { 11 | if (settings.dataType === 'json' && xhr.responseJSON !== void 0) { 12 | let result = xhr.responseJSON; 13 | if (result.code === '20' || result.code === '21') { 14 | if (prompt("Session已经失效,请重新登录")) { 15 | global.location.href = "/login.html"; 16 | }; 17 | } else if (result.error) { 18 | notification.error({ 19 | message: result.error.message 20 | }); 21 | } 22 | } 23 | } else if (xhr.status === undefined) { 24 | } else { 25 | notification.error({ 26 | message: '请求错误,请联系管理员' 27 | }); 28 | } 29 | }); 30 | 31 | export default function (options) { 32 | const defaultOptions = { 33 | dataType: 'json', 34 | cache: true, 35 | jsonp: 'callback' 36 | }; 37 | options.data = processRequest(options.data); 38 | options.url = /^\w+:\/\//.test(options.url) ? options.url : serviceUrl + options.url; 39 | 40 | if (serviceUrl) { 41 | defaultOptions.dataType = 'jsonp'; 42 | options.data.jsonp_param_name = 'callback'; 43 | } 44 | 45 | return $.ajax({ ...defaultOptions, ...options }).then(processResponse); 46 | }; 47 | 48 | // 标准化传给后台的参数 49 | function processRequest(r) { 50 | const str = r || {}; 51 | if (str.start || str.limit) { 52 | str.page = { 53 | start: str.start || 0, 54 | limit: str.limit || 15 55 | }; 56 | delete str.start; 57 | delete str.limit; 58 | } 59 | return { 60 | params: JSON.stringify(str) 61 | }; 62 | } 63 | 64 | // 标准化后台相应数据格式 65 | function processResponse(r) { 66 | let str = {}; 67 | if (r.rows) { 68 | str = r; 69 | str.code = '0'; 70 | str.list = r.rows; 71 | delete str.rows; 72 | } else { 73 | if (!r.error) { 74 | str.code = '0'; 75 | str.data = r; 76 | } else { 77 | str.code = '1'; 78 | str.message = r.message || r.error; 79 | } 80 | } 81 | return str; 82 | } -------------------------------------------------------------------------------- /services/routerService.js: -------------------------------------------------------------------------------- 1 | import avalon from 'avalon2'; 2 | import 'mmRouter'; 3 | import { menu as menuStore } from './storeService'; 4 | 5 | function getPage(component) { 6 | const html = ``; 7 | return html 8 | } 9 | 10 | function applyRouteConfig(config, parentRoute, accPath = '') { 11 | config.map(function (route) { 12 | let components = {}; 13 | if (route.component) { 14 | components.currentPage = route.component; 15 | } 16 | if (route.components) { 17 | components = route.components; 18 | } 19 | avalon.router.add(accPath + route.path, function () { 20 | Object.keys(components).map(viewName => { 21 | let component = components[viewName]; 22 | if (typeof component === 'function') { 23 | component(function (m) { 24 | menuStore.selectedKeys$.onNext([m.name]); 25 | avalon.vmodels[parentRoute.name][viewName] = getPage(m.name); 26 | }); 27 | } else { 28 | avalon.vmodels[parentRoute.name][viewName] = getPage(component.name); 29 | } 30 | }); 31 | }); 32 | // TODO 支持嵌套路由 33 | //route.children && applyRouteConfig(route.children, route, accPath + route.path); 34 | }); 35 | } 36 | 37 | require('/components/common-header'); 38 | require('/components/common-sidebar'); 39 | 40 | const routeConfig = [{ 41 | path: '/', 42 | component(resolve) { 43 | require.async('/components/gf-dashboard', resolve); 44 | } 45 | }, { 46 | path: '/aaa', 47 | component(resolve) { 48 | require.async('/components/gf-aaa', resolve); 49 | } 50 | }, { 51 | path: '/demo', 52 | component(resolve) { 53 | require.async('/components/gf-demo', resolve); 54 | } 55 | }, { 56 | path: '/demo-redux', 57 | component(resolve) { 58 | require.async('/components/gf-demo-redux', resolve); 59 | } 60 | }, { 61 | path: '/demo-fast', 62 | component(resolve) { 63 | require.async('/components/gf-demo-fast', resolve); 64 | } 65 | }, { 66 | path: '/doc-ms-table', 67 | component(resolve) { 68 | require.async('/components/doc-ms-table', resolve); 69 | } 70 | }, { 71 | path: '/doc-ms-form', 72 | component(resolve) { 73 | require.async('/components/doc-ms-form', resolve); 74 | } 75 | }]; 76 | 77 | applyRouteConfig(routeConfig, { 78 | name: 'root' 79 | }); -------------------------------------------------------------------------------- /components/doc-ms-form/doc-ms-form.js: -------------------------------------------------------------------------------- 1 | import avalon from 'avalon2'; 2 | 3 | import ajax from '../../services/ajaxService'; 4 | import { serviceUrl } from "../../services/configService"; 5 | import { createForm, message } from "ane"; 6 | 7 | export const name = 'doc-ms-form'; 8 | 9 | avalon.component(name, { 10 | template: __inline('./doc-ms-form.html'), 11 | defaults: { 12 | $form: createForm({ 13 | record: initialData() 14 | }), 15 | record: initialData(), 16 | json: '', 17 | expire: 0, 18 | regionData: [ 19 | {key: 1, title: "重庆", children: []}, 20 | {key: 2, title: "四川", children: [ 21 | {key: 4, title: '成都', children: []}, 22 | {key: 5, title: '绵阳', children: []}, 23 | {key: 6, title: '攀枝花', children: []} 24 | ]}, 25 | {key: 3, title: "浙江", children: [ 26 | {key: 7, title: '杭州', children: []}, 27 | {key: 8, title: '温州', children: []} 28 | ]} 29 | ], 30 | fileUploadUrl: serviceUrl + '/api/file/uploadFile', 31 | addEducation() { 32 | this.record.education.push(''); 33 | }, 34 | removeEducation(school) { 35 | this.record.education.remove(school); 36 | }, 37 | handleBeforeUpload(file) { 38 | if (file.type !== 'image/jpeg' && file.type !== 'image/png') { 39 | message.error({ 40 | content: '只能选择jpg或者png类型的图片!' 41 | }); 42 | return false; 43 | } 44 | if (file.size / 1024 / 1024 > 1) { 45 | message.error({ 46 | content: '选择的图片必须小于1MB!' 47 | }); 48 | return false; 49 | } 50 | return true; 51 | }, 52 | handleChange(e) { 53 | console.log(e.target.value); 54 | }, 55 | submit() { 56 | // if (!avalon.vmodels['doc_form'].validate()) { 57 | // return false; 58 | // } 59 | this.$form.validateFields(); 60 | }, 61 | onInit(event) { 62 | this.$form.onFieldsChange = (fields, record) => { 63 | this.json = JSON.stringify(record, null, 2); 64 | } 65 | this.$watch('expire', v => { 66 | console.log(v); 67 | }) 68 | } 69 | } 70 | }); 71 | 72 | function initialData() { 73 | return { 74 | name: '123', 75 | gender: 'F', 76 | masterpiece: ['ms-bus'], 77 | location: 4, 78 | birthday: '2017/03/25', 79 | bankai: '2017-10-10 12:12:12', 80 | hobby: ['code'], 81 | avatar: '', 82 | education: ['常乐男子职业技术学院'], 83 | bio: '', 84 | attachment: ['https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png'] 85 | }; 86 | } -------------------------------------------------------------------------------- /components/common-header/common-header.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /services/storeService.js: -------------------------------------------------------------------------------- 1 | import ajax from './ajaxService'; 2 | import { getKeyPath } from './menuService'; 3 | 4 | export const demo = { 5 | key: 'region_id', 6 | initialData: function () { 7 | return { 8 | region_enable: 0, 9 | region_id: '', 10 | region_name: '', 11 | region_parent_id: '', 12 | region_type: '', 13 | suites: [{ 14 | name: '' 15 | }] 16 | }; 17 | }, 18 | fetch: function (params) { 19 | return ajax({ 20 | url: '/api/demo', 21 | type: 'get', 22 | data: params 23 | }); 24 | }, 25 | create: function (params) { 26 | return ajax({ 27 | url: '/api/demo/update', 28 | type: 'post', 29 | data: params 30 | }); 31 | }, 32 | update: function (params) { 33 | return ajax({ 34 | url: '/api/demo/update', 35 | type: 'post', 36 | data: params 37 | }); 38 | }, 39 | remove: function (id) { 40 | return ajax({ 41 | url: '/api/demo/update', 42 | type: 'post', 43 | data: { 44 | id: id 45 | } 46 | }); 47 | } 48 | }; 49 | 50 | export const file = { 51 | insert: function (params) { 52 | $.ajaxFileUpload({ 53 | url: '/api/file/uploadFile', 54 | secureuri: false, 55 | fileElementId: params.fileElementId, 56 | dataType: 'json', 57 | success: params.success 58 | }); 59 | } 60 | }; 61 | 62 | export const github = { 63 | limit: 30, 64 | repository: function (params) { 65 | return ajax({ 66 | url: "/search/repositories", 67 | type: 'get', 68 | data: params 69 | }); 70 | }, 71 | processRequest: function (params) { 72 | return { 73 | q: params.query, 74 | start: (params.page - 1) * this.limit, 75 | limit: this.limit 76 | }; 77 | }, 78 | processResponse: function (data) { 79 | data = data.data; 80 | data.rows = data.items; 81 | data.total = data.total_count; 82 | return data; 83 | } 84 | }; 85 | 86 | export const menu = { 87 | selectedKeys$: Observable(), 88 | openKeys$: Observable() 89 | }; 90 | menu.selectedKeys$.subscribe(v => { 91 | v[0] && getKeyPath(v[0]).then(result => { 92 | menu.openKeys$.onNext(result.map(item => item.key)); 93 | breadcrumb.list$.onNext(result.reverse()); 94 | }); 95 | }); 96 | 97 | export const breadcrumb = { 98 | list$: Observable() 99 | }; 100 | 101 | function Observable() { 102 | return { 103 | onNextCbList: [], 104 | subscribe(onNext) { 105 | this.onNextCbList.push(onNext); 106 | }, 107 | onNext(value) { 108 | this.onNextCbList.forEach(cb => { 109 | if (typeof cb === 'function') { 110 | cb(value); 111 | } 112 | }); 113 | } 114 | }; 115 | } -------------------------------------------------------------------------------- /components/gf-demo/gf-demo.js: -------------------------------------------------------------------------------- 1 | import avalon from 'avalon2'; 2 | 3 | import { createForm, message } from "ane"; 4 | 5 | import { demo as demoStore } from '../../services/storeService'; 6 | 7 | export const name = 'gf-demo'; 8 | 9 | avalon.component(name, { 10 | template: __inline('./gf-demo.html'), 11 | defaults: { 12 | show: false, 13 | list: [], 14 | $searchForm: createForm({ autoAsyncChange: false }), 15 | pagination: { 16 | current: 1, pageSize: 6, total: 0 17 | }, 18 | pattern: /^\d+-\d+-\d+( \d+:\d+:\d+)?$/, 19 | search() { 20 | this.$searchForm.validateFields().then(isAllValid => { 21 | if (isAllValid) { 22 | this.fetch(); 23 | } 24 | }); 25 | }, 26 | fetch() { 27 | const page = { 28 | start: this.pagination.pageSize * (this.pagination.current - 1), 29 | limit: this.pagination.pageSize 30 | }; 31 | demoStore.fetch({...this.$searchForm.record, ...page}).then(data => { 32 | this.pagination.total = data.total; 33 | this.list = data.list; 34 | }); 35 | }, 36 | actions(type, text, record, index) { 37 | if (type === 'add') { 38 | form.isEdit = false; 39 | form.title = '新增'; 40 | form.record = demoStore.initialData(); 41 | this.show = true; 42 | } else if (type === 'edit') { 43 | form.isEdit = true; 44 | form.title = '修改'; 45 | form.record = record; 46 | this.show = true; 47 | } else if (type === 'delete') { 48 | demoStore.remove(record.region_id).then(result => { 49 | if (result.code === '0') { 50 | message.success({ 51 | content: '删除成功' 52 | }); 53 | } 54 | }); 55 | } 56 | }, 57 | handleOk() { 58 | form.$form.validateFields().then(isAllValid => { 59 | if (isAllValid) { 60 | if (form.isEdit) { 61 | demoStore.update(form.$form.record).then(result => { 62 | this.fetch(); 63 | }); 64 | } else { 65 | demoStore.create(form.$form.record).then(result => { 66 | this.fetch(); 67 | }); 68 | } 69 | this.show = false; 70 | } 71 | }) 72 | }, 73 | handleTableChange(pagination) { 74 | this.pagination.current = pagination.current; 75 | this.fetch(); 76 | }, 77 | onInit(event) { 78 | this.fetch(); 79 | } 80 | } 81 | }); 82 | var form = avalon.define({ 83 | $id: 'demo_form', 84 | title: '', 85 | isEdit: false, 86 | $form: createForm({ 87 | record: demoStore.initialData(), 88 | onFieldsChange(fields, record) { 89 | //avalon.mix(form.record, record); 90 | } 91 | }), 92 | record: demoStore.initialData() 93 | }); -------------------------------------------------------------------------------- /typings/avalon.d.ts: -------------------------------------------------------------------------------- 1 | interface avalonComponentRecycleFn { 2 | (event: { 3 | target: Element, 4 | type: string, 5 | vmodel 6 | }); 7 | } 8 | 9 | interface avalonComponent { 10 | (name: string, component: { 11 | template: string, 12 | defaults: { 13 | onInit: avalonComponentRecycleFn, 14 | onReady: avalonComponentRecycleFn, 15 | onViewChange: avalonComponentRecycleFn, 16 | onDispose: avalonComponentRecycleFn 17 | } 18 | }): any 19 | } 20 | 21 | interface avalonInstance { 22 | /** 23 | * 用于获取或修改样式,自动修正厂商前缀及加px,与jQuery的css方法一样智能 24 | */ 25 | css(name: string, value: string | number): number | avalonInstance, 26 | /** 27 | * 取得目标的高,不带单位,如果目标为window,则取得窗口的高,为document取得页面的高 28 | */ 29 | height(value?: string | number) : number, 30 | /** 31 | * 取得元素的位置, 如 {top:111, left: 222} 32 | */ 33 | offset(): { top: number, left: number } 34 | } 35 | 36 | interface AvalonStatic { 37 | /** 38 | * 定义ViewModel,需要指定$id 39 | */ 40 | define(definition): any; 41 | /** 42 | * 定义avalon组件 43 | */ 44 | component(name: string, component): any; 45 | /** 46 | * 扫描元素,与ViewModel绑定 47 | */ 48 | scan(node: Element|string, vm?, beforeReady?: () => void): any; 49 | /** 50 | * 定义指令 51 | */ 52 | directive(name: string, options): any; 53 | /** 54 | * 指令集合 55 | */ 56 | directives: any[]; 57 | /** 58 | * avalon动画 59 | */ 60 | effect(name: string, opts?: any): any; 61 | /** 62 | * 判断一个元素是否包含另一个元素 63 | */ 64 | contains(root: Element, el: Element): boolean 65 | /** 66 | * 给元素绑定事件 67 | */ 68 | bind(elem: Node, type: string, fn: (e) => boolean | void) 69 | /** 70 | * 移除一个元素的事件 71 | */ 72 | unbind(elem: Node, type?: string, fn?: (e) => boolean | void) 73 | root: HTMLElement; 74 | /** 75 | * ViewModel 列表 76 | */ 77 | vmodels: any; 78 | /** 79 | * 过滤器列表 80 | */ 81 | filters: any 82 | /** 83 | * 用于合并多个对象或深克隆,类似于jQuery.extend 84 | */ 85 | mix(target: any, object1?: any, ...objectN: any[]): any; 86 | mix(deep: boolean, target: any, object1?: any, ...objectN: any[]): any, 87 | /** 88 | * no operation 89 | */ 90 | noop(): void, 91 | /** 92 | * virtual dom 93 | */ 94 | vdom(vnode: any, method: string): void 95 | /** 96 | * 生成指定长度数组 97 | */ 98 | range(start: number, end?: number, step?: number): number[] 99 | /** 100 | * IE版本 101 | */ 102 | msie: number, 103 | /** 104 | * 打印日志 105 | */ 106 | log(message?: any, ...optionalParams: any[]): void; 107 | /** 108 | * 打印错误信息 109 | */ 110 | error(message?: any, ...optionalParams: any[]): void; 111 | /** 112 | * 注册文档加载完成事件 113 | */ 114 | ready(fn): void; 115 | /** 116 | * 将字符串安全格式化为正则表达式的源码 117 | */ 118 | escapeRegExp(target): string; 119 | /** 120 | * 构造avalon实例 121 | */ 122 | (el: Node): avalonInstance 123 | } 124 | 125 | declare module 'avalon2' { 126 | export = avalon 127 | } 128 | 129 | declare var avalon: AvalonStatic -------------------------------------------------------------------------------- /services/menuService.js: -------------------------------------------------------------------------------- 1 | import avalon from 'avalon2'; 2 | import ajax from './ajaxService'; 3 | 4 | const menu = [{ 5 | key: 'gf-dashboard', 6 | title: '主页', 7 | icon: 'fa fa-home', 8 | uri: '/' 9 | }, { 10 | key: 'demo1', 11 | title: '例子一级', 12 | icon: 'fa fa-home', 13 | children: [{ 14 | key: 'gf-demo', 15 | title: '例子', 16 | icon: 'fa fa-home', 17 | uri: '/demo' 18 | }, { 19 | key: 'gf-demo-redux', 20 | title: 'redux例子', 21 | icon: 'fa fa-home', 22 | uri: '/demo-redux' 23 | }, { 24 | key: 'gf-demo-fast', 25 | title: '快速CURD例子', 26 | icon: 'fa fa-home', 27 | uri: '/demo-fast' 28 | }] 29 | }, { 30 | key: 'doc-ms', 31 | title: '组件文档', 32 | icon: 'fa fa-book', 33 | children: [{ 34 | key: 'doc-ms-table', 35 | title: 'Table', 36 | uri: '/doc-ms-table' 37 | }, { 38 | key: 'doc-ms-form', 39 | title: 'Form', 40 | uri: '/doc-ms-form' 41 | }] 42 | }, { 43 | key: 'rxjs-demo-page', 44 | title: 'RxJS Demo Page', 45 | icon: 'fa fa-circle', 46 | uri: '/pages/rxjs-demo/rxjs-demo.html', 47 | target: '_blank' 48 | }]; 49 | 50 | // 根据权限过滤菜单 51 | const menuPromise = new Promise((rs, rj) => { 52 | ajax({ 53 | url: '/api/loged', 54 | type: 'get' 55 | }).then((result) => { 56 | if (result.code === '0') { 57 | $('#loadImg').css('display', 'none'); 58 | $('.login-area').removeClass('hidden').addClass('animated flipInX'); 59 | travelMenu(menu, result.data.t.functions, result.data.t.allowedFunctions); 60 | avalon.mix(avalon.vmodels.root, { user: result.data.t }); 61 | rs(menu.slice(0)); 62 | } else { 63 | rj(result); 64 | } 65 | }); 66 | }); 67 | 68 | function travelMenu(menulet, functions, allowedFunctions) { 69 | if (!menulet) { 70 | return ; 71 | } 72 | for (let i = 0, item; item = menulet[i++]; ) { 73 | let hasPermission = false; 74 | for (let j = 0, func; func = functions[j++]; ) { 75 | if (func.code === item.name && (allowedFunctions[func.code])) { 76 | item.uri = func.uri || item.uri || 'javascript:;'; 77 | item.icon = func.icon_url || item.icon; 78 | item.target = item.target || '_self'; 79 | item.children = item.children || []; 80 | item.opened = false; 81 | hasPermission = true; 82 | break; 83 | } 84 | if (allowedFunctions['all']) { 85 | hasPermission = true; 86 | } 87 | } 88 | item.show = hasPermission === true; 89 | 90 | travelMenu(item.children, functions, allowedFunctions); 91 | } 92 | } 93 | 94 | function walkMenu(menu, key, process, level = 1) { 95 | let finded = false; 96 | for (let i = 0; i < menu.length; i++) { 97 | const item = menu[i]; 98 | process(item); 99 | if (item.key === key) { 100 | finded = true; 101 | break; 102 | } 103 | if (item.children && walkMenu(item.children, key, process, level + 1)) { 104 | finded = true; 105 | break; 106 | } 107 | process('', true); 108 | }; 109 | return finded; 110 | } 111 | function getKeyPath(key) { 112 | return menuPromise.then((menu) => { 113 | const keyPath = []; 114 | 115 | walkMenu(menu.toJSON ? menu.toJSON() : menu, key, function (item, shift) { 116 | if (shift) { 117 | keyPath.shift(); 118 | } else { 119 | keyPath.unshift(item); 120 | } 121 | }) 122 | 123 | return keyPath; 124 | }); 125 | } 126 | export { 127 | getKeyPath, 128 | menuPromise as menu 129 | } -------------------------------------------------------------------------------- /fis-conf.js: -------------------------------------------------------------------------------- 1 | // npm install [-g] fis3-hook-commonjs 2 | fis.hook('commonjs', { 3 | paths: { 4 | 'moment-locale': './node_modules/moment/locale/zh-cn.js', 5 | redux: './node_modules/redux/dist/redux.js', 6 | bootstrap: './node_modules/bootstrap/dist/js/bootstrap.js' 7 | }, 8 | extList: ['.js'] 9 | }); 10 | 11 | fis.set('project.ignore', ['node_modules/**', 'output/**', '.git/**', 'fis-conf.js', 12 | 'README.md', 'readme.txt', 'cmd.cmd', 'package.json', 'LICENSE']); 13 | fis.set('baseurl', ''); 14 | 15 | fis.unhook('components'); 16 | fis.hook('node_modules', { 17 | ignoreDevDependencies: true, 18 | shimBuffer: false, 19 | shimProcess: false, 20 | shutup: true 21 | }); 22 | 23 | // 默认情况下不添加hash 24 | ['/{pages,components,services,vendor}/**.{ts,js}', '/*.{ts,js}', '/node_modules/babel-runtime/**.{ts,js}'].forEach(function (blob) { 25 | fis.match(blob, { 26 | preprocessor: fis.plugin('js-require-css'), 27 | parser: fis.plugin('babel-6.x', { 28 | plugins: ['transform-es3-property-literals', 'transform-es3-member-expression-literals', 'add-module-exports'] 29 | }), 30 | rExt: '.js' 31 | }); 32 | }); 33 | fis.match('**', { 34 | useHash: false, 35 | release: false 36 | }); 37 | fis.match('**.js.map', { 38 | release: '/static/$0' 39 | }); 40 | fis.match('/*.html', { 41 | release: '/$0' 42 | }); 43 | fis.match('/pages/**/*.html', { 44 | release: '/$0' 45 | }); 46 | fis.match('/*.{ts,js}', { 47 | isMod: true, 48 | release: '/static/$0' 49 | }); 50 | fis.match('/pages/**/*.{ts,js}', { 51 | isMod: true, 52 | release: '/static/$0' 53 | }); 54 | fis.match('/{node_modules,components}/**/*.{ts,js}', { 55 | isMod: true, // 设置 comp 下都是一些组件,组件建议都是匿名方式 define 56 | release: '/static/$0' 57 | }); 58 | fis.match('/components/**/*.html', { 59 | postprocessor: fis.plugin('component-view', { }), 60 | release: false 61 | }); 62 | fis.match('/{node_modules,components}/**/*.{css,eot,png,gif,svg,ttf,woff,woff2,map}', { 63 | release: '/static/$0' 64 | }); 65 | fis.match('/services/*.{ts,js}', { 66 | isMod: true, 67 | release: '/static/$0' 68 | }); 69 | fis.media('dev').match('/services/configService.{ts,js}', { 70 | postprocessor: function (content, file, settings) { 71 | return content 72 | .replace('__DOMAIN__', '') 73 | .replace('__SERVICE_URL__', ''); 74 | } 75 | }); 76 | fis.match('/vendor/**/*.{ts,js}', { 77 | isMod: true, 78 | release: '/static/$0' 79 | }); 80 | fis.match('/static/**', { 81 | release: '/static/$0' 82 | }); 83 | // mock假数据 84 | fis.media('dev').match('/mock/**', { 85 | release: '/$0' 86 | }); 87 | 88 | fis.match('::package', { 89 | // npm install [-g] fis3-postpackager-loader 90 | // 分析 __RESOURCE_MAP__ 结构,来解决资源加载问题 91 | postpackager: fis.plugin('loader', { 92 | resourceType: 'commonJs', 93 | useInlineMap: true, 94 | obtainStyle: false 95 | }) 96 | }) 97 | 98 | fis.media('gh-pages') 99 | .match('**', { 100 | domain: '/ms-bus' 101 | }) 102 | .match('/services/configService.{ts,js}', { 103 | postprocessor: function (content, file, settings) { 104 | return content 105 | .replace('__DOMAIN__', '/ms-bus') 106 | .replace('__SERVICE_URL__', 'https://www.easy-mock.com/mock/58ff1b7c5e43ae5dbea5eff3'); 107 | } 108 | }) 109 | .match('*.{js,ts}', { 110 | optimizer: fis.plugin('uglify-js') 111 | }) 112 | .match('*.{css}', { 113 | optimizer: fis.plugin('clean-css') 114 | }) 115 | .match('app.js', { 116 | release: '/$0' 117 | }) 118 | .match('vendor.js', { 119 | release: '/$0' 120 | }) 121 | .match('app.css', { 122 | release: '/$0' 123 | }) 124 | .match('::package', { 125 | packager: fis.plugin('deps-pack', { 126 | useTrack: false, 127 | 'app.js': [ 128 | 'index.js', 129 | 'index.js:deps', 130 | '!node_modules/**', 131 | '!node_modules/**:deps' 132 | ], 133 | 'vendor.js': [ 134 | 'index.js', 135 | 'index.js:deps' 136 | ], 137 | 'app.css': [ 138 | 'index.js:deps' 139 | ] 140 | }), 141 | }); 142 | // .match('/mock/**', { 143 | // release: '/$0' 144 | // }); -------------------------------------------------------------------------------- /components/gf-demo-fast/gf-demo-fast.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/gf-demo/gf-demo.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/gf-demo-redux/gf-demo-redux.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/common-curd/common-curd.js: -------------------------------------------------------------------------------- 1 | import avalon from 'avalon2'; 2 | 3 | import { createForm, notification, message } from "ane"; 4 | 5 | export default avalon.component('common-curd', { 6 | template: ' ', 7 | defaults: { 8 | show: false, 9 | loading: false, 10 | list: [], 11 | $searchForm: createForm({ autoAsyncChange: false }), 12 | pagination: { 13 | current: 1, pageSize: 6, total: 0 14 | }, 15 | $dialogs: { 16 | main: null 17 | }, 18 | search() { 19 | this.$searchForm.validateFields().then(isAllValid => { 20 | if (isAllValid) { 21 | this.fetch(); 22 | } 23 | }); 24 | }, 25 | fetch() { 26 | const page = { 27 | start: this.pagination.pageSize * (this.pagination.current - 1), 28 | limit: this.pagination.pageSize 29 | }; 30 | this.loading = true; 31 | this.$store.fetch({...this.$searchForm.record, ...page}).then(data => { 32 | this.pagination.total = data.total; 33 | this.list = data.list; 34 | this.loading = false; 35 | }, () => { 36 | this.loading = false; 37 | }); 38 | }, 39 | handleTableChange(pagination) { 40 | this.pagination.current = pagination.current; 41 | this.fetch(); 42 | }, 43 | handle: {}, 44 | _handle: { 45 | add(text, record, index) { 46 | this.$dialogs.main.beginCreate(this.$store.initialData()); 47 | this.show = true; 48 | }, 49 | edit(text, record, index) { 50 | this.$dialogs.main.beginUpdate(record); 51 | this.show = true; 52 | }, 53 | del(text, record, index) { 54 | this.$store.remove(record.region_id).then(result => { 55 | if (result.code === '0') { 56 | message.success({ 57 | content: '删除成功' 58 | }); 59 | } 60 | }); 61 | }, 62 | }, 63 | actions(type, ...args) { 64 | this.handle[type] && this.handle[type].apply(this, args); 65 | }, 66 | handleOk() { 67 | this.$dialogs.main.submit().then(([isEdit, record]) => { 68 | if (typeof isEdit === 'boolean') { 69 | this.show = false; 70 | if (isEdit) { 71 | return this.$store.update(record); 72 | } else { 73 | return this.$store.create(record); 74 | } 75 | } 76 | }).then(result => { 77 | if (result !== undefined && result.code === '0') { 78 | this.fetch(); 79 | } 80 | }).catch(err => { 81 | avalon.log(err); 82 | }); 83 | }, 84 | _initMainDialog() { 85 | if (this.$dialogs.main === null) { 86 | this.$dialogs.main = avalon.define({ 87 | $id: this.$id + '_dialog_main', 88 | title: '新增', 89 | isEdit: false, 90 | $form: createForm({ 91 | record: this.$store.initialData() 92 | }), 93 | record: this.$store.initialData(), 94 | beginCreate(record) { 95 | this.isEdit = false; 96 | this.title = '新增'; 97 | this.record = record; 98 | }, 99 | beginUpdate(record) { 100 | this.isEdit = true; 101 | this.title = '修改'; 102 | this.record = record; 103 | }, 104 | submit() { 105 | return this.$form.validateFields().then(isAllValid => { 106 | if (isAllValid) { 107 | return [this.isEdit, this.$form.record]; 108 | } else { 109 | return Promise.reject('表单验证未通过'); 110 | } 111 | }) 112 | } 113 | }); 114 | } 115 | }, 116 | _disposeDialogs() { 117 | Object.keys(this.$dialogs).map(name => { 118 | let dialog = this.$dialogs[name]; 119 | if (dialog) { 120 | delete this.$dialogs[name]; 121 | delete avalon.vmodels[dialog.$id]; 122 | } 123 | }); 124 | }, 125 | onInit(event) { 126 | this.fetch(); 127 | this._initMainDialog(); 128 | this.handle = avalon.mix(this._handle, this.handle); 129 | }, 130 | onDispose(event) { 131 | this._disposeDialogs(); 132 | } 133 | } 134 | }); -------------------------------------------------------------------------------- /components/doc-ms-form/doc-ms-form.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/gf-demo-redux/gf-demo-redux.js: -------------------------------------------------------------------------------- 1 | import avalon from 'avalon2'; 2 | import { createStore, combineReducers, applyMiddleware } from 'redux'; 3 | import thunk from 'redux-thunk'; 4 | 5 | import { createForm, message } from "ane"; 6 | 7 | import { demo as demoStore } from '../../services/storeService'; 8 | 9 | export const name = 'gf-demo-redux'; 10 | 11 | function fetch(params) { 12 | return (dispatch, getState) => { 13 | const { page, pageSize, search } = getState().region; 14 | demoStore.fetch({ 15 | ...search, 16 | start: pageSize * (params.page - 1), 17 | limit: pageSize 18 | }).then(data => { 19 | dispatch({ 20 | type: 'region/fetch', 21 | payload: { 22 | list: data.list, 23 | total: data.total, 24 | page: params.page || page 25 | } 26 | }); 27 | }); 28 | } 29 | } 30 | function insert(params?) { 31 | return (dispatch, getState) => { 32 | const { page, record } = getState().region; 33 | demoStore.create(record).then(data => { 34 | dispatch({ type: 'region/closeDialog' }); 35 | dispatch(fetch({ page })); 36 | }); 37 | } 38 | } 39 | function update(params?) { 40 | return (dispatch, getState) => { 41 | const { page, record } = getState().region; 42 | demoStore.update(record).then(data => { 43 | dispatch(fetch({ page })); 44 | }); 45 | } 46 | } 47 | function del(params) { 48 | return (dispatch, getState) => { 49 | const { page } = getState().region; 50 | demoStore.remove(params).then(result => { 51 | if (result.code === '0') { 52 | message.success({ 53 | content: '删除成功' 54 | }); 55 | } 56 | dispatch(fetch({ page })); 57 | }); 58 | } 59 | } 60 | 61 | interface RegionEntity { 62 | region_id: string, 63 | region_name: string, 64 | region_parent_id: string, 65 | suites: { 66 | name: string 67 | }[] 68 | } 69 | 70 | interface Region { 71 | show: boolean, 72 | isEdit: boolean, 73 | list: RegionEntity[], 74 | total: number, 75 | page: number, 76 | pageSize: number, 77 | search: { 78 | region_id: string, 79 | region_name: { 80 | firstName: string 81 | }, 82 | startDate: string, 83 | endDate: string 84 | }, 85 | record: RegionEntity 86 | } 87 | interface All { 88 | region: Region 89 | } 90 | 91 | const region = function regionReducer(state: Region, action): Region { 92 | if (state === undefined) { 93 | state = { 94 | show: false, 95 | isEdit: false, 96 | list: [], 97 | total: 0, 98 | page: 1, 99 | pageSize: 6, 100 | search: null, 101 | record: null 102 | }; 103 | } 104 | switch (action.type) { 105 | case 'region/closeDialog': 106 | return { 107 | ...state, 108 | show: false 109 | }; 110 | case 'region/fetch': 111 | return { 112 | ...state, 113 | ...action.payload 114 | }; 115 | case 'region/readyForAdd': 116 | return { 117 | ...state, 118 | isEdit: false, 119 | show: true 120 | }; 121 | case 'region/readyForEdit': 122 | return { 123 | ...state, 124 | isEdit: true, 125 | show: true 126 | }; 127 | case 'region/changeSearch': 128 | return { 129 | ...state, 130 | search: action.payload 131 | } 132 | case 'region/changeRecord': 133 | return { 134 | ...state, 135 | record: action.payload 136 | } 137 | default: 138 | return state; 139 | } 140 | } 141 | const store = createStore( 142 | combineReducers({ 143 | region 144 | }), 145 | global.__REDUX_DEVTOOLS_EXTENSION__ && global.__REDUX_DEVTOOLS_EXTENSION__(), 146 | applyMiddleware(thunk) 147 | ); 148 | 149 | avalon.component(name, { 150 | template: __inline('./gf-demo-redux.html'), 151 | defaults: { 152 | show: false, 153 | isEdit: false, 154 | list: [], 155 | $searchForm: createForm({ 156 | onFieldsChange(fields, record) { 157 | store.dispatch({ type: 'region/changeSearch', payload: record }); 158 | } 159 | }), 160 | pagination: { 161 | current: 1, total: 0, pageSize: 6 162 | }, 163 | pattern: /^\d+-\d+-\d+( \d+:\d+:\d+)?$/, 164 | search() { 165 | this.fetch(); 166 | }, 167 | fetch(params = {}) { 168 | store.dispatch(fetch(params)); 169 | }, 170 | actions(type, text, record, index) { 171 | if (type === 'add') { 172 | form.title = '新增'; 173 | form.record = demoStore.initialData(); 174 | store.dispatch({ type: 'region/readyForAdd' }); 175 | } else if (type === 'edit') { 176 | form.title = '修改'; 177 | form.record = record; 178 | store.dispatch({ type: 'region/readyForEdit' }); 179 | } else if (type === 'delete') { 180 | store.dispatch(del(record.region_id)); 181 | } 182 | }, 183 | handleOk() { 184 | form.$form.validateFields().then(isAllValid => { 185 | if (isAllValid) { 186 | if (this.isEdit) { 187 | store.dispatch(update()); 188 | } else { 189 | store.dispatch(insert()); 190 | } 191 | store.dispatch({ type: 'region/closeDialog' }); 192 | } 193 | }) 194 | }, 195 | handleCancel() { 196 | store.dispatch({ type: 'region/closeDialog' }); 197 | }, 198 | handleTableChange(pagination) { 199 | this.fetch({ page: pagination.current }); 200 | }, 201 | mapStateToVm() { 202 | const { 203 | show, isEdit, list, total, page, pageSize, search, record 204 | } = store.getState().region; 205 | this.list = list; 206 | this.pagination.total = total; 207 | this.pagination.current = page; 208 | this.pagination.pageSize = pageSize; 209 | this.isEdit = isEdit; 210 | this.show = show; 211 | }, 212 | onInit(event) { 213 | this.mapStateToVm(); 214 | store.subscribe(() => { 215 | this.mapStateToVm(); 216 | }); 217 | this.fetch(); 218 | }, 219 | onReady(event) { 220 | } 221 | } 222 | }); 223 | const form = avalon.define({ 224 | $id: 'demo_redux_form', 225 | title: '', 226 | $form: createForm({ 227 | record: demoStore.initialData(), 228 | onFieldsChange(fields, record) { 229 | //avalon.mix(form.record, record); 230 | store.dispatch({ type: 'region/changeRecord', payload: record }); 231 | } 232 | }), 233 | record: demoStore.initialData() 234 | }); -------------------------------------------------------------------------------- /static/mod.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file: mod.js 3 | * @author fis 4 | * ver: 1.0.13 5 | * update: 2016/01/27 6 | * https://github.com/fex-team/mod 7 | */ 8 | var require; 9 | 10 | /* eslint-disable no-unused-vars */ 11 | var define; 12 | 13 | (function (global) { 14 | 15 | // 避免重复加载而导致已定义模块丢失 16 | if (require) { 17 | return; 18 | } 19 | 20 | var head = document.getElementsByTagName('head')[0]; 21 | var loadingMap = {}; 22 | var factoryMap = {}; 23 | var modulesMap = {}; 24 | var scriptsMap = {}; 25 | var resMap = {}; 26 | var pkgMap = {}; 27 | 28 | var createScripts = function(queues, onerror){ 29 | 30 | var docFrag = document.createDocumentFragment(); 31 | 32 | for(var i = 0, len = queues.length; i < len; i++){ 33 | var id = queues[i].id; 34 | var url = queues[i].url; 35 | 36 | if (url in scriptsMap) { 37 | continue; 38 | } 39 | 40 | scriptsMap[url] = true; 41 | 42 | var script = document.createElement('script'); 43 | if (onerror) { 44 | (function(script, id){ 45 | var tid = setTimeout(function(){ 46 | onerror(id); 47 | }, require.timeout); 48 | 49 | script.onerror = function () { 50 | clearTimeout(tid); 51 | onerror(id); 52 | }; 53 | 54 | var onload = function () { 55 | clearTimeout(tid); 56 | }; 57 | 58 | if ('onload' in script) { 59 | script.onload = onload; 60 | } 61 | else { 62 | script.onreadystatechange = function () { 63 | if (this.readyState === 'loaded' || this.readyState === 'complete') { 64 | onload(); 65 | } 66 | }; 67 | } 68 | })(script, id); 69 | } 70 | script.type = 'text/javascript'; 71 | script.src = url; 72 | 73 | docFrag.appendChild(script); 74 | } 75 | 76 | head.appendChild(docFrag); 77 | }; 78 | 79 | var loadScripts = function(ids, callback, onerror){ 80 | var queues = []; 81 | for(var i = 0, len = ids.length; i < len; i++){ 82 | var id = ids[i]; 83 | var queue = loadingMap[id] || (loadingMap[id] = []); 84 | queue.push(callback); 85 | 86 | // 87 | // resource map query 88 | // 89 | var res = resMap[id] || resMap[id + '.js'] || {}; 90 | var pkg = res.pkg; 91 | var url; 92 | 93 | if (pkg) { 94 | url = pkgMap[pkg].url || pkgMap[pkg].uri; 95 | } 96 | else { 97 | url = res.url || res.uri || id; 98 | } 99 | 100 | queues.push({ 101 | id: id, 102 | url: url 103 | }); 104 | } 105 | 106 | createScripts(queues, onerror); 107 | }; 108 | 109 | define = function (id, factory) { 110 | id = id.replace(/\.js$/i, ''); 111 | factoryMap[id] = factory; 112 | 113 | var queue = loadingMap[id]; 114 | if (queue) { 115 | for (var i = 0, n = queue.length; i < n; i++) { 116 | queue[i](); 117 | } 118 | delete loadingMap[id]; 119 | } 120 | }; 121 | 122 | require = function (id) { 123 | 124 | // compatible with require([dep, dep2...]) syntax. 125 | if (id && id.splice) { 126 | return require.async.apply(this, arguments); 127 | } 128 | 129 | id = require.alias(id); 130 | 131 | var mod = modulesMap[id]; 132 | if (mod) { 133 | return mod.exports; 134 | } 135 | 136 | // 137 | // init module 138 | // 139 | var factory = factoryMap[id]; 140 | if (!factory) { 141 | throw '[ModJS] Cannot find module `' + id + '`'; 142 | } 143 | 144 | mod = modulesMap[id] = { 145 | exports: {} 146 | }; 147 | 148 | // 149 | // factory: function OR value 150 | // 151 | var ret = (typeof factory === 'function') ? factory.apply(mod, [require, mod.exports, mod]) : factory; 152 | 153 | if (ret) { 154 | mod.exports = ret; 155 | } 156 | 157 | return mod.exports; 158 | }; 159 | 160 | require.async = function (names, onload, onerror) { 161 | if (typeof names === 'string') { 162 | names = [names]; 163 | } 164 | 165 | var needMap = {}; 166 | var needNum = 0; 167 | var needLoad = []; 168 | 169 | function findNeed(depArr) { 170 | var child; 171 | 172 | for (var i = 0, n = depArr.length; i < n; i++) { 173 | // 174 | // skip loading or loaded 175 | // 176 | var dep = require.alias(depArr[i]); 177 | 178 | if (dep in needMap) { 179 | continue; 180 | } 181 | 182 | needMap[dep] = true; 183 | 184 | if (dep in factoryMap) { 185 | // check whether loaded resource's deps is loaded or not 186 | child = resMap[dep] || resMap[dep + '.js']; 187 | if (child && 'deps' in child) { 188 | findNeed(child.deps); 189 | } 190 | continue; 191 | } 192 | 193 | needLoad.push(dep); 194 | needNum++; 195 | 196 | child = resMap[dep] || resMap[dep + '.js']; 197 | if (child && 'deps' in child) { 198 | findNeed(child.deps); 199 | } 200 | } 201 | } 202 | 203 | function updateNeed() { 204 | if (0 === needNum--) { 205 | var args = []; 206 | for (var i = 0, n = names.length; i < n; i++) { 207 | args[i] = require(names[i]); 208 | } 209 | 210 | onload && onload.apply(global, args); 211 | } 212 | } 213 | 214 | findNeed(names); 215 | loadScripts(needLoad, updateNeed, onerror); 216 | updateNeed(); 217 | }; 218 | 219 | require.ensure = function(names, callback) { 220 | require.async(names, function() { 221 | callback && callback.call(this, require); 222 | }); 223 | }; 224 | 225 | require.resourceMap = function (obj) { 226 | var k; 227 | var col; 228 | 229 | // merge `res` & `pkg` fields 230 | col = obj.res; 231 | for (k in col) { 232 | if (col.hasOwnProperty(k)) { 233 | resMap[k] = col[k]; 234 | } 235 | } 236 | 237 | col = obj.pkg; 238 | for (k in col) { 239 | if (col.hasOwnProperty(k)) { 240 | pkgMap[k] = col[k]; 241 | } 242 | } 243 | }; 244 | 245 | require.loadJs = function (url) { 246 | if (url in scriptsMap) { 247 | return; 248 | } 249 | 250 | scriptsMap[url] = true; 251 | 252 | var script = document.createElement('script'); 253 | script.type = 'text/javascript'; 254 | script.src = url; 255 | head.appendChild(script); 256 | }; 257 | 258 | require.loadCss = function (cfg) { 259 | if (cfg.content) { 260 | var sty = document.createElement('style'); 261 | sty.type = 'text/css'; 262 | 263 | if (sty.styleSheet) { // IE 264 | sty.styleSheet.cssText = cfg.content; 265 | } 266 | else { 267 | sty.innerHTML = cfg.content; 268 | } 269 | head.appendChild(sty); 270 | } 271 | else if (cfg.url) { 272 | var link = document.createElement('link'); 273 | link.href = cfg.url; 274 | link.rel = 'stylesheet'; 275 | link.type = 'text/css'; 276 | head.appendChild(link); 277 | } 278 | }; 279 | 280 | 281 | require.alias = function (id) { 282 | return id.replace(/\.js$/i, ''); 283 | }; 284 | 285 | require.timeout = 5000; 286 | 287 | })(this); 288 | -------------------------------------------------------------------------------- /static/polyfill/respond.src.js: -------------------------------------------------------------------------------- 1 | /*! Respond.js v1.4.2: min/max-width media query polyfill 2 | * Copyright 2014 Scott Jehl 3 | * Licensed under MIT 4 | * http://j.mp/respondjs */ 5 | 6 | /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */ 7 | /*! NOTE: If you're already including a window.matchMedia polyfill via Modernizr or otherwise, you don't need this part */ 8 | (function(w) { 9 | "use strict"; 10 | w.matchMedia = w.matchMedia || function(doc, undefined) { 11 | var bool, docElem = doc.documentElement, refNode = docElem.firstElementChild || docElem.firstChild, fakeBody = doc.createElement("body"), div = doc.createElement("div"); 12 | div.id = "mq-test-1"; 13 | div.style.cssText = "position:absolute;top:-100em"; 14 | fakeBody.style.background = "none"; 15 | fakeBody.appendChild(div); 16 | return function(q) { 17 | div.innerHTML = '­'; 18 | docElem.insertBefore(fakeBody, refNode); 19 | bool = div.offsetWidth === 42; 20 | docElem.removeChild(fakeBody); 21 | return { 22 | matches: bool, 23 | media: q 24 | }; 25 | }; 26 | }(w.document); 27 | })(this); 28 | 29 | (function(w) { 30 | "use strict"; 31 | var respond = {}; 32 | w.respond = respond; 33 | respond.update = function() {}; 34 | var requestQueue = [], xmlHttp = function() { 35 | var xmlhttpmethod = false; 36 | try { 37 | xmlhttpmethod = new w.XMLHttpRequest(); 38 | } catch (e) { 39 | xmlhttpmethod = new w.ActiveXObject("Microsoft.XMLHTTP"); 40 | } 41 | return function() { 42 | return xmlhttpmethod; 43 | }; 44 | }(), ajax = function(url, callback) { 45 | var req = xmlHttp(); 46 | if (!req) { 47 | return; 48 | } 49 | req.open("GET", url, true); 50 | req.onreadystatechange = function() { 51 | if (req.readyState !== 4 || req.status !== 200 && req.status !== 304) { 52 | return; 53 | } 54 | callback(req.responseText); 55 | }; 56 | if (req.readyState === 4) { 57 | return; 58 | } 59 | req.send(null); 60 | }, isUnsupportedMediaQuery = function(query) { 61 | return query.replace(respond.regex.minmaxwh, "").match(respond.regex.other); 62 | }; 63 | respond.ajax = ajax; 64 | respond.queue = requestQueue; 65 | respond.unsupportedmq = isUnsupportedMediaQuery; 66 | respond.regex = { 67 | media: /@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi, 68 | keyframes: /@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi, 69 | comments: /\/\*[^*]*\*+([^/][^*]*\*+)*\//gi, 70 | urls: /(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g, 71 | findStyles: /@media *([^\{]+)\{([\S\s]+?)$/, 72 | only: /(only\s+)?([a-zA-Z]+)\s?/, 73 | minw: /\(\s*min\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/, 74 | maxw: /\(\s*max\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/, 75 | minmaxwh: /\(\s*m(in|ax)\-(height|width)\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/gi, 76 | other: /\([^\)]*\)/g 77 | }; 78 | respond.mediaQueriesSupported = w.matchMedia && w.matchMedia("only all") !== null && w.matchMedia("only all").matches; 79 | if (respond.mediaQueriesSupported) { 80 | return; 81 | } 82 | var doc = w.document, docElem = doc.documentElement, mediastyles = [], rules = [], appendedEls = [], parsedSheets = {}, resizeThrottle = 30, head = doc.getElementsByTagName("head")[0] || docElem, base = doc.getElementsByTagName("base")[0], links = head.getElementsByTagName("link"), lastCall, resizeDefer, eminpx, getEmValue = function() { 83 | var ret, div = doc.createElement("div"), body = doc.body, originalHTMLFontSize = docElem.style.fontSize, originalBodyFontSize = body && body.style.fontSize, fakeUsed = false; 84 | div.style.cssText = "position:absolute;font-size:1em;width:1em"; 85 | if (!body) { 86 | body = fakeUsed = doc.createElement("body"); 87 | body.style.background = "none"; 88 | } 89 | docElem.style.fontSize = "100%"; 90 | body.style.fontSize = "100%"; 91 | body.appendChild(div); 92 | if (fakeUsed) { 93 | docElem.insertBefore(body, docElem.firstChild); 94 | } 95 | ret = div.offsetWidth; 96 | if (fakeUsed) { 97 | docElem.removeChild(body); 98 | } else { 99 | body.removeChild(div); 100 | } 101 | docElem.style.fontSize = originalHTMLFontSize; 102 | if (originalBodyFontSize) { 103 | body.style.fontSize = originalBodyFontSize; 104 | } 105 | ret = eminpx = parseFloat(ret); 106 | return ret; 107 | }, applyMedia = function(fromResize) { 108 | var name = "clientWidth", docElemProp = docElem[name], currWidth = doc.compatMode === "CSS1Compat" && docElemProp || doc.body[name] || docElemProp, styleBlocks = {}, lastLink = links[links.length - 1], now = new Date().getTime(); 109 | if (fromResize && lastCall && now - lastCall < resizeThrottle) { 110 | w.clearTimeout(resizeDefer); 111 | resizeDefer = w.setTimeout(applyMedia, resizeThrottle); 112 | return; 113 | } else { 114 | lastCall = now; 115 | } 116 | for (var i in mediastyles) { 117 | if (mediastyles.hasOwnProperty(i)) { 118 | var thisstyle = mediastyles[i], min = thisstyle.minw, max = thisstyle.maxw, minnull = min === null, maxnull = max === null, em = "em"; 119 | if (!!min) { 120 | min = parseFloat(min) * (min.indexOf(em) > -1 ? eminpx || getEmValue() : 1); 121 | } 122 | if (!!max) { 123 | max = parseFloat(max) * (max.indexOf(em) > -1 ? eminpx || getEmValue() : 1); 124 | } 125 | if (!thisstyle.hasquery || (!minnull || !maxnull) && (minnull || currWidth >= min) && (maxnull || currWidth <= max)) { 126 | if (!styleBlocks[thisstyle.media]) { 127 | styleBlocks[thisstyle.media] = []; 128 | } 129 | styleBlocks[thisstyle.media].push(rules[thisstyle.rules]); 130 | } 131 | } 132 | } 133 | for (var j in appendedEls) { 134 | if (appendedEls.hasOwnProperty(j)) { 135 | if (appendedEls[j] && appendedEls[j].parentNode === head) { 136 | head.removeChild(appendedEls[j]); 137 | } 138 | } 139 | } 140 | appendedEls.length = 0; 141 | for (var k in styleBlocks) { 142 | if (styleBlocks.hasOwnProperty(k)) { 143 | var ss = doc.createElement("style"), css = styleBlocks[k].join("\n"); 144 | ss.type = "text/css"; 145 | ss.media = k; 146 | head.insertBefore(ss, lastLink.nextSibling); 147 | if (ss.styleSheet) { 148 | ss.styleSheet.cssText = css; 149 | } else { 150 | ss.appendChild(doc.createTextNode(css)); 151 | } 152 | appendedEls.push(ss); 153 | } 154 | } 155 | }, translate = function(styles, href, media) { 156 | var qs = styles.replace(respond.regex.comments, "").replace(respond.regex.keyframes, "").match(respond.regex.media), ql = qs && qs.length || 0; 157 | href = href.substring(0, href.lastIndexOf("/")); 158 | var repUrls = function(css) { 159 | return css.replace(respond.regex.urls, "$1" + href + "$2$3"); 160 | }, useMedia = !ql && media; 161 | if (href.length) { 162 | href += "/"; 163 | } 164 | if (useMedia) { 165 | ql = 1; 166 | } 167 | for (var i = 0; i < ql; i++) { 168 | var fullq, thisq, eachq, eql; 169 | if (useMedia) { 170 | fullq = media; 171 | rules.push(repUrls(styles)); 172 | } else { 173 | fullq = qs[i].match(respond.regex.findStyles) && RegExp.$1; 174 | rules.push(RegExp.$2 && repUrls(RegExp.$2)); 175 | } 176 | eachq = fullq.split(","); 177 | eql = eachq.length; 178 | for (var j = 0; j < eql; j++) { 179 | thisq = eachq[j]; 180 | if (isUnsupportedMediaQuery(thisq)) { 181 | continue; 182 | } 183 | mediastyles.push({ 184 | media: thisq.split("(")[0].match(respond.regex.only) && RegExp.$2 || "all", 185 | rules: rules.length - 1, 186 | hasquery: thisq.indexOf("(") > -1, 187 | minw: thisq.match(respond.regex.minw) && parseFloat(RegExp.$1) + (RegExp.$2 || ""), 188 | maxw: thisq.match(respond.regex.maxw) && parseFloat(RegExp.$1) + (RegExp.$2 || "") 189 | }); 190 | } 191 | } 192 | applyMedia(); 193 | }, makeRequests = function() { 194 | if (requestQueue.length) { 195 | var thisRequest = requestQueue.shift(); 196 | ajax(thisRequest.href, function(styles) { 197 | translate(styles, thisRequest.href, thisRequest.media); 198 | parsedSheets[thisRequest.href] = true; 199 | w.setTimeout(function() { 200 | makeRequests(); 201 | }, 0); 202 | }); 203 | } 204 | }, ripCSS = function() { 205 | for (var i = 0; i < links.length; i++) { 206 | var sheet = links[i], href = sheet.href, media = sheet.media, isCSS = sheet.rel && sheet.rel.toLowerCase() === "stylesheet"; 207 | if (!!href && isCSS && !parsedSheets[href]) { 208 | if (sheet.styleSheet && sheet.styleSheet.rawCssText) { 209 | translate(sheet.styleSheet.rawCssText, href, media); 210 | parsedSheets[href] = true; 211 | } else { 212 | if (!/^([a-zA-Z:]*\/\/)/.test(href) && !base || href.replace(RegExp.$1, "").split("/")[0] === w.location.host) { 213 | if (href.substring(0, 2) === "//") { 214 | href = w.location.protocol + href; 215 | } 216 | requestQueue.push({ 217 | href: href, 218 | media: media 219 | }); 220 | } 221 | } 222 | } 223 | } 224 | makeRequests(); 225 | }; 226 | ripCSS(); 227 | respond.update = ripCSS; 228 | respond.getEmValue = getEmValue; 229 | function callMedia() { 230 | applyMedia(true); 231 | } 232 | if (w.addEventListener) { 233 | w.addEventListener("resize", callMedia, false); 234 | } else if (w.attachEvent) { 235 | w.attachEvent("onresize", callMedia); 236 | } 237 | })(this); -------------------------------------------------------------------------------- /static/polyfill/html5shiv.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | ;(function(window, document) { 5 | /*jshint evil:true */ 6 | /** version */ 7 | var version = '3.7.3'; 8 | 9 | /** Preset options */ 10 | var options = window.html5 || {}; 11 | 12 | /** Used to skip problem elements */ 13 | var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i; 14 | 15 | /** Not all elements can be cloned in IE **/ 16 | var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i; 17 | 18 | /** Detect whether the browser supports default html5 styles */ 19 | var supportsHtml5Styles; 20 | 21 | /** Name of the expando, to work with multiple documents or to re-shiv one document */ 22 | var expando = '_html5shiv'; 23 | 24 | /** The id for the the documents expando */ 25 | var expanID = 0; 26 | 27 | /** Cached data for each document */ 28 | var expandoData = {}; 29 | 30 | /** Detect whether the browser supports unknown elements */ 31 | var supportsUnknownElements; 32 | 33 | (function() { 34 | try { 35 | var a = document.createElement('a'); 36 | a.innerHTML = ''; 37 | //if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles 38 | supportsHtml5Styles = ('hidden' in a); 39 | 40 | supportsUnknownElements = a.childNodes.length == 1 || (function() { 41 | // assign a false positive if unable to shiv 42 | (document.createElement)('a'); 43 | var frag = document.createDocumentFragment(); 44 | return ( 45 | typeof frag.cloneNode == 'undefined' || 46 | typeof frag.createDocumentFragment == 'undefined' || 47 | typeof frag.createElement == 'undefined' 48 | ); 49 | }()); 50 | } catch(e) { 51 | // assign a false positive if detection fails => unable to shiv 52 | supportsHtml5Styles = true; 53 | supportsUnknownElements = true; 54 | } 55 | 56 | }()); 57 | 58 | /*--------------------------------------------------------------------------*/ 59 | 60 | /** 61 | * Creates a style sheet with the given CSS text and adds it to the document. 62 | * @private 63 | * @param {Document} ownerDocument The document. 64 | * @param {String} cssText The CSS text. 65 | * @returns {StyleSheet} The style element. 66 | */ 67 | function addStyleSheet(ownerDocument, cssText) { 68 | var p = ownerDocument.createElement('p'), 69 | parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement; 70 | 71 | p.innerHTML = 'x'; 72 | return parent.insertBefore(p.lastChild, parent.firstChild); 73 | } 74 | 75 | /** 76 | * Returns the value of `html5.elements` as an array. 77 | * @private 78 | * @returns {Array} An array of shived element node names. 79 | */ 80 | function getElements() { 81 | var elements = html5.elements; 82 | return typeof elements == 'string' ? elements.split(' ') : elements; 83 | } 84 | 85 | /** 86 | * Extends the built-in list of html5 elements 87 | * @memberOf html5 88 | * @param {String|Array} newElements whitespace separated list or array of new element names to shiv 89 | * @param {Document} ownerDocument The context document. 90 | */ 91 | function addElements(newElements, ownerDocument) { 92 | var elements = html5.elements; 93 | if(typeof elements != 'string'){ 94 | elements = elements.join(' '); 95 | } 96 | if(typeof newElements != 'string'){ 97 | newElements = newElements.join(' '); 98 | } 99 | html5.elements = elements +' '+ newElements; 100 | shivDocument(ownerDocument); 101 | } 102 | 103 | /** 104 | * Returns the data associated to the given document 105 | * @private 106 | * @param {Document} ownerDocument The document. 107 | * @returns {Object} An object of data. 108 | */ 109 | function getExpandoData(ownerDocument) { 110 | var data = expandoData[ownerDocument[expando]]; 111 | if (!data) { 112 | data = {}; 113 | expanID++; 114 | ownerDocument[expando] = expanID; 115 | expandoData[expanID] = data; 116 | } 117 | return data; 118 | } 119 | 120 | /** 121 | * returns a shived element for the given nodeName and document 122 | * @memberOf html5 123 | * @param {String} nodeName name of the element 124 | * @param {Document|DocumentFragment} ownerDocument The context document. 125 | * @returns {Object} The shived element. 126 | */ 127 | function createElement(nodeName, ownerDocument, data){ 128 | if (!ownerDocument) { 129 | ownerDocument = document; 130 | } 131 | if(supportsUnknownElements){ 132 | return ownerDocument.createElement(nodeName); 133 | } 134 | if (!data) { 135 | data = getExpandoData(ownerDocument); 136 | } 137 | var node; 138 | 139 | if (data.cache[nodeName]) { 140 | node = data.cache[nodeName].cloneNode(); 141 | } else if (saveClones.test(nodeName)) { 142 | node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode(); 143 | } else { 144 | node = data.createElem(nodeName); 145 | } 146 | 147 | // Avoid adding some elements to fragments in IE < 9 because 148 | // * Attributes like `name` or `type` cannot be set/changed once an element 149 | // is inserted into a document/fragment 150 | // * Link elements with `src` attributes that are inaccessible, as with 151 | // a 403 response, will cause the tab/window to crash 152 | // * Script elements appended to fragments will execute when their `src` 153 | // or `text` property is set 154 | return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node; 155 | } 156 | 157 | /** 158 | * returns a shived DocumentFragment for the given document 159 | * @memberOf html5 160 | * @param {Document} ownerDocument The context document. 161 | * @returns {Object} The shived DocumentFragment. 162 | */ 163 | function createDocumentFragment(ownerDocument, data){ 164 | if (!ownerDocument) { 165 | ownerDocument = document; 166 | } 167 | if(supportsUnknownElements){ 168 | return ownerDocument.createDocumentFragment(); 169 | } 170 | data = data || getExpandoData(ownerDocument); 171 | var clone = data.frag.cloneNode(), 172 | i = 0, 173 | elems = getElements(), 174 | l = elems.length; 175 | for(;i'), 122 | defaults = $.minicolors.defaults; 123 | 124 | // Do nothing if already initialized 125 | if( input.data('minicolors-initialized') ) return; 126 | 127 | // Handle settings 128 | settings = $.extend(true, {}, defaults, settings); 129 | 130 | // The wrapper 131 | minicolors 132 | .addClass('minicolors-theme-' + settings.theme) 133 | .toggleClass('minicolors-with-opacity', settings.opacity); 134 | 135 | // Custom positioning 136 | if( settings.position !== undefined ) { 137 | $.each(settings.position.split(' '), function() { 138 | minicolors.addClass('minicolors-position-' + this); 139 | }); 140 | } 141 | 142 | // The input 143 | input 144 | .addClass('minicolors-input') 145 | .data('minicolors-initialized', false) 146 | .data('minicolors-settings', settings) 147 | .prop('size', 7) 148 | .wrap(minicolors) 149 | .after( 150 | '
' + 151 | '
' + 152 | '
' + 153 | '
' + 154 | '
' + 155 | '
' + 156 | '
' + 157 | '
' + 158 | '
' + 159 | '
' + 160 | '
' + 161 | '
' 162 | ); 163 | 164 | // The swatch 165 | if( !settings.inline ) { 166 | input.after(''); 167 | input.next('.minicolors-swatch').on('click', function(event) { 168 | event.preventDefault(); 169 | input.focus(); 170 | }); 171 | } 172 | 173 | // Prevent text selection in IE 174 | input.parent().find('.minicolors-panel').on('selectstart', function() { return false; }).end(); 175 | 176 | // Inline controls 177 | if( settings.inline ) input.parent().addClass('minicolors-inline'); 178 | 179 | updateFromInput(input, false); 180 | 181 | input.data('minicolors-initialized', true); 182 | 183 | } 184 | 185 | // Returns the input back to its original state 186 | function destroy(input) { 187 | 188 | var minicolors = input.parent(); 189 | 190 | // Revert the input element 191 | input 192 | .removeData('minicolors-initialized') 193 | .removeData('minicolors-settings') 194 | .removeProp('size') 195 | .removeClass('minicolors-input'); 196 | 197 | // Remove the wrap and destroy whatever remains 198 | minicolors.before(input).remove(); 199 | 200 | } 201 | 202 | // Shows the specified dropdown panel 203 | function show(input) { 204 | 205 | var minicolors = input.parent(), 206 | panel = minicolors.find('.minicolors-panel'), 207 | settings = input.data('minicolors-settings'); 208 | 209 | // Do nothing if uninitialized, disabled, inline, or already open 210 | if( !input.data('minicolors-initialized') || 211 | input.prop('disabled') || 212 | minicolors.hasClass('minicolors-inline') || 213 | minicolors.hasClass('minicolors-focus') 214 | ) return; 215 | 216 | hide(); 217 | 218 | minicolors.addClass('minicolors-focus'); 219 | panel 220 | .stop(true, true) 221 | .fadeIn(settings.showSpeed, function() { 222 | if( settings.show ) settings.show.call(input.get(0)); 223 | }); 224 | 225 | } 226 | 227 | // Hides all dropdown panels 228 | function hide() { 229 | 230 | $('.minicolors-focus').each( function() { 231 | 232 | var minicolors = $(this), 233 | input = minicolors.find('.minicolors-input'), 234 | panel = minicolors.find('.minicolors-panel'), 235 | settings = input.data('minicolors-settings'); 236 | 237 | panel.fadeOut(settings.hideSpeed, function() { 238 | if( settings.hide ) settings.hide.call(input.get(0)); 239 | minicolors.removeClass('minicolors-focus'); 240 | }); 241 | 242 | }); 243 | } 244 | 245 | // Moves the selected picker 246 | function move(target, event, animate) { 247 | 248 | var input = target.parents('.minicolors').find('.minicolors-input'), 249 | settings = input.data('minicolors-settings'), 250 | picker = target.find('[class$=-picker]'), 251 | offsetX = target.offset().left, 252 | offsetY = target.offset().top, 253 | x = Math.round(event.pageX - offsetX), 254 | y = Math.round(event.pageY - offsetY), 255 | duration = animate ? settings.animationSpeed : 0, 256 | wx, wy, r, phi; 257 | 258 | // Touch support 259 | if( event.originalEvent.changedTouches ) { 260 | x = event.originalEvent.changedTouches[0].pageX - offsetX; 261 | y = event.originalEvent.changedTouches[0].pageY - offsetY; 262 | } 263 | 264 | // Constrain picker to its container 265 | if( x < 0 ) x = 0; 266 | if( y < 0 ) y = 0; 267 | if( x > target.width() ) x = target.width(); 268 | if( y > target.height() ) y = target.height(); 269 | 270 | // Constrain color wheel values to the wheel 271 | if( target.parent().is('.minicolors-slider-wheel') && picker.parent().is('.minicolors-grid') ) { 272 | wx = 75 - x; 273 | wy = 75 - y; 274 | r = Math.sqrt(wx * wx + wy * wy); 275 | phi = Math.atan2(wy, wx); 276 | if( phi < 0 ) phi += Math.PI * 2; 277 | if( r > 75 ) { 278 | r = 75; 279 | x = 75 - (75 * Math.cos(phi)); 280 | y = 75 - (75 * Math.sin(phi)); 281 | } 282 | x = Math.round(x); 283 | y = Math.round(y); 284 | } 285 | 286 | // Move the picker 287 | if( target.is('.minicolors-grid') ) { 288 | picker 289 | .stop(true) 290 | .animate({ 291 | top: y + 'px', 292 | left: x + 'px' 293 | }, duration, settings.animationEasing, function() { 294 | updateFromControl(input, target); 295 | }); 296 | } else { 297 | picker 298 | .stop(true) 299 | .animate({ 300 | top: y + 'px' 301 | }, duration, settings.animationEasing, function() { 302 | updateFromControl(input, target); 303 | }); 304 | } 305 | 306 | } 307 | 308 | // Sets the input based on the color picker values 309 | function updateFromControl(input, target) { 310 | 311 | function getCoords(picker, container) { 312 | 313 | var left, top; 314 | if( !picker.length || !container ) return null; 315 | left = picker.offset().left; 316 | top = picker.offset().top; 317 | 318 | return { 319 | x: left - container.offset().left + (picker.outerWidth() / 2), 320 | y: top - container.offset().top + (picker.outerHeight() / 2) 321 | }; 322 | 323 | } 324 | 325 | var hue, saturation, brightness, x, y, r, phi, 326 | 327 | hex = input.val(), 328 | opacity = input.attr('data-opacity'), 329 | 330 | // Helpful references 331 | minicolors = input.parent(), 332 | settings = input.data('minicolors-settings'), 333 | swatch = minicolors.find('.minicolors-swatch'), 334 | 335 | // Panel objects 336 | grid = minicolors.find('.minicolors-grid'), 337 | slider = minicolors.find('.minicolors-slider'), 338 | opacitySlider = minicolors.find('.minicolors-opacity-slider'), 339 | 340 | // Picker objects 341 | gridPicker = grid.find('[class$=-picker]'), 342 | sliderPicker = slider.find('[class$=-picker]'), 343 | opacityPicker = opacitySlider.find('[class$=-picker]'), 344 | 345 | // Picker positions 346 | gridPos = getCoords(gridPicker, grid), 347 | sliderPos = getCoords(sliderPicker, slider), 348 | opacityPos = getCoords(opacityPicker, opacitySlider); 349 | 350 | // Handle colors 351 | if( target.is('.minicolors-grid, .minicolors-slider') ) { 352 | 353 | // Determine HSB values 354 | switch(settings.control) { 355 | 356 | case 'wheel': 357 | // Calculate hue, saturation, and brightness 358 | x = (grid.width() / 2) - gridPos.x; 359 | y = (grid.height() / 2) - gridPos.y; 360 | r = Math.sqrt(x * x + y * y); 361 | phi = Math.atan2(y, x); 362 | if( phi < 0 ) phi += Math.PI * 2; 363 | if( r > 75 ) { 364 | r = 75; 365 | gridPos.x = 69 - (75 * Math.cos(phi)); 366 | gridPos.y = 69 - (75 * Math.sin(phi)); 367 | } 368 | saturation = keepWithin(r / 0.75, 0, 100); 369 | hue = keepWithin(phi * 180 / Math.PI, 0, 360); 370 | brightness = keepWithin(100 - Math.floor(sliderPos.y * (100 / slider.height())), 0, 100); 371 | hex = hsb2hex({ 372 | h: hue, 373 | s: saturation, 374 | b: brightness 375 | }); 376 | 377 | // Update UI 378 | slider.css('backgroundColor', hsb2hex({ h: hue, s: saturation, b: 100 })); 379 | break; 380 | 381 | case 'saturation': 382 | // Calculate hue, saturation, and brightness 383 | hue = keepWithin(parseInt(gridPos.x * (360 / grid.width()), 10), 0, 360); 384 | saturation = keepWithin(100 - Math.floor(sliderPos.y * (100 / slider.height())), 0, 100); 385 | brightness = keepWithin(100 - Math.floor(gridPos.y * (100 / grid.height())), 0, 100); 386 | hex = hsb2hex({ 387 | h: hue, 388 | s: saturation, 389 | b: brightness 390 | }); 391 | 392 | // Update UI 393 | slider.css('backgroundColor', hsb2hex({ h: hue, s: 100, b: brightness })); 394 | minicolors.find('.minicolors-grid-inner').css('opacity', saturation / 100); 395 | break; 396 | 397 | case 'brightness': 398 | // Calculate hue, saturation, and brightness 399 | hue = keepWithin(parseInt(gridPos.x * (360 / grid.width()), 10), 0, 360); 400 | saturation = keepWithin(100 - Math.floor(gridPos.y * (100 / grid.height())), 0, 100); 401 | brightness = keepWithin(100 - Math.floor(sliderPos.y * (100 / slider.height())), 0, 100); 402 | hex = hsb2hex({ 403 | h: hue, 404 | s: saturation, 405 | b: brightness 406 | }); 407 | 408 | // Update UI 409 | slider.css('backgroundColor', hsb2hex({ h: hue, s: saturation, b: 100 })); 410 | minicolors.find('.minicolors-grid-inner').css('opacity', 1 - (brightness / 100)); 411 | break; 412 | 413 | default: 414 | // Calculate hue, saturation, and brightness 415 | hue = keepWithin(360 - parseInt(sliderPos.y * (360 / slider.height()), 10), 0, 360); 416 | saturation = keepWithin(Math.floor(gridPos.x * (100 / grid.width())), 0, 100); 417 | brightness = keepWithin(100 - Math.floor(gridPos.y * (100 / grid.height())), 0, 100); 418 | hex = hsb2hex({ 419 | h: hue, 420 | s: saturation, 421 | b: brightness 422 | }); 423 | 424 | // Update UI 425 | grid.css('backgroundColor', hsb2hex({ h: hue, s: 100, b: 100 })); 426 | break; 427 | 428 | } 429 | 430 | // Adjust case 431 | input.val( convertCase(hex, settings.letterCase) ); 432 | 433 | } 434 | 435 | // Handle opacity 436 | if( target.is('.minicolors-opacity-slider') ) { 437 | if( settings.opacity ) { 438 | opacity = parseFloat(1 - (opacityPos.y / opacitySlider.height())).toFixed(2); 439 | } else { 440 | opacity = 1; 441 | } 442 | if( settings.opacity ) input.attr('data-opacity', opacity); 443 | } 444 | 445 | // Set swatch color 446 | swatch.find('SPAN').css({ 447 | backgroundColor: hex, 448 | opacity: opacity 449 | }); 450 | 451 | // Handle change event 452 | doChange(input, hex, opacity); 453 | 454 | } 455 | 456 | // Sets the color picker values from the input 457 | function updateFromInput(input, preserveInputValue) { 458 | 459 | var hex, 460 | hsb, 461 | opacity, 462 | x, y, r, phi, 463 | 464 | // Helpful references 465 | minicolors = input.parent(), 466 | settings = input.data('minicolors-settings'), 467 | swatch = minicolors.find('.minicolors-swatch'), 468 | 469 | // Panel objects 470 | grid = minicolors.find('.minicolors-grid'), 471 | slider = minicolors.find('.minicolors-slider'), 472 | opacitySlider = minicolors.find('.minicolors-opacity-slider'), 473 | 474 | // Picker objects 475 | gridPicker = grid.find('[class$=-picker]'), 476 | sliderPicker = slider.find('[class$=-picker]'), 477 | opacityPicker = opacitySlider.find('[class$=-picker]'); 478 | 479 | // Determine hex/HSB values 480 | hex = convertCase(parseHex(input.val(), true), settings.letterCase); 481 | if( !hex ){ 482 | hex = convertCase(parseHex(settings.defaultValue, true), settings.letterCase); 483 | } 484 | hsb = hex2hsb(hex); 485 | 486 | // Update input value 487 | if( !preserveInputValue ) input.val(hex); 488 | 489 | // Determine opacity value 490 | if( settings.opacity ) { 491 | // Get from data-opacity attribute and keep within 0-1 range 492 | opacity = input.attr('data-opacity') === '' ? 1 : keepWithin(parseFloat(input.attr('data-opacity')).toFixed(2), 0, 1); 493 | if( isNaN(opacity) ) opacity = 1; 494 | input.attr('data-opacity', opacity); 495 | swatch.find('SPAN').css('opacity', opacity); 496 | 497 | // Set opacity picker position 498 | y = keepWithin(opacitySlider.height() - (opacitySlider.height() * opacity), 0, opacitySlider.height()); 499 | opacityPicker.css('top', y + 'px'); 500 | } 501 | 502 | // Update swatch 503 | swatch.find('SPAN').css('backgroundColor', hex); 504 | 505 | // Determine picker locations 506 | switch(settings.control) { 507 | 508 | case 'wheel': 509 | // Set grid position 510 | r = keepWithin(Math.ceil(hsb.s * 0.75), 0, grid.height() / 2); 511 | phi = hsb.h * Math.PI / 180; 512 | x = keepWithin(75 - Math.cos(phi) * r, 0, grid.width()); 513 | y = keepWithin(75 - Math.sin(phi) * r, 0, grid.height()); 514 | gridPicker.css({ 515 | top: y + 'px', 516 | left: x + 'px' 517 | }); 518 | 519 | // Set slider position 520 | y = 150 - (hsb.b / (100 / grid.height())); 521 | if( hex === '' ) y = 0; 522 | sliderPicker.css('top', y + 'px'); 523 | 524 | // Update panel color 525 | slider.css('backgroundColor', hsb2hex({ h: hsb.h, s: hsb.s, b: 100 })); 526 | break; 527 | 528 | case 'saturation': 529 | // Set grid position 530 | x = keepWithin((5 * hsb.h) / 12, 0, 150); 531 | y = keepWithin(grid.height() - Math.ceil(hsb.b / (100 / grid.height())), 0, grid.height()); 532 | gridPicker.css({ 533 | top: y + 'px', 534 | left: x + 'px' 535 | }); 536 | 537 | // Set slider position 538 | y = keepWithin(slider.height() - (hsb.s * (slider.height() / 100)), 0, slider.height()); 539 | sliderPicker.css('top', y + 'px'); 540 | 541 | // Update UI 542 | slider.css('backgroundColor', hsb2hex({ h: hsb.h, s: 100, b: hsb.b })); 543 | minicolors.find('.minicolors-grid-inner').css('opacity', hsb.s / 100); 544 | break; 545 | 546 | case 'brightness': 547 | // Set grid position 548 | x = keepWithin((5 * hsb.h) / 12, 0, 150); 549 | y = keepWithin(grid.height() - Math.ceil(hsb.s / (100 / grid.height())), 0, grid.height()); 550 | gridPicker.css({ 551 | top: y + 'px', 552 | left: x + 'px' 553 | }); 554 | 555 | // Set slider position 556 | y = keepWithin(slider.height() - (hsb.b * (slider.height() / 100)), 0, slider.height()); 557 | sliderPicker.css('top', y + 'px'); 558 | 559 | // Update UI 560 | slider.css('backgroundColor', hsb2hex({ h: hsb.h, s: hsb.s, b: 100 })); 561 | minicolors.find('.minicolors-grid-inner').css('opacity', 1 - (hsb.b / 100)); 562 | break; 563 | 564 | default: 565 | // Set grid position 566 | x = keepWithin(Math.ceil(hsb.s / (100 / grid.width())), 0, grid.width()); 567 | y = keepWithin(grid.height() - Math.ceil(hsb.b / (100 / grid.height())), 0, grid.height()); 568 | gridPicker.css({ 569 | top: y + 'px', 570 | left: x + 'px' 571 | }); 572 | 573 | // Set slider position 574 | y = keepWithin(slider.height() - (hsb.h / (360 / slider.height())), 0, slider.height()); 575 | sliderPicker.css('top', y + 'px'); 576 | 577 | // Update panel color 578 | grid.css('backgroundColor', hsb2hex({ h: hsb.h, s: 100, b: 100 })); 579 | break; 580 | 581 | } 582 | 583 | // Fire change event, but only if minicolors is fully initialized 584 | if( input.data('minicolors-initialized') ) { 585 | doChange(input, hex, opacity); 586 | } 587 | 588 | } 589 | 590 | // Runs the change and changeDelay callbacks 591 | function doChange(input, hex, opacity) { 592 | 593 | var settings = input.data('minicolors-settings'), 594 | lastChange = input.data('minicolors-lastChange'); 595 | 596 | // Only run if it actually changed 597 | if( !lastChange || lastChange.hex !== hex || lastChange.opacity !== opacity ) { 598 | 599 | // Remember last-changed value 600 | input.data('minicolors-lastChange', { 601 | hex: hex, 602 | opacity: opacity 603 | }); 604 | 605 | // Fire change event 606 | if( settings.change ) { 607 | if( settings.changeDelay ) { 608 | // Call after a delay 609 | clearTimeout(input.data('minicolors-changeTimeout')); 610 | input.data('minicolors-changeTimeout', setTimeout( function() { 611 | settings.change.call(input.get(0), hex, opacity); 612 | }, settings.changeDelay)); 613 | } else { 614 | // Call immediately 615 | settings.change.call(input.get(0), hex, opacity); 616 | } 617 | } 618 | input.trigger('change').trigger('input'); 619 | } 620 | 621 | } 622 | 623 | // Generates an RGB(A) object based on the input's value 624 | function rgbObject(input) { 625 | var hex = parseHex($(input).val(), true), 626 | rgb = hex2rgb(hex), 627 | opacity = $(input).attr('data-opacity'); 628 | if( !rgb ) return null; 629 | if( opacity !== undefined ) $.extend(rgb, { a: parseFloat(opacity) }); 630 | return rgb; 631 | } 632 | 633 | // Genearates an RGB(A) string based on the input's value 634 | function rgbString(input, alpha) { 635 | var hex = parseHex($(input).val(), true), 636 | rgb = hex2rgb(hex), 637 | opacity = $(input).attr('data-opacity'); 638 | if( !rgb ) return null; 639 | if( opacity === undefined ) opacity = 1; 640 | if( alpha ) { 641 | return 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + parseFloat(opacity) + ')'; 642 | } else { 643 | return 'rgb(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ')'; 644 | } 645 | } 646 | 647 | // Converts to the letter case specified in settings 648 | function convertCase(string, letterCase) { 649 | return letterCase === 'uppercase' ? string.toUpperCase() : string.toLowerCase(); 650 | } 651 | 652 | // Parses a string and returns a valid hex string when possible 653 | function parseHex(string, expand) { 654 | string = string.replace(/[^A-F0-9]/ig, ''); 655 | if( string.length !== 3 && string.length !== 6 ) return ''; 656 | if( string.length === 3 && expand ) { 657 | string = string[0] + string[0] + string[1] + string[1] + string[2] + string[2]; 658 | } 659 | return '#' + string; 660 | } 661 | 662 | // Keeps value within min and max 663 | function keepWithin(value, min, max) { 664 | if( value < min ) value = min; 665 | if( value > max ) value = max; 666 | return value; 667 | } 668 | 669 | // Converts an HSB object to an RGB object 670 | function hsb2rgb(hsb) { 671 | var rgb = {}; 672 | var h = Math.round(hsb.h); 673 | var s = Math.round(hsb.s * 255 / 100); 674 | var v = Math.round(hsb.b * 255 / 100); 675 | if(s === 0) { 676 | rgb.r = rgb.g = rgb.b = v; 677 | } else { 678 | var t1 = v; 679 | var t2 = (255 - s) * v / 255; 680 | var t3 = (t1 - t2) * (h % 60) / 60; 681 | if( h === 360 ) h = 0; 682 | if( h < 60 ) { rgb.r = t1; rgb.b = t2; rgb.g = t2 + t3; } 683 | else if( h < 120 ) {rgb.g = t1; rgb.b = t2; rgb.r = t1 - t3; } 684 | else if( h < 180 ) {rgb.g = t1; rgb.r = t2; rgb.b = t2 + t3; } 685 | else if( h < 240 ) {rgb.b = t1; rgb.r = t2; rgb.g = t1 - t3; } 686 | else if( h < 300 ) {rgb.b = t1; rgb.g = t2; rgb.r = t2 + t3; } 687 | else if( h < 360 ) {rgb.r = t1; rgb.g = t2; rgb.b = t1 - t3; } 688 | else { rgb.r = 0; rgb.g = 0; rgb.b = 0; } 689 | } 690 | return { 691 | r: Math.round(rgb.r), 692 | g: Math.round(rgb.g), 693 | b: Math.round(rgb.b) 694 | }; 695 | } 696 | 697 | // Converts an RGB object to a hex string 698 | function rgb2hex(rgb) { 699 | var hex = [ 700 | rgb.r.toString(16), 701 | rgb.g.toString(16), 702 | rgb.b.toString(16) 703 | ]; 704 | $.each(hex, function(nr, val) { 705 | if (val.length === 1) hex[nr] = '0' + val; 706 | }); 707 | return '#' + hex.join(''); 708 | } 709 | 710 | // Converts an HSB object to a hex string 711 | function hsb2hex(hsb) { 712 | return rgb2hex(hsb2rgb(hsb)); 713 | } 714 | 715 | // Converts a hex string to an HSB object 716 | function hex2hsb(hex) { 717 | var hsb = rgb2hsb(hex2rgb(hex)); 718 | if( hsb.s === 0 ) hsb.h = 360; 719 | return hsb; 720 | } 721 | 722 | // Converts an RGB object to an HSB object 723 | function rgb2hsb(rgb) { 724 | var hsb = { h: 0, s: 0, b: 0 }; 725 | var min = Math.min(rgb.r, rgb.g, rgb.b); 726 | var max = Math.max(rgb.r, rgb.g, rgb.b); 727 | var delta = max - min; 728 | hsb.b = max; 729 | hsb.s = max !== 0 ? 255 * delta / max : 0; 730 | if( hsb.s !== 0 ) { 731 | if( rgb.r === max ) { 732 | hsb.h = (rgb.g - rgb.b) / delta; 733 | } else if( rgb.g === max ) { 734 | hsb.h = 2 + (rgb.b - rgb.r) / delta; 735 | } else { 736 | hsb.h = 4 + (rgb.r - rgb.g) / delta; 737 | } 738 | } else { 739 | hsb.h = -1; 740 | } 741 | hsb.h *= 60; 742 | if( hsb.h < 0 ) { 743 | hsb.h += 360; 744 | } 745 | hsb.s *= 100/255; 746 | hsb.b *= 100/255; 747 | return hsb; 748 | } 749 | 750 | // Converts a hex string to an RGB object 751 | function hex2rgb(hex) { 752 | hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16); 753 | return { 754 | r: hex >> 16, 755 | g: (hex & 0x00FF00) >> 8, 756 | b: (hex & 0x0000FF) 757 | }; 758 | } 759 | 760 | // Handle events 761 | $(document) 762 | // Hide on clicks outside of the control 763 | .on('mousedown.minicolors touchstart.minicolors', function(event) { 764 | if( !$(event.target).parents().add(event.target).hasClass('minicolors') ) { 765 | hide(); 766 | } 767 | }) 768 | // Start moving 769 | .on('mousedown.minicolors touchstart.minicolors', '.minicolors-grid, .minicolors-slider, .minicolors-opacity-slider', function(event) { 770 | var target = $(this); 771 | event.preventDefault(); 772 | $(document).data('minicolors-target', target); 773 | move(target, event, true); 774 | }) 775 | // Move pickers 776 | .on('mousemove.minicolors touchmove.minicolors', function(event) { 777 | var target = $(document).data('minicolors-target'); 778 | if( target ) move(target, event); 779 | }) 780 | // Stop moving 781 | .on('mouseup.minicolors touchend.minicolors', function() { 782 | $(this).removeData('minicolors-target'); 783 | }) 784 | // Show panel when swatch is clicked 785 | .on('mousedown.minicolors touchstart.minicolors', '.minicolors-swatch', function(event) { 786 | var input = $(this).parent().find('.minicolors-input'); 787 | event.preventDefault(); 788 | show(input); 789 | }) 790 | // Show on focus 791 | .on('focus.minicolors', '.minicolors-input', function() { 792 | var input = $(this); 793 | if( !input.data('minicolors-initialized') ) return; 794 | show(input); 795 | }) 796 | // Fix hex on blur 797 | .on('blur.minicolors', '.minicolors-input', function() { 798 | var input = $(this), 799 | settings = input.data('minicolors-settings'); 800 | if( !input.data('minicolors-initialized') ) return; 801 | 802 | // Parse Hex 803 | input.val(parseHex(input.val(), true)); 804 | 805 | // Is it blank? 806 | if( input.val() === '' ) input.val(parseHex(settings.defaultValue, true)); 807 | 808 | // Adjust case 809 | input.val( convertCase(input.val(), settings.letterCase) ); 810 | 811 | }) 812 | // Handle keypresses 813 | .on('keydown.minicolors', '.minicolors-input', function(event) { 814 | var input = $(this); 815 | if( !input.data('minicolors-initialized') ) return; 816 | switch(event.keyCode) { 817 | case 9: // tab 818 | hide(); 819 | break; 820 | case 13: // enter 821 | case 27: // esc 822 | hide(); 823 | input.blur(); 824 | break; 825 | } 826 | }) 827 | // Update on keyup 828 | .on('keyup.minicolors', '.minicolors-input', function() { 829 | var input = $(this); 830 | if( !input.data('minicolors-initialized') ) return; 831 | updateFromInput(input, true); 832 | }) 833 | // Update on paste 834 | .on('paste.minicolors', '.minicolors-input', function() { 835 | var input = $(this); 836 | if( !input.data('minicolors-initialized') ) return; 837 | setTimeout( function() { 838 | updateFromInput(input, true); 839 | }, 1); 840 | }); 841 | 842 | })(jQuery); --------------------------------------------------------------------------------